插件系统
Rsbuild 提供了一套轻量强大的插件系统,用以实现自身的大多数功能,并允许用户进行扩展。
开发者编写的插件能够修改 Rsbuild 的默认行为,并添加各类额外功能,包括但不限于:
- 获取上下文信息
- 注册生命周期钩子
- 转换模块的源代码
- 修改 Rspack 配置
- 修改 Rsbuild 配置
- ...
对比其他插件
在开发 Rsbuild 插件之前,你可能已经接触过 webpack、Vite、esbuild 等工具的插件系统。
总体而言,Rsbuild 的插件 API 和 esbuild 相似,与 webpack 或 Rspack 插件相比,Rsbuild 的插件 API 更加简洁和容易上手。
// esbuild plugin const esbuildPlugin = { name: 'example', setup(build) { build.onEnd(() => console.log('done')); }, }; // Rsbuild plugin const rsbuildPlugin = () => ({ name: 'example', setup(api) { api.onAfterBuild(() => console.log('done')); }, }); // Rspack plugin class RspackExamplePlugin { apply(compiler) { compiler.hooks.done.tap('RspackExamplePlugin', () => { console.log('done'); }); } }
从功能上看,Rsbuild 的插件 API 主要围绕 Rsbuild 的运行流程和构建配置,并提供一些 hooks 用于扩展。而 Rspack 的插件 API 则更加复杂和丰富,能够修改打包过程的每一个环节。
Rsbuild 插件中可以集成 Rspack 插件,如果 Rsbuild 提供的 hooks 无法满足你的需求,你也可以通过 Rspack 插件来实现功能,并在 Rsbuild 插件中注册 Rspack 插件:
const rsbuildPlugin = () => ({ name: 'example', setup(api) { api.modifyRspackConfig((config) => { config.plugins?.push(new RspackExamplePlugin()); }); }, });
开发插件
插件提供类似 (options?: PluginOptions) => RsbuildPlugin 的函数作为入口。
插件示例
pluginFoo.ts
import type { RsbuildPlugin } from '@rsbuild/core'; export type PluginFooOptions = { message?: string; }; export const pluginFoo = (options: PluginFooOptions = {}): RsbuildPlugin => ({ name: 'plugin-foo', setup(api) { api.onAfterStartDevServer(() => { const msg = options.message || 'hello!'; console.log(msg); }); }, });
注册插件:
rsbuild.config.ts
import { pluginFoo } from './pluginFoo'; export default { plugins: [pluginFoo({ message: 'world!' })], };
插件结构
函数形式的插件可以 接受选项对象 并 返回插件实例,并通过闭包机制管理内部状态。
其中各部分的作用分别为:
name 属性用于标注插件名称。 setup 作为插件逻辑的主入口。 api 对象包含了各类钩子和工具函数。
命名规范
插件的命名规范如下:
- 插件的函数命名为
pluginAbc,并通过具名导出。 - 插件的
name 采用 scope:foo-bar 或 plugin-foo-bar 格式,添加 scope: 可以避免和其他插件产生命名冲突。
下面是一个例子:
pluginFooBar.ts
import type { RsbuildPlugin } from '@rsbuild/core'; export const pluginFooBar = (): RsbuildPlugin => ({ name: 'scope:foo-bar', setup() {}, });
TIP
Rsbuild 官方插件的 name 统一使用 rsbuild: 作为前缀,比如 rsbuild:react 对应 @rsbuild/plugin-react。
模板仓库
rsbuild-plugin-template 是一个最小的 Rsbuild 插件模板仓库,你可以基于该仓库来开发你的 Rsbuild 插件。
Environment 插件
Rsbuild 支持同时为多个环境构建产物,并支持某个插件仅在指定环境下运行。
当你希望你开发的插件支持作为 Environment 插件使用时,需要注意以下几点:
- 每个 environment 有自身的 Rsbuild 配置:
- 避免副作用,你的插件代码可能执行多次:
- 当同一个插件在不同环境下注册多次时,会被视为多个 Rsbuild 插件(哪怕它们指向同一个插件实例),这是因为它们带有不同的 Rsbuild environment 上下文。
下面是一个 Environment 插件例子:
pluginFoo.ts
import type { RsbuildPlugin } from '@rsbuild/core'; export type PluginFooOptions = { title?: string; }; export const pluginFoo = (options: PluginFooOptions = {}): RsbuildPlugin => ({ name: 'plugin-foo', setup(api) { api.modifyEnvironmentConfig((config) => { config.html.title = options.title || 'My Default Title'; }); api.modifyBundlerChain((chain, { environment }) => { chain.name(environment.config.html.title); }); }, });
引用其他插件
Rsbuild 的 plugins 配置项支持传入一个嵌套的数组,这意味着你可以通过这种方式在插件内部引用其他 Rsbuild 插件。
例如,在 pluginFoo 内部引用并注册 pluginBar:
import { pluginBar } from 'rsbuild-plugin-bar'; export const pluginFoo = (): RsbuildPlugin => { const foo = { name: 'plugin-foo', setup(api) { // ... }, }; return [foo, pluginBar()]; };
生命周期钩子
Rsbuild 在内部按照约定的生命周期进行任务调度,插件可以通过注册钩子来介入工作流程的任意阶段,并实现自己的功能。
Rsbuild 生命周期钩子的完整列表参考 API 文档。
Rsbuild 不会接管底层 Rspack 的生命周期,相关生命周期钩子的使用方式见对应文档:Rspack Plugin API。
使用配置项
自行编写的插件通常使用初始化时传入函数的参数作为配置项即可,开发者可以随意定义和使用函数的入参。
但某些情况下插件可能需要读取 / 修改 Rsbuild 公用的配置项,这时就需要了解 Rsbuild 内部对配置项的生产和消费流程:
- 读取、解析配置并合并默认值
- 插件通过
api.modifyRsbuildConfig(...) 回调修改配置项 - 归一化配置项并提供给插件后续消费,此后无法再修改配置项
整套流程可以通过这个简单的插件体现:
export const pluginUploadDist = (): RsbuildPlugin => ({ name: 'plugin-upload-dist', setup(api) { api.modifyRsbuildConfig((config) => { // 关闭代码压缩 config.output ||= {}; config.output.minify = false; // 轮到其它插件修改配置... }); api.onBeforeBuild(() => { // 读取最终的配置 const config = api.getNormalizedConfig(); if (!config.output.minify !== false) { // 其它插件又启用了压缩则报错 throw new Error( 'You must disable minimize to upload readable dist files.', ); } }); api.onAfterBuild(() => { const config = api.getNormalizedConfig(); const distRoot = config.output.distPath.root; // 上传 `distRoot` 目录下所有产物文件... }); }, });
插件中有三种方式使用配置项对象:
api.modifyRsbuildConfig(config => {}) 在回调中修改配置 api.getRsbuildConfig() 获取配置项 api.getNormalizedConfig() 获取归一化后的配置项
归一化的配置项会再次合并默认值,并移除大部分可选类型,对于 PluginUploadDist 的例子,其部分类型定义为:
api.modifyRsbuildConfig((config: RsbuildConfig) => {}); api.getRsbuildConfig() as RsbuildConfig; type RsbuildConfig = { output?: { minify?: boolean; distPath?: { root?: string }; }; }; api.getNormalizedConfig() as NormalizedConfig; type NormalizedConfig = { output: { minify: boolean; distPath: { root: string }; }; };
getNormalizedConfig() 的返回值类型与 RsbuildConfig 的略有不同、相比文档其它地方描述的类型进行了收窄, 在使用时无需自行判空、填充默认值。
因此使用配置项的最佳方式应该是:
- 通过
api.modifyRsbuildConfig(config => {}) 来修改配置 - 在其后的生命周期中读取
api.getNormalizedConfig() 作为插件实际使用的配置
修改 Rspack 配置
Rsbuild 插件允许你修改内置的 Rspack 配置,包括:
示例
比如,通过 Rsbuild 插件来注册 eslint-rspack-plugin:
import type { RsbuildPlugin } from '@rsbuild/core'; import ESLintRspackPlugin from 'eslint-rspack-plugin'; export const pluginEslint = (options?: Options): RsbuildPlugin => ({ name: 'plugin-eslint', setup(api) { api.modifyRspackConfig((config) => { config.plugins?.push( new ESLintRspackPlugin({ // plugins options }), ); }); }, });