包阅导读总结
1. 关键词:Rspack、webpack、迁移、性能优势、经验教训
2. 总结:前 Firefox 工程师分享从 webpack 迁移到 Rspack 的经验,包括选择 Rspack 的原因、迁移中的问题及解决办法、性能对比等,认为 Rspack 有成功机会,期待其未来发展。
3. 主要内容:
– 前言
– 前 Firefox 工程师分享从 webpack 迁移到 Rspack 的经验
– 为什么选择 Rspack
– 升级方案简单低风险,能加速构建,提高生产力和降低成本
– 相比 Vite,对特定项目更适合
– 迁移中的经验教训
– TypeScript
– 需注意 SWC 转换的设置和默认的类型检查方式
– CSS
– 优化配置和压缩设置以减小资源体积
– Service Workers
– 原生支持有特点,需注意相关设置
– React Cosmos
– 迁移存在障碍,后有可用插件
– Knip
– 原不支持 Rspack,后开发相关插件
– 命令行选项
– 与 webpack-dev-server 存在差异
– Rsdoctor
– 用于分析包和构建时间的插件
– 结果
– SPA 项目和 Web extension 项目构建速度大幅提升
– 展望未来
– 期待 Rspack 被市场接受和未来发展
思维导图:
文章地址:https://mp.weixin.qq.com/s/JEcys-EmqdzokbSeB25MUA
文章来源:mp.weixin.qq.com
作者:Brian??Birtles
发布时间:2024/8/24 0:00
语言:中文
总字数:4463字
预计阅读时间:18分钟
评分:86分
标签:前端构建工具,Rspack,webpack,性能优化,迁移经验
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
前言
前 Firefox 工程师 Brian Birtles 分享了从 webpack 迁移到 Rspack 的经验教训,包括迁移动机、实际操作、遇到的问题及解决方案,以及 Rspack 的性能优势。今日前端早读课文章由公号:ByteDance Web Infra 分享。
@Brian Birtles 为前 Firefox 工程师,W3C Web Animations 工作组成员。
正文从这开始~~
注意:本文发布时,Rspack 的最新版本是 v1.0.0-beta.4。随着 Rspack 接近 1.0 正式发布,一些细节可能会有所变化。
Rspack 是一个基于 Rust 的 webpack 替代品,承诺能够更快,并且还包括一些常见的便利功能。在首次尝试的一年之后,我终于完成了将我最大的两个 webpack 项目转换为 Rspack。
【第3108期】Bundler 的设计取舍:为什么要开发 Rspack?
以下是我在过程中学到的一些经验教训。
为什么选择 Rspack?
首先,为什么要选择 Rspack?
对我来说,这是因为我已经在我的两个最大项目中使用 webpack,而与其他构建工具相比,Rspack 提供了一个相对简单且低风险的升级方案。这些项目的构建时间也足够慢,加速构建可以显著提高生产力,并降低 CI 成本。
那么,为什么不选择 Vite 呢?很多人已经从 webpack 切换到 Vite,并且非常喜欢它。而我之所以选择 Rspack 并用于这些特定的项目,理由如下:
-
对于 webpack 用户来说,升级到 Rspack 是一个更加简单且低风险的路径。
-
据我所知,Vite 的 on-demand 文件服务在 SSR(服务器端渲染)上下文中(如 Next.js 应用)表现出色,但我正在使用 webpack 构建一个 SPA 和一个 Web extension,这种情况下并不是很重要。
-
我对 Vite 的体验并不理想。
-
我喜欢保持开发和生产构建的结果尽可能接近,这样我可以自信地测试即将发布的内容。Vite 在这方面较弱,它在开发模式使用 esbuild 和在生产构建基于 Rollup。实际上,我发现两者之间的差距太大了,以至于我放弃了在 Vite 的开发模式中实现一些功能,因为需要两次实现这些功能实在是太费劲了。
-
据说 Rspack 比 Vite 更快,至少在我关心的领域是这样的。
Rspack 潜在的最大问题,是像 webpack 一样具有上手成本,这应该是 Rspack 团队在 Rspack 之上还推出了一个更易用的工具 —— Rsbuild 的原因。
一路上的经验教训
在 Rspack 的 迁移指南 中,已经涵盖了从迁移 webpack 的步骤。
因此以下是我所学到的,未在指南中提及、或是可能不明显的事项。
TypeScript
Rspack 使用 SWC 来转换 TypeScript,这意味着你不再需要 ts-loader。然而,在使用 Rspack 构建后,我注意到生成的 JS 文件比使用 webpack 加 ts-loader 时大得多。
经过排查后发现,SWC 为 async iterators 等功能生成了很多样板代码。在 webpack 构建时,ts-loader 读取了我的 tsconfig.json,其中指定了 “target”: “es2020″,因此这些代码没有被降级到更低的版本。
然而,SWC 默认将它们降低到 ES5。解决方法是在 Rspack 配置中指定 SWC 的 target 即可。
例如:
const config = {
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: 'builtin:swc-loader',
options: {
sourceMap: true,
jsc: {
parser: {
syntax: 'typescript',
},
// 在这里添加 target
target: 'es2022',
},
},
},
type: 'javascript/auto',
},
],
},
};
我相信通过使用 env.targets 设置指定浏览器版本也可以实现这一点。
然而,即便这样做了,Rspack 生成的 JS 资源仍然较大,但当 Rspack 1.0 alpha 默认启用 optimization.concatenateModules 后,产物的体积缩小到与 webpack 相差不超过 3%,有时甚至略小。
类型检查
使用 SWC 转译 TypeScript 的结果之一,是默认不再执行类型检查。
你需要在 tsconfig.json 中启用 isolatedModules,并确定何时、以及如何执行类型检查。
我选择使用官方文档推荐的 fork-ts-checker-webpack-plugin,但仔细想想,我不确定这是否真的必要。
假设你已经设置好编辑器来执行类型检查,并在 CI 中运行 tsc,也可能将其作为 pre-commit 钩子(例如,使用 tsc-files),那么你可能不需要在每次构建时都执行类型检查。
使用 webpack 时,我在开发过程中忽略了一些 TypeScript 错误,以免它们在重构时打断我,方法如下:
use: {
loader: 'ts-loader',
options: env.ignoreUnused
? {
ignoreDiagnostics: [
6133 /* <variable> is declared but its value is never read */,
6192 /* All imports in import declaration are unused */,
],
}
: undefined,
}
在使用 Rspack 时,如果你使用 fork-ts-checker-webpack-plugin 进行类型检查,你可以在那里传入类似的选项:
new ForkTsCheckerWebpackPlugin({
issue: {
exclude: env.ignoreUnused
? [
// <variable> is declared but its value is never read
{ code: 'TS6133' },
// All imports in import declaration are unused
{ code: 'TS6192' },
]
: [],
},
}),
CSS
尽管 Rspack 的迁移指南提到了将 mini-css-extract-plugin 替换为 rspack.CssExtractRspackPlugin,但 CssExtractRspackPlugin 的文档中指出:
如果你的项目不依赖 css-loader,建议使用 Rspack 内置的 CSS 解决方案 experiments.css 以获得更好的性能。
因此,对于我的 Web 项目,我能够将我的 CSS 配置从以下内容:
// rspack.config.js
const config = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
url: false,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
},
],
},
// ...
],
},
// ...
};
更新为:
const config = {
experiments: {
css: true,
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'postcss-loader' }],
type: 'css/auto',
},
// ...
],
},
// ...
};
然而,我注意到我的 CSS 资源比 webpack 生成的资源大约大了 11%。通过比较输出的代码,我发现 Rspack 的输出没有执行某些优化,比如将 rgba () 颜色转换为十六进制值。
默认情况下,Rspack 使用 Lightning CSS 来压缩 CSS 资源,事实证明,Lightning CSS 使用的默认设置非常保守。在将一些更新的值添加到minimizerOptions.targets
属性后,Rspack 的输出比 webpack 的稍微小了一些:
optimization: {
// ...
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
// ...
}),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: {
+ targets: [
+ 'last 2 Chrome versions',
+ 'Firefox ESR',
+ 'last 2 Safari versions',
+ ],
},
}),
],
},
Service Workers
我的应用程序包含一个 Service Worker,并依赖谷歌的 Workbox 的 InjectManifest 插件来触发 Service Worker 资源的生成,并填充其 precache manifest 。不幸的是,当我迁移时,这个插件尚未被 Rspack 支持。
不过,Rspack 1.0.0-alpha.0 添加了对 Service Worker(以及各种类型的 worklets)的原生支持。
有一点小的古怪之处在于,你需要传递一个 URL 对象给navigator.serviceWorker.register()
(而不是字符串),并且你也不能传递一个引用 URL 对象的变量。
例如,以下代码会为sw.ts
生成一个 Service Worker 的片段:
const registrationPromise = navigator.serviceWorker.register(
new URL(
/* webpackChunkName: "serviceworker" */
'./sw.ts',
import.meta.url,
),
);
你还需要注意确保 Service Worker 文件最终拥有一个固定的文件名,而不是带有 hash 的防缓存文件名:
// rspack.config.js
const config = {
// ...
output: {
chunkFilename: (assetInfo) => {
if (assetInfo.chunk?.name === 'serviceworker') {
return '[name].js';
}
return '[name].[contenthash].js';
},
},
};
然而,所有这些仍然无法像 InjectManifest 那样,嵌入一个 precache asset manifest。我尝试了各种替代方法,但都无法与 Rspack 原生的 Service Worker 支持一起工作,所以最终我通过提取 Workbox 的 InjectManifest 插件中所需的部分,并将其适应 Rspack 来编写自己的解决方案。
一段时间后,Rspack 宣布 Workbox 已完全支持。但是我写的包更小,并且在进行更改时似乎比 Workbox 更好,所以我暂时会坚持使用它,但我相信大多数人会乐意继续使用 Workbox。
然而,原生 Service Worker 支持有一个特点,那就是它似乎在消除无用代码之前运行。因此,如果你在构建时使用常量来禁用 Service Worker 注册,就像在下面的代码中一样,你可能会发现即使它没有被引用,Service Worker 资源仍会生成。
function registerServiceWorker() {
if (!('serviceWorker' in navigator) || !__ENABLE_SW__) {
return Promise.resolve(null);
}
// The following code will be eliminated when __ENABLE_SW__
// is falsy, but the sw.js asset will still be generated.
const registrationPromise = navigator.serviceWorker.register(
new URL(
/* webpackChunkName: "sw" */
'./sw.ts',
import.meta.url,
),
);
// ...
return registrationPromise;
}
React Cosmos
在迁移到 Rspack 时,最大的障碍可能是让 React Cosmos 与之协作。我是 React Cosmos 的忠实粉丝,因为我发现在开发和测试组件时,它不像 Storybook 那样需要很多额外的开发依赖,而是与现有的打包工具一起工作。
不幸的是,React Cosmos 不支持 Rspack,只支持 webpack、Vite 和其他一些工具。它提供了关于如何配置自定义打包工具的说明,我心想:”将 webpack 插件移植到 Rspack 有多难呢?”
事实证明,答案是 “相当难”,这主要是因为我坚持用 tsup 构建插件,并且对每个 endpoint 应该如何打包感到困惑。
不过就在几周之后,react-cosmos-plugin-rspack 诞生了,为 webpack 插件提供了一种即插即用的替代品。
Knip
最后一个需要注意的依赖是 Knip。Knip 是一个用于检测项目中未使用冗余项(如依赖、导出、文件等)的工具。如果它检测到你在使用 webpack,它会查找你的 webpack 配置,检测项目的 entrypoints、webpack 插件等,并相应地分析你的项目。不幸的是,它并不支持 Rspack。
我的公司 是 Lars 对 Knip 工作的赞助商之一,所以我联系他看看是否可以委托开发一个用于 Rspack 的 Knip 插件。Lars 很快接受了,并邀请其他人参与众筹,几天后 插件就完成了。
作为结果,Knip 检查出我不再需要以下任何依赖:
-
clean-webpack-plugin(已被 output.clean 替代)
-
copy-webpack-plugin(已被 rspack.CopyRspackPlugin 替代)
-
css-loader(已被 experiments.css 替代)
-
mini-css-extract-plugin(已被 experiments.css 替代)
-
react-cosmos-plugin-webpack(已被 react-cosmos-plugin-rspack 替代)
-
ts-loader(已被 builtin:swc-loader 和 fork-ts-checker-webpack-plugin 替代)
-
webpack, webpack-cli(已被 @rspack/cli 替代)
-
webpack-dev-server(内置于 @rspack/cli)
-
workbox-webpack-plugin(已被 @birchill/inject-manifest-plugin 替代)
命令行选项
Rspack CLI 包含一个内置的开发服务器,但不幸的是,命令行选项在某些地方与 webpack-dev-server 不同,尤其是缺少像--port
和--hot/--no-hot
这样的选项。
为了解决这些问题,我最终使用了各种--env
值,并在rspack.config.js
的配置函数中进行检查。这似乎是一个不必要的兼容,希望在 Rspack 未来的版本中得到解决。
Rsdoctor
Rspack 团队很贴心地创建了一个插件 Rsdoctor,用于分析你的包和构建时间。以下是我最终使用的配置:
new RsdoctorRspackPlugin({
disableTOSUpload: true,
linter: {
rules: {
// Don't warn about using non ES5 features
'ecma-version-check': 'off',
},
},
supports: { generateTileGraph: true },
});
没有改变的部分
除了上述变化之外,其他一切似乎都能如常工作,包括以下插件:
我还使用了 Bugsnag webpack 插件,但它们只在新版本发布时运行,所以我还没机会进行测试。
结果
那么,Rspack 速度究竟有多快呢?
对于我的 SPA 项目,我得到以下结果:
环境 | webpack | Rspack | Rspack(不进行类型检查) |
---|---|---|---|
笔记本 | 19.1s | 8.1s (-57.7%) | 3.04s (-84.16%) |
台式机 | 13.24s | 5.57s (-57.94%) | 2.44s (-81.61%) |
最右边一列的结果是最重要的,因为类型检查是在一个单独的进程中进行的,大多数时候你只需等待构建完成,
此外,如果我正确理解 Rsdoctor 的分析结果,大部分编译时间似乎都来自 postcss-loader,因此我希望当 Tailwind 4 发布时,我可以切换到使用 Rspack 内置的 lightningcss-loader,从而看到编译时间的进一步下降。
对于我的 Web extension 项目,提升更为显著,在启用类型检查的情况下,我的台式机上编译时间从 11.65 秒减少到 3.6 秒(快 69%)。
展望未来
随着 Rspack 临近 1.0 版本,我们又见证了两个基于 Rust 的构建工具 —— Farm 和 Mako 的发布(它们似乎也起源于中国)。显然,还有未来的 webpack 替代品 Turbopack,以及 Rollup 的 Rust 版本 Rolldown。
这些工具中哪一个会获得更多用户的青睐,这值得期待。就我个人而言,我认为 Rspack 有很大的成功机会,因为:
-
webpack 大概是当今 Web 应用中最广泛使用的构建工具,而 Rspack 提供了最简单和最低风险的从 webpack 迁移的路径。这一点很容易被忽视,但希望这篇文章能展示出,即使对于像我这样复杂度适中的应用,迁移到一个不同的构建工具,即使是高度兼容的工具,也可能是相当繁琐的过程。
-
webpack 拥有丰富的插件生态系统,大多数插件都可以在 Rspack 中使用。我使用的许多服务,如 Relative CI 和 Bugsnag,都提供 webpack 插件,但不支持其他任何构建工具。因此,除非其他构建工具能提供与 webpack 插件 API 的兼容性,否则它们将处于劣势。
-
Rspack 已经足够快了。其他工具可能最终会稍快一些(有趣的是,Mako 和 Farm 都声称比 Rsbuild 快一点,但并没有展示它们与 Rspack 的对比),但如果你的构建时间已经只有 2~3 秒,即使快一个数量级,也不足以成为一个令人信服的切换理由。
随着 Rspack 临近 1.0 版本,我很期待看到它将如何被市场接受,以及团队会把它带向何方。
译者注:Rspack 1.0 将于 2024 年 8 月发布,尽请期待~
关于本文
作者:@Brian Birtles
译文:https://mp.weixin.qq.com/s/MXpE5ULV3jXVNO3lXfFM6Q
原文:https://birtles.blog/2024/08/14/lessons-learned-switching-to-rspack/
这期前端早读课
对你有帮助,帮”赞“一下,
期待下一期,帮”在看” 一下 。