Rspack 2.0 正式发布!

Rspack 2.0 已正式发布,这是一个面向未来的打包工具,旨在提供卓越的构建性能和现代化的开发体验。本文将详细介绍 Rspack 2.0 的主要更新,包括性能提升、产物优化、ESM 支持和新特性。

回顾 1.x 版本

在 2024 年 8 月发布的 Rspack 1.0 中,我们设定了一个明确的目标:在保持与 webpack API 和生态兼容的前提下,实现 10 倍的构建性能提升。经过一段时间的努力,这一目标已经基本达成。Rspack 不仅实现了 webpack 的核心能力和插件 API,还在开发体验、产物优化以及现代语言特性支持等方面不断演进,陆续引入并完善了增量构建、按需编译、持久化缓存、常量内联、虚拟模块、Barrel 文件优化等新特性。

与此同时,Rspack 也逐渐被越来越多的用户采用。与 1.0 发布时相比,Rspack 的周下载量已由 10 万增长至 500 万。这表明 Rspack 已经成为前端开发领域的一个重要工具。

迈向 2.0 版本

JavaScript 生态在不断发展,新的开发范式也在不断涌现。这促使我们重新思考:一个面向未来的打包工具应当如何演进。Rspack 的目标不仅仅是成为一个“更快的 webpack”。在 1.x 阶段,我们有意让 Rspack 的 API 和默认值与 webpack 5 保持一致,这一策略是为了帮助现有项目低成本迁移到 Rspack。但随着 JavaScript 模块规范和生态的发展,一些历史设计已不再适合作为当下的默认选择。

从 2.0 开始,在保持对 webpack 生态兼容的前提下,Rspack 将逐步引入更符合现代 JavaScript 开发的默认行为、API 设计和构建产物。这将是一个渐进的过程,我们会尽量避免在单个主版本中引入过多不兼容变更,同时也会提供迁移指南,将迁移成本控制在可接受范围内。

2.0 版本亮点

Rspack 2.0 主要带来了以下更新:

性能提升

构建提速

构建性能始终是 Rspack 的核心关注点之一。与 Rspack 1.7 相比,Rspack 2.0 的整体性能提升了约 10%,相较 1.0 最多可提升 100%。

版本生产构建(无缓存)生产构建(有缓存)热更新
Rspack 1.05.6 s5.6 s128 ms
Rspack 1.73.6 s2.2 s134 ms
Rspack 2.03.1 s1.4 s118 ms

这些改进主要来自对核心架构的持续优化。我们针对关键性能路径重构了部分算法与数据结构,升级了部分过时依赖,并清理了不再使用的代码路径。

在开启 持久化缓存 的场景下,构建性能和内存占用得到了进一步改善:

  • 现在 Rspack 支持对 SWC 压缩插件 的结果进行缓存复用,在命中缓存时,构建性能提升约 50%。
  • 通过优化底层存储实现,在启用缓存时,内存占用下降了 20%+。

精简默认依赖

Rspack 2.0 减少了默认安装的 npm 依赖数量:

  • @rspack/dev-server:依赖数量从 192 降至 1。
  • @rspack/core:依赖数量从 8 降至 1。
  • @rspack/cli:现在为零依赖。

在 Rspack 1.x 中,@rspack/dev-server 通过 webpack-dev-server 间接引入了较多依赖,这增加了安装体积,也加大了依赖管理的复杂度。为此,我们对它进行了重构,梳理并精简了功能和依赖,使安装体积减少超过 90%(从 15 MB 降至 1.4 MB)。

同时,@rspack/cli 也不再默认依赖 @rspack/dev-server,这意味着如果只使用 rspack build,将不再需要引入开发服务器相关依赖。

此外,我们还采用了以下方式来减少依赖:

  • 将非核心依赖调整为可选依赖。例如 [@module-federation/runtime-tools] 现在仅在使用 [模块联邦插件] 时才需要手动安装。
  • 使用更轻量的替代实现,例如以 [connect-next] 替代 Express。对于开发服务器而言,Connect 的中间件模型已经能够覆盖大多数场景,这也与 Rsbuild 的开发服务器实现保持一致。
  • 将部分依赖 [打包进 npm 发布产物],由 Rspack 统一控制这些依赖及其子依赖的版本,避免依赖自动升级带来的潜在供应链风险。
  • 优先使用 Node.js 20+ 内置的原生 API,例如用 [styleText] 替代 picocolors。

产物优化

静态分析增强

Rspack 2.0 在静态分析能力上进行了增强,使更多复杂代码模式纳入 [tree shaking] 的优化范围。一些过去难以分析的场景,现在也能参与导出级别的裁剪。

  • CommonJS require 解构赋值:Rspack 现在可以识别解构中实际使用到的导出成员,仅保留必要代码

    const { bar } = require('./foo');
  • CommonJS require 成员访问与调用:Rspack 现在能继续向下分析对象的成员访问和调用,判断哪些导出被使用

    const foo = require('./foo');
    
    foo.bar;
    foo.baz();
  • 动态 import() 结果的内联成员访问与调用:对于这种直接在表达式中访问模块成员的写法,Rspack 同样可以识别使用到的导出,并进行裁剪

    (await import('./foo')).bar;

编译器注解支持

Rspack 2.0 现在支持 [编译器注解],允许你使用 #NO_SIDE_EFFECTS 将函数标记为无副作用。当该函数调用的返回值未被使用时,tree shaking 会自动移除未使用的代码。

例如,在下面的代码中,join 被标记为无副作用函数。当它的返回值没有被使用时,该调用会被安全移除。

// utils.js
/*#__NO_SIDE_EFFECTS__*/
export function join(a, b) {
  return `${a}-${b}`;
}
// index.js
import { join } from './utils';

join('btn', 'primary');

该功能仍处于实验阶段,目前需要通过 [experiments.pureFunctions] 启用,并将在后续版本中默认开启,查看 [指南] 了解更多。

对于无法直接修改源码的第三方模块,Rspack 还允许你通过 [module.parser.javascript.pureFunctions] 选项手动声明纯函数列表,以达到同样的效果。

// rspack.config.mjs
export default {
  module: {
    parser: {
      javascript: {
        pureFunctions: ['myFunctionName'],
      },
    },
  },
};

模块联邦 tree shaking

Rspack 现在支持对 [模块联邦] 的共享依赖进行 tree shaking。它可以在导出级别裁剪共享依赖,移除未使用的部分,从而减小共享包的体积。

在 Rspack 1.x 中,只要依赖被声明为 shared,运行时通常需要加载完整包。即使只用到少量导出,也会引入全部内容。对于体积较大的共享库,这会带来显著的开销。

而 Rspack 2.0 支持在 shared 选项中开启 treeShaking。此时,ModuleFederationPlugin 会为共享依赖生成裁剪后的产物,运行时优先加载该结果;若无法命中,再回退到完整依赖,以保证行为一致。

例如:

// rspack.config.mjs
import { rspack } from '@rspack/core';

export default {
  plugins: [
    new rspack.container.ModuleFederationPlugin({
      shared: {
        'lodash-es': {
          singleton: true,
          treeShaking: {
            mode: 'runtime-infer',
            usedExports: ['debounce'],
          },
        },
      },
    }),
  ],
};

查看 [共享依赖 tree shaking 指南] 了解更多。

改进 ESM 支持

纯 ESM 包

Rspack 的核心包现在以 pure ESM 包的形式发布,并移除了自身的 CommonJS 构建产物,这使模块加载方式更加统一,也更符合当前 Node.js 的主流实践。

本次变更涉及以下 npm 包:

  • [@rspack/core]
  • [@rspack/cli]
  • [@rspack/dev-server]
  • [@rspack/plugin-react-refresh]

在 Node.js 20 及以上版本中,运行时已原生支持通过 [require(esm)] 加载 ESM 模块。因此,对于大多数仍通过 JavaScript API 使用 Rspack 的项目而言,这一变更通常不会带来实际影响,也无需额外修改现有代码。

import.meta 支持

Rspack 2.0 改进了对 [import.meta] 的支持。

在 Rspack 1.x 中,为了兼容非 ESM 产物,Rspack 会在编译阶段解析 import.meta 并替换为对应的值。对于无法识别的 import.meta 属性,通常会直接替换为 undefined。

从 Rspack 2.0 开始,在生成 ESM 产物时,Rspack 默认会保留无法识别的 import.meta 属性,而不是在编译阶段将其替换。这使你可以使用更多的 import.meta 属性,而不必担心它们被错误地处理。

新特性

React Server Components 支持

Rspack 2.0 现在支持 [React Server Components],这使得开发者可以更方便地在服务器端渲染 React 组件,提高应用的初始加载速度和性能。

支持 #/ 子路径别名导入

Rspack 2.0 引入了对 #/ 子路径别名导入的支持,这使得开发者可以更灵活地组织模块路径,提高代码的可读性和维护性。

import Button from '#/components/Button';

简化目标环境配置

Rspack 2.0 简化了目标环境的配置,使开发者可以更轻松地指定目标浏览器和 Node.js 版本,确保生成的代码在目标环境中正常运行。

简化 swc-loader 配置

Rspack 2.0 简化了 swc-loader 的配置,减少了配置文件的复杂度,提高了开发效率。

支持控制 CSS 导入

Rspack 2.0 允许开发者更精细地控制 CSS 的导入方式,例如按需导入或异步导入,以优化应用的加载性能。

使用哈希作为模块 ID

Rspack 2.0 支持使用哈希作为模块 ID,这有助于提高模块的唯一性和缓存效率。

代码分割改进

Rspack 2.0 在代码分割方面进行了改进,支持更细粒度的代码分割策略,进一步优化应用的加载性能。

总结

Rspack 2.0 的发布标志着我们在构建性能和现代化开发体验方面迈出了重要的一步。通过性能提升、产物优化、ESM 支持和新特性引入,Rspack 2.0 为开发者提供了更强大的工具和支持。建议现有用户逐步迁移到 Rspack 2.0,享受更高效的开发体验。未来,我们将继续努力,不断完善 Rspack,为前端开发带来更多创新和便利。