[tgui] Retvrn to rspack (#91982)

## About The Pull Request
Swaps us back to RSPack. I keep getting pings about this #91925, which I
can only guess is webpack related. That, and I don't want to just give
up on rspack, I was merely frustrated with it while fixing bun builds.

## Why It's Good For The Game
Less javascript. Puts us back on our last build tool. Might fix the html
entity issue
We can also run the config in ts which I think is sweet

## Changelog
This commit is contained in:
Jeremiah
2025-07-09 12:17:09 +12:00
committed by GitHub
parent 4f422e5d5c
commit 87a4c082ab
9 changed files with 590 additions and 473 deletions
-15
View File
@@ -1,15 +0,0 @@
{
"$schema": "https://swc.rs/schema.json",
"jsc": {
"loose": true,
"parser": {
"syntax": "typescript",
"tsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
}
}
}
+392 -243
View File
File diff suppressed because it is too large Load Diff
+8 -13
View File
@@ -7,9 +7,9 @@
"packages/*"
],
"scripts": {
"tgui:analyze": "webpack --analyze",
"tgui:build": "webpack",
"tgui:dev": "bun packages/tgui-dev-server/index.ts",
"tgui:analyze": "rspack --analyze",
"tgui:build": "rspack build",
"tgui:dev": "bun --smol packages/tgui-dev-server/index.ts",
"tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx",
"tgui:prettier": "prettier --check .",
"tgui:test": "bun test",
@@ -18,25 +18,20 @@
"tgui:eslint-fix": "eslint --fix packages --ext .js,.cjs,.ts,.jsx,.tsx"
},
"dependencies": {
"@swc/core": "^1.12.1",
"@rspack/cli": "^1.4.3",
"@rspack/core": "^1.4.3",
"@typescript-eslint/parser": "^8.13.0",
"@typescript-eslint/utils": "^8.13.0",
"css-loader": "^7.1.2",
"esbuild-loader": "^4.3.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-unused-imports": "^3.1.0",
"mini-css-extract-plugin": "^2.9.2",
"prettier": "^3.5.3",
"sass": "^1.80.6",
"sass-loader": "^16.0.3",
"swc-loader": "^0.2.6",
"typescript": "^5.8.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^6.0.1"
"sass-embedded": "^1.89.2",
"sass-loader": "^16.0.5",
"typescript": "^5.8.3"
},
"devDependencies": {
"@happy-dom/global-registrator": "^17.4.7",
+4 -2
View File
@@ -7,14 +7,16 @@
import fs from 'node:fs';
import { reloadByondCache } from './reloader';
import { createCompiler } from './webpack';
import { RspackCompiler } from './webpack';
const reloadOnce = process.argv.includes('--reload');
async function setupServer() {
fs.mkdirSync('./public/.tmp', { recursive: true });
const compiler = await createCompiler({ mode: 'development', hot: true });
const compiler = new RspackCompiler();
await compiler.setup();
// Reload cache once
if (reloadOnce) {
+18 -45
View File
@@ -1,65 +1,39 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { createRequire } from 'module';
import { createRequire } from 'node:module';
import { config } from '../../rspack.config-dev';
import { loadSourceMaps } from './link/retrace';
import { broadcastMessage, setupLink } from './link/server';
import { createLogger } from './logging.js';
import { reloadByondCache } from './reloader.js';
import { createLogger } from './logging';
import { reloadByondCache } from './reloader';
import { resolveGlob } from './util';
const logger = createLogger('webpack');
const logger = createLogger('rspack');
export async function createCompiler(
options: Record<string, any>,
): Promise<WebpackCompiler> {
const compiler = new WebpackCompiler();
await compiler.setup(options);
export class RspackCompiler {
rspack: any;
config: any;
bundleDir: string;
return compiler;
}
type WebpackImport = typeof import('webpack');
class WebpackCompiler {
public webpack: WebpackImport;
public config: Record<string, any>;
public bundleDir: string;
async setup(options: Record<string, any>): Promise<void> {
async setup() {
// Create a require context that is relative to project root
// and retrieve all necessary dependencies.
const requireFromRoot = createRequire(import.meta.dirname + '/../../..');
const webpack: WebpackImport = await requireFromRoot('webpack');
const requireFromRoot = createRequire(`${import.meta.dirname}/../../..`);
const rspack = await requireFromRoot('@rspack/core');
const createConfig = await requireFromRoot('./webpack.config.js');
const config = createConfig({}, options);
// Inject the HMR plugin into the config if we're using it
if (options.hot) {
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
this.webpack = webpack;
this.rspack = rspack;
this.config = config;
this.bundleDir = config.output.path;
this.bundleDir = config.output?.path || '';
}
async watch(): Promise<void> {
async watch() {
logger.log('setting up');
// Setup link
const link = setupLink();
setupLink();
// Instantiate the compiler
const compiler = this.webpack.webpack(this.config);
const compiler = this.rspack.rspack(this.config);
// Clear garbage before compiling
compiler.hooks.watchRun.tapPromise('tgui-dev-server', async () => {
const files = await resolveGlob(this.bundleDir, '*.hot-update.*');
logger.log(`clearing garbage (${files.length} files)`);
for (const file of files) {
await Bun.file(file).delete();
}
@@ -77,7 +51,6 @@ class WebpackCompiler {
type: 'hotUpdate',
});
});
// Start watching
logger.log('watching for changes');
compiler.watch({}, (err, stats) => {
@@ -86,7 +59,7 @@ class WebpackCompiler {
return;
}
stats
?.toString(this.config.devServer?.stats)
?.toString(this.config.stats)
.split('\n')
.forEach((line) => logger.log(line));
});
+29
View File
@@ -0,0 +1,29 @@
import path from 'node:path';
import rspack, { type Configuration } from '@rspack/core';
import oldConfig, { createStats } from './rspack.config';
export const config = {
...oldConfig,
devtool: 'cheap-module-source-map',
devServer: {
hot: true,
},
mode: 'development',
output: {
...oldConfig.output,
path: path.resolve(import.meta.dirname, './public/.tmp'),
},
plugins: [
new rspack.CssExtractRspackPlugin({
chunkFilename: '[name].bundle.css',
filename: '[name].bundle.css',
}),
new rspack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new rspack.HotModuleReplacementPlugin(),
],
stats: createStats(false),
} satisfies Configuration;
+132
View File
@@ -0,0 +1,132 @@
import path from 'node:path';
import { defineConfig } from '@rspack/cli';
import rspack, { type StatsOptions } from '@rspack/core';
export function createStats(verbose: boolean): StatsOptions {
return {
assets: verbose,
builtAt: verbose,
cached: false,
children: false,
chunks: false,
colors: true,
entrypoints: true,
hash: false,
modules: false,
performance: false,
timings: verbose,
version: verbose,
};
}
const dirname = path.resolve();
export default defineConfig({
context: dirname,
devtool: false,
entry: {
tgui: './packages/tgui',
'tgui-panel': './packages/tgui-panel',
'tgui-say': './packages/tgui-say',
},
mode: 'production',
module: {
rules: [
{
test: /\.([tj]s(x)?|cjs)$/,
type: 'javascript/auto',
use: [
{
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
],
},
{
test: /\.(s)?css$/,
type: 'javascript/auto',
use: [
rspack.CssExtractRspackPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
options: {
api: 'modern-compiler',
implementation: 'sass-embedded',
},
},
],
},
{
test: /\.(png|jpg)$/,
type: 'asset/resource',
generator: {
filename: '[name][ext]',
},
},
{
test: /\.svg$/,
oneOf: [
{
issuer: /\.(s)?css$/,
type: 'asset/inline',
},
{
type: 'asset/resource',
},
],
generator: {
filename: '[name][ext]',
},
},
],
},
optimization: {
emitOnErrors: false,
},
output: {
path: 'public',
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
chunkLoadTimeout: 15000,
publicPath: '/',
assetModuleFilename: '[name][ext]',
},
performance: {
hints: false,
},
plugins: [
new rspack.CssExtractRspackPlugin({
chunkFilename: '[name].bundle.css',
filename: '[name].bundle.css',
}),
new rspack.EnvironmentPlugin({
NODE_ENV: 'production',
}),
],
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
tgui: path.resolve(dirname, './packages/tgui'),
'tgui-panel': path.resolve(dirname, './packages/tgui-panel'),
'tgui-say': path.resolve(dirname, './packages/tgui-say'),
'tgui-dev-server': path.resolve(dirname, './packages/tgui-dev-server'),
},
},
stats: createStats(true),
target: ['web', 'browserslist:edge >= 123'],
});
-148
View File
@@ -1,148 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
const webpack = require('webpack');
const path = require('path');
const ExtractCssPlugin = require('mini-css-extract-plugin');
const createStats = (verbose) => ({
assets: verbose,
builtAt: verbose,
cached: false,
children: false,
chunks: false,
colors: true,
entrypoints: true,
hash: false,
modules: false,
performance: false,
timings: verbose,
version: verbose,
});
module.exports = (env = {}, argv) => {
const mode = argv.mode || 'production';
const config = {
mode,
context: path.resolve(__dirname),
target: ['web', 'browserslist:edge>=123'],
entry: {
tgui: ['./packages/tgui'],
'tgui-panel': ['./packages/tgui-panel'],
'tgui-say': ['./packages/tgui-say'],
},
output: {
path:
mode !== 'production'
? path.resolve(__dirname, './public/.tmp')
: path.resolve(__dirname, './public'),
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
chunkLoadTimeout: 15000,
publicPath: '/',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
tgui: path.resolve(__dirname, './packages/tgui'),
'tgui-panel': path.resolve(__dirname, './packages/tgui-panel'),
'tgui-say': path.resolve(__dirname, './packages/tgui-say'),
'tgui-dev-server': path.resolve(
__dirname,
'./packages/tgui-dev-server',
),
},
},
module: {
rules: [
{
test: /\.([tj]s(x)?|cjs)$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('swc-loader'),
},
],
},
{
test: /\.(s)?css$/,
use: [
ExtractCssPlugin.loader,
require.resolve('css-loader'),
require.resolve('sass-loader'),
],
},
{
test: /\.(cur|png|jpg)$/,
type: 'asset/resource',
},
{
test: /.svg$/,
oneOf: [
{
issuer: /\.(s)?css$/,
type: 'asset/inline',
},
{
type: 'asset/resource',
},
],
},
],
},
optimization: {
emitOnErrors: false,
},
performance: {
hints: false,
},
devtool: false,
cache: {
type: 'filesystem',
cacheLocation: path.resolve(
__dirname,
`node_modules/.cache/webpack/${mode}`,
),
buildDependencies: {
config: [__filename],
},
},
stats: createStats(true),
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: mode,
}),
new ExtractCssPlugin({
filename: '[name].bundle.css',
chunkFilename: '[name].bundle.css',
}),
],
};
// Production build specific options
if (mode === 'production') {
const { EsbuildPlugin } = require('esbuild-loader');
config.optimization.minimizer = [
new EsbuildPlugin({
css: true,
legalComments: 'none',
}),
];
} else {
config.devServer = {
clientLogLevel: 'silent',
hot: true,
noInfo: false,
progress: false,
quiet: false,
stats: createStats(false),
};
config.devtool = 'cheap-module-source-map';
}
return config;
};
+7 -7
View File
@@ -26,7 +26,7 @@ const dependencies: Record<string, any> = await Bun.file("dependencies.sh")
.then(formatDeps)
.catch((err) => {
Juke.logger.error(
"Failed to read dependencies.sh, please ensure it exists and is formatted correctly."
"Failed to read dependencies.sh, please ensure it exists and is formatted correctly.",
);
Juke.logger.error(err);
throw new Juke.ExitCode(1);
@@ -82,7 +82,7 @@ export const NoWarningParameter = new Juke.Parameter({
export const CutterTarget = new Juke.Target({
onlyWhen: () => {
const files = Juke.glob(cutter_path);
return files.length == 0;
return files.length === 0;
},
executes: async () => {
const repo = dependencies.CUTTER_REPO;
@@ -225,7 +225,7 @@ export const DmTestTarget = new Juke.Target({
"-trusted",
"-verbose",
"-params",
"log-directory=ci"
"log-directory=ci",
);
Juke.rm("*.test.*");
try {
@@ -272,7 +272,7 @@ export const AutowikiTarget = new Juke.Target({
"-trusted",
"-verbose",
"-params",
"log-directory=ci"
"log-directory=ci",
);
Juke.rm("*.test.*");
if (!fs.existsSync("data/autowiki_edits.txt")) {
@@ -305,11 +305,11 @@ export const TgFontTarget = new Juke.Target({
fs.mkdirSync("tgui/packages/tgfont/static", { recursive: true });
fs.copyFileSync(
"tgui/packages/tgfont/dist/tgfont.css",
"tgui/packages/tgfont/static/tgfont.css"
"tgui/packages/tgfont/static/tgfont.css",
);
fs.copyFileSync(
"tgui/packages/tgfont/dist/tgfont.woff2",
"tgui/packages/tgfont/static/tgfont.woff2"
"tgui/packages/tgfont/static/tgfont.woff2",
);
},
});
@@ -317,7 +317,7 @@ export const TgFontTarget = new Juke.Target({
export const TguiTarget = new Juke.Target({
dependsOn: [BunTarget],
inputs: [
"tgui/webpack.config.js",
"tgui/rspack.config.mjs",
"tgui/**/package.json",
"tgui/packages/**/*.+(js|cjs|ts|tsx|jsx|scss)",
],