Posted in

【第 3355 期】前 Firefox 工程师迁移到 Rspack 的经验教训_AI阅读总结 — 包阅AI

包阅导读总结

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/

这期前端早读课
对你有帮助,帮”
“一下,
期待下一期,帮”
在看” 一下 。