在上个月,字节跳动开源了基于 Rust 的新一代构建引擎 Rspack,对 webpack 的 API 保持良好的兼容性,同时带来 5~10 倍的性能提升:
官网文档: https://www.rspack.dev/
这篇文章我们不妨用一个开源项目来实测一下,这里选取一个非常流行的绘图软件 exclidraw,实际上它们的源码都是用 TS 写的,并且是开源的,整体通过 create-react-app 来进行搭建。
exclidraw:https://excalidraw.com/
exclidraw 开源:https://github.com/excalidraw/excalidraw
接下来,我们就来一步步将这个复杂的开源项目接入到 Rspack,看看最后效果如何。当然,你也可以跟着一步步进行操作,实际体验下迁移前后的差异。
拉取 exclidraw 项目仓库
首先你需要将代码拉取下来:
git clone git@github.com:excalidraw/excalidraw.git
复制代码
OK,我们首先安装依赖并且启动项目:
你可以发现如下的界面:
说明项目已经正常启动起来了。接下来我们来一步步接入 Rspack。
初始化 Rspack
我们先安装 @rspack/cli:
然后配置入口:
{
"scripts": {
"build:rspack": "rspack build",
"start:rspack": "rspack serve"
},
"dependencies": {
"@rspack/cli": "0.1.4"
}
}
复制代码
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
这样的环境变量文件,在构建流程中我们需要通过对应的工具来读取这些文件。
html-webpack-plugin:https://www.npmjs.com/package/html-webpack-plugin
好,接下来,我们来一步步进行操作。
完善 Rspack 工作流
Sass 编译配置
我们安装下 sass-loader 并加上 sass 相关的配置。
{
"dependencies": {
"sass-loader": "13.2.2"
}
}
复制代码
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/plugin-html 来处理下。
{
"dependencies": {
"@rspack/plugin-html": "0.1.4"
}
}
复制代码
{
plugins: [
new html({
template: "./public/index.html",
templateParameters: false,
}),
],
}
复制代码
再次访问,终端没有报错,但是产物运行的时候报错了。
点进去发现原来是 import.meta.env 没被替换,我们可以通过 define 配置解决这个问题。
在 Rspack 的 Github Issue 可以发现这个 issue: https://github.com/web-infra-dev/rspack/issues/2392,对于 import.meta 的转换已经在团队的规划当中了。
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 配置文件:
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 的能力,我们测试下试试。
copy-webpack-plugin:https://www.npmjs.com/package/copy-webpack-plugin
module.exports = {
builtins: {
copy: {
patterns: [
{
from: "public",
globOptions: {
ignore: ["**/index.html"]
},
},
],
},
}
}
复制代码
性能对比
我们再来对比下性能, 从迁移前的 51s
优化到了 3s
以内,构建性能提升了 10 倍以上。
最后我们回顾一下,完整的 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,
}),
],
};
复制代码
使用 react-scripts-rspack 一步到位
当然,Rspack 也提供从 CRA 自动迁移的方案——react-scripts-rspack
,使用方式也非常简单,安装 react-scripts-rspack
这个包,然后执行下面的命令来启动或者构建项目:
// 开发环境启动项目
react-scripts-rspack start
// 生产环境打包项目
react-scripts-rspack build
复制代码
实际使用案例: https://github.com/excalidraw/excalidraw/pull/6425
如果有兴趣,你也可以尝试一下这个自动迁移方案,迁移成本会更低。
小结
我们可以发现,对于 exclidraw 这个基于 webpack 的相对复杂的开源项目而言,我们把构建工具迁移到 Rspack 并没有想象中那么繁琐,迁移过程相对轻松,主要有两个原因:
同时迁移之后带来了 10 倍以上的构建性能提升,性能收益也很可观。让我们期待 Rspack 能在未来有更多的落地吧。
exclidraw :https://excalidraw.com/
Rspack 仓库:https://github.com/web-infra-dev/rspack
Rspack 团队核心工程师金睿将在今年 5 月 26-27 日的 QCon 全球软件开发大会(广州站)中深度解析 Rspack 的基础架构、性能优化思路、项目性能排查与优化思路,大会还有来自蔚来汽车的专家分享 Quickjs on webf,干货十足,点此了解详情。
评论