我们组一个React中后台项目,跑了三年多了,452个模块。Webpack 5打包一次生产环境接近两分钟,CI排队的时候看着进度条,总觉得在浪费生命。 上周刷GitHub看到Rspack已经2.1.7了,release note里写了不少性能优化,我寻思趁这周需求间隙试试。官方文档看起来挺清爽的:改个配置文件名,换几个loader,完事。半天就够了吧。 结果折腾了两天。文档写得挺清楚,但坑都在文档没写的地方。
SWC替换Babel,上来就给我一棒子
配置文件改名那步确实没啥好说的,webpack.config.js 改成 rspack.config.js,webpack 换成 @rspack/core,内置插件名字换一换,五分钟搞定。跑 npx rspack build,直接就开始编译了,没报错。 然后我把 babel-loader 换成 builtin:swc-loader,按照文档的示例改了改配置:
css
// 改之前
{
test: /.(j|t)sx?$/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
'@babel/plugin-proposal-decorators',
'babel-plugin-styled-components'
]
}
}
// 改之后(第一版,有问题的)
{
test: /.(j|t)sx?$/,
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: true
},
transform: {
react: { runtime: 'automatic' },
legacyDecorator: true
}
}
}
}
编译炸了。报错大概长这样:
javascript
Error: Expected ';', '}' or <EOF>
,- src/utils/legacy-parser.ts:42:15
|
42 | const result = arr?.filter(x => x != null)
| ^
可选链 ?. 报语法错误??我第一反应是项目里是不是有什么奇怪的语法没注意到,检查了一圈发现不是。然后我以为是 SWC 配置哪里写错了,试了好几种写法,改 parser、改 target、改 transform,折腾了一个多小时都不行。
最后去翻 Rspack 的 issues 才搞明白:builtin:swc-loader 的 syntax: 'typescript' 并不会自动开启所有 ES 新特性的解析。Babel 的 @babel/preset-env 默认就处理可选链、空值合并这些,但 SWC 不会,得在 jsc 配置里显式指定 target:
yaml
// 最终能用的版本
{
test: /.(j|t)sx?$/,
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: true
},
transform: {
react: { runtime: 'automatic' },
legacyDecorator: true
},
target: 'es2020' // 加上这行就好了
}
}
}
结果还没完。我们项目里有一些 .js 文件也用了 decorators(别问为什么,三年前的代码,什么妖魔鬼怪都有),TypeScript parser 不认 JS 文件里的 decorators。又得给 .js 单独写一条规则:
yaml
{
test: /.js$/,
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'ecmascript', decorators: true },
transform: { legacyDecorator: true }
}
}
}
光这一个 loader 就搞了我大半天。Babel 时代 @babel/preset-env 一把梭的日子一去不复返了。SWC 配置粒度更细,但你得真的知道自己在配什么。
另外 babel-plugin-styled-components 这个------SWC 里没有直接对应的插件。我们项目里用 styled-components 不多,趁这个机会直接迁到 CSS Modules 了。如果你们项目里 styled-components 用得重,这个迁移成本得额外算。
CSS Modules全炸了,差点回滚
Loader 搞完之后编译总算过了。心想最难的应该搞定了,跑起来看看效果吧。 页面出来了。然后我差点没认出来------所有样式都乱了。 我们在 Webpack 里 CSS Modules 用的 [local]_[hash:8] 命名:
css
// Webpack的配置
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]_[hash:8]'
}
}
}
我一开始在 Rspack 里这么写的:
bash
// 有问题的版本
{
test: /.module.css$/,
use: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],
type: 'css/module'
}
问题出在 type: 'css/module' 走的是 Rspack 内置的 CSS Modules 处理,它用的是自己的默认命名规则,完全忽略了 css-loader 里的 localIdentName。两种机制不能混用,我当时不知道,配置写上去跟没写一样。
搞到第二天下午还没搞定的时候,我一度想把配置回滚了,先不迁了。后来静下心来翻 Rspack 文档里 CSS 那章,才发现内置方式要把命名配置放在 module.parser 里:
css
// 最终正确的版本
module: {
parser: {
'css/module': {
exports: 'local',
localIdentName: '[local]_[hash:8]'
}
},
rules: [
{
test: /.module.css$/,
type: 'css/module',
use: [rspack.CssExtractRspackPlugin.loader]
}
]
}
改完样式恢复了。哦对,还有一点------如果你的项目不是用 .module.css 后缀区分,而是在 webpack 里配 modules: { auto: true } 按文件名自动识别的,Rspack 的 auto 行为和 Webpack 不完全一致,这块得多测试。
剩下的坑不大但都挺烦人
copy-webpack-plugin 换成 rspack.CopyRspackPlugin,配置格式基本一样,这个倒丝滑。fork-ts-checker-webpack-plugin 换成 ts-checker-rspack-plugin,注意 Rspack 2.x 推荐搭配 TypeScript 7 使用,我们还在 TS 5.x,暂时兼容但最好计划升级。 thread-loader 和 cache-loader 直接删掉就行------Rspack 本身就是 Rust 多线程编译,内置持久化缓存,这俩在 Webpack 时代用来加速的 loader 在这反而拖后腿。webpack-dev-server 换成 rspack serve,代理配置大部分兼容,但 historyApiFallback 的 rewrites 函数传参方式有点不一样,踩了一下。 对了还有 import.meta.env------我一开始看 2.1.6 的 changelog 说支持了,正高兴呢,结果 2.1.7 又给 revert 了。老老实实用 DefinePlugin 吧。
迁移完的感受
冷启动从38秒降到6秒左右,HMR从400多毫秒降到不到100毫秒,生产构建从快两分钟降到15秒上下。内存从将近2G降到600多M。 冷启动那个提升说实话感知不强,等6秒反正也会去倒杯水。但 HMR 是实打实的体感变化------改一行代码页面上几乎是即时反馈,之前那400毫秒的延迟其实每次都在打断思路。CI 构建时间缩短对发布节奏的提升也是真的,push 完基本就能部署了。
值不值得迁,得看你自己的情况。我们项目是 Webpack 5,450多个模块,构建两分钟,迁完确实爽。如果你项目规模差不多,我觉得可以试试。但如果你还在 Webpack 4,先升到5吧,Rspack对4的loader兼容性更差,迁移成本会高不少。已经在用Vite的就别折腾了,Rspack的优势是Webpack存量项目的平滑迁移,跟Vite不在一个赛道。
最后一个建议:迁之前先把你那个 Webpack 配置理一遍。我们那个配置文件三年里好几个人往上叠,里面有不少已经没用的 loader。趁迁移清理一遍,比翻译一份臃肿的配置省事得多。