在上个月,字节跳动开源了基于 Rust 的新一代构建引擎 Rspack,号称能快速的从 Webpack 应用进行迁移并且性能提高 5-10 倍,从官方的 benchmark 数据可以看到明显的性能优势:
官网文档: www.rspack.dev/
事实上真是如此吗?这篇文章我们不妨用一个实际的开源项目来评测一下,我们这里选取一个非常流行的绘图软件 exclidraw,也是我本人经常使用的一个画图软件,实际上它们的源码都是用 TS 写的,并且是开源的,整体通过 create-react-app 来进行搭建。
接下来,我们就来一步步将这个复杂的开源项目接入到 Rspack,看看最后效果如何。当然,你也可以跟着一步步进行操作,实际体验下迁移前后的差异。
拉取 exclidraw 项目仓库
首先你需要将代码拉取下来:
git clone git@github.com:excalidraw/excalidraw.git
OK,我们首先安装依赖并且启动项目:
yarn
yarn start
你可以发现如下的界面:
说明项目已经正常启动起来了。接下来我们来一步步接入 Rspack。
初始化 Rspack
参考 Rspack 官网的做法,我们先安装 @rspack/cli:
yarn add @rspack/cli
然后配置入口:
- package.json
{
"scripts": {
"build:rspack": "rspack build",
"start:rspack": "rspack serve"
},
"dependencies": {
"@rspack/cli": "0.1.4"
}
}
- rspack.config.js
module.exports = {
context: __dirname,
entry: {
main: './src/index.tsx'
}
}
除此之外,我们还需要梳理一下要搭建这个项目的构建工作流需要考虑哪些因素:
-
Sass 配置。因为项目中使用了 Sass 语法,我们需要添加相应配置。
-
HTML 插件。在传统的 webpack 项目中,我们一般用 html-webpack-plugin 来处理 html,将 css、js 的内容插入到 html 中,并处理一些模板变量。那么在 Rspack 中也不例外,官方也提供了 @rspack/plugin-html 这一平替方案。
-
dotenv 的配置。你可以注意到项目根目录存在
.env.development
这样的环境变量文件,在构建流程中我们需要通过对应的工具来读取这些文件。
好,接下来,我们来一步步进行操作。
完善 Rspack 工作流
Sass 编译配置
我们安装下sass-loader 并加上 sass 相关的配置。
- package.json
{
"dependencies": {
"sass-loader": "13.2.2"
}
}
- rspack.config.js
module: {
rules: [
{
test: /.scss$/,
use: [
{
loader: "sass-loader",
},
],
type: "css",
},
],
},
根据文档,rspack 内置了对 css 的支持,因此我们这里只需要配置type: 'css'
即可,而不需要使用 css-loader。
HTML 插件配置
现在你可以尝试运行pnpm start:rspack
,结果正常编译了, 我们再检查下产物,访问 http://localhost:8080/
。
结果我们发现如下的报错:
URIError: Failed to decode param '/%REACT_APP_CDN_MATOMO_TRACKER_URL%'
at decodeURIComponent (<anonymous>)
at decode_param (/Users/xxx/excalidraw/node_modules/@rspack/dev-server/node_modules/webpack-dev-server/node_modules/express/lib/router/layer.js:172:12)
at Layer.match (/Users/xxx/project/excalidraw/node_modules/@rspack/dev-server/node_modules/webpack-dev-server/node_modules/express/lib/router/layer.js:123:27)
很显然,html 中的模板变量没有被处理,我们使用 Rspack 官方推荐的 @rspack/plugin-html 来处理下。
- package.json
{
"dependencies": {
"@rspack/plugin-html": "0.1.4"
}
}
- rspack.config.js
{
plugins: [
new html({
template: "./public/index.html",
templateParameters: false,
}),
],
}
再次访问,终端没有报错,但是产物运行的时候报错了。
点进去发现原来是 import.meta.env 没被替换,我们可以通过 define 配置解决这个问题。
在 Rspack 的 Github Issue 可以发现这个 issue: github.com/web-infra-d… import.meta 的转换已经在团队的规划当中了。
- rspack.config.js
module.exports = {
builtins: {
define: {
"import.meta.env && import.meta.env.MODE": JSON.stringify(process.env.NODE_ENV || 'production'),
},
}
}
环境变量文件读取
首先我们安装下dotenv
这个工具库:
{
"dependencies": {
"dotenv": "16.0.1"
}
}
然后完善一下 rspack 配置文件:
- rspack.config.js
const env = process.env.NODE_ENV || "development";
const dotEnvFiles =
env === "development" ? [".env.development"] : [".env.production"];
dotEnvFiles.forEach((doteEnvFile) => {
require("dotenv-expand")(require("dotenv").config({ path: doteEnvFile }));
});
const REACT_APP = /^REACT_APP_/i;
const filterEnv = {};
const define = Object.keys(process.env)
.filter((key) => REACT_APP.test(key))
.reduce((env, key) => {
filterEnv[key] = process.env[key];
env[`process.env.${key}`] = JSON.stringify(process.env[key]);
return env;
}, {});
module.exports = {
builtins: {
define: {
...define,
"import.meta.env && import.meta.env.MODE": JSON.stringify(env),
"process.env": JSON.stringify(filterEnv),
},
},
}
我们再次启动看看,正常跑起来了!!!
静态资源问题
如果你观察仔细的话,你可以发现页面的 favicon 还没有正常显示。我们知道 favicon 作为一个网站的图标,对于一个正规的网站来说还是比较重要的,接下来我们可以分析一下为什么 favicon 没有显示。
其实原因很简单,favicon 文件在根目录的 public 目录中,我们在构建完成之后并没有将其中的静态资源拷贝到产物目录中,导致最后访问不了了。
而好消息是,Rspack 官方好像内置了 copy 功能,对标 copy-webpack-plugin 的能力,我们测试下试试。
- rspack.config.js
module.exports = {
builtins: {
copy: {
patterns: [
{
from: "public",
globOptions: {
ignore: ["**/index.html"]
},
},
],
},
}
}
性能对比
我们再来对比下性能, 从迁移前的 51s
优化到了 3s
以内,性能的确提高了 10 倍以上,看来 Rspack 的性能宣传非虚。
最后我们回顾一下,完整的 rspack 配置:
const html = require("@rspack/plugin-html").default;
const env = process.env.NODE_ENV || "development";
const dotEnvFiles =
env === "development" ? [".env.development"] : [".env.production"];
dotEnvFiles.forEach((doteEnvFile) => {
require("dotenv-expand")(require("dotenv").config({ path: doteEnvFile }));
});
const REACT_APP = /^REACT_APP_/i;
const filterEnv = {};
const define = Object.keys(process.env)
.filter((key) => REACT_APP.test(key))
.reduce((env, key) => {
filterEnv[key] = process.env[key];
env[`process.env.${key}`] = JSON.stringify(process.env[key]);
return env;
}, {});
/**
* @type {import('@rspack/cli').Configuration}
*/
module.exports = {
entry: {
main: "./src/index.tsx",
},
module: {
rules: [
{
test: /.scss$/,
use: [
{
loader: "sass-loader",
},
],
type: "css",
},
],
},
builtins: {
define: {
...define,
"import.meta.env && import.meta.env.MODE": JSON.stringify(process.env.NODE_ENV || 'production'),
"process.env": JSON.stringify(filterEnv),
},
copy: {
patterns: [
{
from: "public",
globOptions: {
ignore: ["**/index.html"]
},
},
],
},
},
plugins: [
new html({
template: "./public/index.html",
templateParameters: false,
}),
],
};
小结
我们可以发现,对于 exclidraw 这个基于 webpack 的相对复杂的开源项目而言,我们把构建工具迁移到 Rspack 并没有想象中那么繁琐,迁移过程相对轻松,主要有两个原因:
- 一方面得益于 Rspack 官方对于 webpack 本身的兼容,如果你去看过 Rspack 仓库的代码会发现里面沿用了 webpack 绝大部分的测试用例,对于 webpack 的很多基础能力 Rspack 是能完全覆盖并且保证足够的稳定性。
- 另一方面, Rspack 也对 webpack 生态中常用的 loader 和插件做了兼容,比如 sass-loader、html 插件、copy 插件等等。
而且迁移完成之后收益也非常明显,构建速度确实有 10 倍以上的提升,由此可见 Rspack 在性能提升和迁移成本之间做了很好的权衡,我们期待 Rspack 能在未来有更多的落地吧。
原文链接:https://juejin.cn/post/7218220654594949179 作者:神三元