5月27日 16:48

Rspack 插件系统是怎么工作的?

Rspack 的插件系统基于 Rust 实现,同时提供与 Webpack 高度兼容的 JavaScript API,是 Rspack 扩展性和灵活性的核心机制。Rspack 的大部分原生功能(模块解析、代码分割、Tree Shaking 等)都通过 Rust 侧的内部插件完成,而用户侧的插件开发接口则对齐了 Webpack 的 Compiler/Compilation 钩子体系。理解这套插件系统的工作原理,是从 Webpack 迁移到 Rspack 以及开发自定义构建能力的关键。

插件系统架构

Rspack 的插件系统以钩子(Hook)为核心,开发者通过在构建流程的各个阶段注册回调来注入自定义逻辑。整体架构分为两层:

  • Rust 侧插件:Rspack 的核心构建逻辑全部通过 Rust 插件实现,包括模块解析、依赖分析、代码生成和产物优化。这些内部插件性能极高,但用户通常不需要直接操作它们。
  • JavaScript 侧插件:对齐 Webpack 的插件 API,用户通过 apply(compiler) 方法注册钩子回调。Rspack 已经兼容了大部分 Webpack 的 Compiler 和 Compilation 钩子。

插件的核心能力包括:

  • 修改构建配置(compiler.options
  • 拦截模块的构建和解析过程
  • 在产物输出前处理和优化资源
  • 生成额外的文件(HTML、CSS、Source Map 等)

插件分类

兼容 Webpack 的内置插件

为了与 Webpack 功能保持一致,Rspack 复制了大部分 Webpack 内置插件,保持相同的命名和配置参数。常用插件包括:

  • HtmlWebpackPlugin:基于模板生成 HTML 文件,自动注入打包后的 JS/CSS 引用
  • MiniCssExtractPlugin:将 CSS 从 JS bundle 中提取为独立文件,支持按需加载
  • DefinePlugin:在编译时替换代码中的全局变量,常用于注入环境变量
  • CopyWebpackPlugin:将静态资源复制到输出目录
  • CleanWebpackPlugin:每次构建前清理输出目录

Rspack 独有的内置插件

Rspack 提供了一些原生高性能插件作为 Webpack 对应插件的替代方案,它们在 Rust 层实现,性能显著优于 JavaScript 实现:

  • RspackHtmlPlugin:Rust 实现的 HTML 生成插件,构建速度远快于 HtmlWebpackPlugin
  • RspackCssExtractPlugin:Rust 实现的 CSS 提取插件,避免了 JavaScript 与 Rust 之间的通信开销

这类插件的使用方式和配置项与 Webpack 版本基本一致,迁移成本极低。

社区插件和 unplugin 支持

Rspack 支持使用社区提供的 Webpack 兼容插件。可以在 awesome-rspack 中查看已验证的社区插件列表。

此外,Rspack 支持基于 unplugin 实现的插件。unplugin 是一套跨构建工具的插件抽象层,使用时需要引入插件的 /rspack 子路径:

javascript
import Icons from 'unplugin-icons/rspack'; module.exports = { plugins: [Icons({ compiler: 'vue3' })], };

使用插件

基本配置

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin'); const { DefinePlugin } = require('@rspack/core'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', }), new DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production'), }), ], };

CSS 提取配置

javascript
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, ], }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css', }), ], };

环境变量注入

javascript
const { DefinePlugin } = require('@rspack/core'); module.exports = { plugins: [ new DefinePlugin({ 'process.env.API_URL': JSON.stringify('https://api.example.com'), 'process.env.VERSION': JSON.stringify('1.0.0'), }), ], };

开发自定义插件

插件基本结构

Rspack 的自定义插件结构与 Webpack 完全一致:一个带有 apply 方法的类,apply 接收 compiler 实例作为参数。以下是一个注册多个阶段钩子的完整示例:

javascript
class MyCustomPlugin { constructor(options) { this.options = options; } apply(compiler) { // 编译开始时执行 compiler.hooks.run.tapAsync('MyCustomPlugin', (compiler, callback) => { console.log('Starting compilation...'); callback(); }); // compilation 创建后执行 compiler.hooks.compilation.tap('MyCustomPlugin', (compilation) => { compilation.hooks.processAssets.tapAsync( { name: 'MyCustomPlugin', stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, }, (assets, callback) => { // 在此处理或修改产物资源 callback(); }, ); }); // 编译完成时执行 compiler.hooks.done.tap('MyCustomPlugin', (stats) => { console.log('Compilation completed!'); }); } } module.exports = MyCustomPlugin;

Compiler 钩子

Compiler 代表整个构建流程的实例,以下是最常用的钩子:

钩子类型触发时机
runAsyncSeriesHook编译开始(单次构建)
watchRunAsyncSeriesHook监听模式下编译开始
compileSyncHook编译参数创建后、创建 compilation 前
compilationSyncHookcompilation 创建后,可访问模块工厂
emitAsyncSeriesHook输出资源到目录前,可修改最终产物
doneSyncHook编译完成,可获取 stats
failedSyncHook编译失败

Compilation 钩子

Compilation 代表单次编译过程,核心钩子包括:

钩子类型触发时机
buildModuleSyncHook单个模块开始构建前
succeedModuleSyncHook单个模块构建成功后
processAssetsAsyncSeriesHook处理产物资源(优化、替换、新增)
chunkAssetSyncHookchunk 产物生成后

钩子类型(SyncHook、AsyncSeriesHook、AsyncParallelHook 等)决定了回调的执行方式:同步钩子按注册顺序依次执行,异步钩子支持 Promise 或 callback 形式。理解钩子类型对开发正确的插件至关重要——在 SyncHook 中使用异步操作会导致构建失败。

插件兼容性

Rspack 致力于兼容 Webpack 插件生态,但兼容程度因插件而异:

完全兼容

基于 Webpack 公开钩子 API 的插件基本都可以直接使用,包括文件生成、资源处理和大部分优化插件。Rspack 官方维护了插件兼容性列表,已通过测试的 Webpack 插件可以直接迁移。

部分兼容

以下情况可能需要调整:

  • 依赖 Webpack 内部 API(非公开接口)的插件,如直接访问 compiler._modules 等私有属性
  • 使用了特定 Webpack 版本才有的特性的插件
  • 对 Compilation 的数据结构做了假设的插件(Rspack 的内部数据结构与 Webpack 有差异)

不兼容

这几类插件目前无法在 Rspack 中使用:

  • 依赖 Webpack 的 JavaScript 运行时特性的插件(Rspack 的核心运行在 Rust 中)
  • 深度依赖 Webpack 内部数据结构的插件
  • 使用了实验性 API 的插件

遇到不兼容的插件时,可以优先查找是否有 Rspack 原生替代方案,或者使用 unplugin 重新实现插件的跨构建工具版本。

性能考量

Rspack 的插件系统在性能方面有几个关键点需要注意:

优先使用 Rust 原生插件:Rspack 提供的 RspackHtmlPlugin、RspackCssExtractPlugin 等原生插件在 Rust 侧执行,避免了 JavaScript 和 Rust 之间的跨语言通信开销。Rspack 团队已将多个 JavaScript 插件移植到 Rust,构建性能提升显著。

减少 Rust-JS 通信:每个 JavaScript 插件的钩子回调都会触发一次 Rust 到 JavaScript 的跨语言调用。如果自定义插件注册了大量高频钩子(如 buildModule),通信开销会累积。可以将逻辑合并到较少的钩子回调中,或使用批量处理来降低调用频率。

异步钩子的合理使用:在不需要异步操作的场景中使用同步钩子(tap 而非 tapAsync),可以减少不必要的异步调度开销。

插件顺序:某些插件的执行顺序会影响构建结果。processAssets 钩子通过 stage 参数控制阶段,使用 PROCESS_ASSETS_STAGE_ADDITIONSPROCESS_ASSETS_STAGE_OPTIMIZATIONS 等常量确保插件在正确的阶段执行。

从 Webpack 迁移插件到 Rspack

将现有 Webpack 插件迁移到 Rspack 时,按以下步骤排查:

  1. 检查兼容性列表:先确认该插件是否已通过 Rspack 兼容性测试
  2. 替换为原生替代:如果有 Rspack 原生替代方案(如 RspackHtmlPlugin 替代 HtmlWebpackPlugin),优先使用原生版本
  3. 避免内部 API:确保插件只使用 Webpack 公开的钩子 API,不依赖私有属性或方法
  4. 测试验证:迁移后在项目中运行完整构建,对比输出产物是否一致

Rspack 在 compiler 实例上暴露了 compiler.webpack 命名空间,使依赖 webpack 模块的插件可以正常运行。例如,compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS 可以直接访问到 Compilation 的常量。

对于暂时无法迁移的 Webpack 插件,也可以考虑通过 unplugin 重新实现其核心逻辑,从而同时支持 Webpack 和 Rspack 两个构建工具。

标签:Rspack