我们那个写了五年的老旧 React 项目,开发启动时间已经飙升到了 3 分钟,每次修改代码热更新(HMR)都要等上 15 到 20 秒。开发体验差到极致,天天都在“改行代码去接杯水”的节奏里度过。之前尝试过转到 Vite,但在转换大量 CommonJS 依赖和各种非标准的 webpack 魔法语法时,直接被排查不完的运行时报错劝退。最终,我们把目光投向了近来大热的 Rust 构建工具——Rspack。

迁移过程并不是一键搞定的爽剧,而是充满了跟各种遗留 Webpack 插件斗智斗勇的踩坑史。虽然官方宣传 Rspack 拥有极高的 Webpack 兼容度,但如果你的项目里塞满了自定义 Loader、特定版本的 Less 配置,或者依赖了某些冷门的 Webpack 内部钩子,那迁移过程依然是个心智负担极大的重体力活。

我们遇到的第一个大坑是 less-loader 与 CSS Modules 的配合问题。旧项目里大量使用了 CSS Modules 进行样式隔离,同时在 Less 中定义了许多全局混入(mixin)。Rspack 内置了极其高效的 CSS 处理器,但默认的 CSS Modules 处理逻辑和原本 Webpack 配合 less-loader 有细微区别。如果不做硬编码级别的兼容,编译出来的类名哈希值就会错乱,导致整个后台管理界面的样式直接崩溃。

下面是我们耗费了两天时间,反复调整并最终稳定跑在生产环境的 rspack.config.js 配置:

const path = require('path');
const rspack = require('@rspack/core');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.tsx',
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(j|t)sx?$/,
        use: [
          {
            loader: 'builtin:swc-loader',
            options: {
              jsc: {
                parser: {
                  syntax: 'typescript',
                  tsx: true,
                },
                transform: {
                  react: {
                    runtime: 'automatic',
                    development: true,
                    refresh: true,
                  },
                },
              },
            },
          },
        ],
      },
      {
        test: /\.module\.less$/,
        type: 'css/module',
        use: [
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                javascriptEnabled: true,
                modifyVars: {
                  '@primary-color': '#1890ff',
                },
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        exclude: /\.module\.less$/,
        type: 'css',
        use: [
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                javascriptEnabled: true,
              },
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new rspack.ReactRefreshPlugin(),
    new rspack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
  ],
  devServer: {
    port: 8080,
    hot: true,
    historyApiFallback: true,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
};

这里要吐槽一下 Rspack 的内置 builtin:swc-loader。它的编译速度确实无可挑剔,但如果你的 React 代码里用到了旧版的装饰器(Legacy Decorators)或者特定的 TS 元数据反射(Metadata Reflection),就必须在 swc-loader 的配置里显式开启对应的编译器开关,否则直接在词法解析阶段就会报错。

其次就是旧的打包插件生态。像 copy-webpack-plugin 这种工具,在 Rspack 中应该干脆替换为 Rspack 内置的 rspack.CopyRspackPlugin,以榨干 Rust 级别的执行性能。直接混用 Webpack 的旧 JS 插件往往会导致 Rust 核心与 Node.js 之间频繁进行序列化通信,极大地拖慢编译进程,甚至引发神秘的内存泄漏。

目前,迁移到 Rspack 后的开发首屏启动直接从 3 分钟缩短到了 12 秒,热更新几乎是秒级完成,体验提升非常显著。至于生产构建,我们暂时保留了原有的 Webpack 打包分支,做双引擎并跑,观察一段时间看看 Rspack 输出的代码在老旧浏览器上是否会有潜在的兼容性问题。