使用 Rspack 构建真实开源项目,看看迁移成本和性能收益如何?

9,626 阅读3分钟

在上个月,字节跳动开源了基于 Rust 的新一代构建引擎 Rspack,对 webpack 的 API 保持良好的兼容性,同时带来 5~10 倍的性能提升:

官网文档: 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/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/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 配置:

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 本身 API 的兼容。在 Rspack 仓库的代码中沿用了 webpack 绝大部分的测试用例,对于 webpack 的很多基础能力 Rspack 是能完全覆盖的。
  • 另一方面, Rspack 也对 webpack 生态中常用的 loader 和插件做了兼容,比如 sass-loader、html 插件、copy 插件等等。

同时迁移之后带来了 10 倍以上的构建性能提升,性能收益也很可观。让我们期待 Rspack 能在未来有更多的落地吧。