Plugin core

本章节描述了 Rsbuild 插件核心的类型定义和 API。

RsbuildPlugin

插件对象的类型,插件对象包含以下属性:

  • name:插件的名称,唯一标识符。
  • setup:插件逻辑的主入口函数,可以是一个异步函数。该函数仅会在初始化插件时执行一次。插件 API 对象上挂载了提供给插件使用的上下文数据、工具函数和注册生命周期钩子的函数,关于生命周期钩子的完整介绍,请阅读 Plugin Hooks 章节。
  • pre:声明前置插件的名称,这些插件会在当前插件之前执行。
  • post:声明后置插件的名称,这些插件会在当前插件之后执行。
  • remove:声明需要移除的插件,可以传入插件 name 的数组。
type RsbuildPlugin = {  name: string;  pre?: string[];  post?: string[];  remove?: string[];  setup: (api: RsbuildPluginAPI) => Promise<void> | void; };

你可以从 @rsbuild/core 中导入该类型:

import type { RsbuildPlugin } from '@rsbuild/core';  export default (): RsbuildPlugin => ({  name: 'plugin-foo',   pre: ['plugin-bar'],   setup(api) {  api.onAfterBuild(() => {  console.log('after build!');  });  }, });

前置插件

默认情况下,插件会按照添加顺序依次执行,通过 pre 字段可以声明前置执行的插件。

比如有下面两个插件:

const pluginFoo = {  name: 'plugin-foo', };  const pluginBar = {  name: 'plugin-bar',  pre: ['plugin-foo'], };

Bar 插件在 pre 字段中配置了 Foo 插件,因此 Foo 插件一定会在 Bar 插件之前执行。

后置插件

同样的,通过 post 字段可以声明后置执行的插件。

const pluginFoo = {  name: 'plugin-foo', };  const pluginBar = {  name: 'plugin-bar',  post: ['plugin-foo'], };

Bar 插件在 post 字段中配置了 Foo 插件,因此 Foo 插件一定会在 Bar 插件之后执行。

移除插件

通过 remove 字段可以在一个插件中移除其他插件。

const pluginFoo = {  name: 'plugin-foo', };  const pluginBar = {  name: 'plugin-bar',  remove: ['plugin-foo'], };

比如同时注册上述的 Foo 和 Bar 插件,由于 Bar 插件声明 remove 了 Foo 插件,因此 Foo 插件不会生效。

需要注意的是:如果当前插件注册为特定环境插件,则仅支持移除同环境插件,不能移除全局插件。

api.context

api.context 是一个只读对象,提供一些上下文信息。

api.context 的内容与 rsbuild.context 完全一致,请参考 rsbuild.context

  • 示例:
const pluginFoo = () => ({  setup(api) {  console.log(api.context.distPath);  }, });

api.getRsbuildConfig

获取 Rsbuild 配置。

  • 类型:
type GetRsbuildConfig = {  (): Readonly<RsbuildConfig>;  (type: 'original' | 'current'): Readonly<RsbuildConfig>;  (type: 'normalized'): NormalizedConfig; };
  • 参数:

你可以通过 type 参数来指定读取的 Rsbuild 配置类型:

// 获取用户定义的原始 Rsbuild 配置。 getRsbuildConfig('original');  // 获取当前的 Rsbuild 配置。 // 在 Rsbuild 的不同执行阶段,该配置的内容会发生变化。 // 比如 `modifyRsbuildConfig` 钩子执行后会修改当前 Rsbuild 配置的内容。 getRsbuildConfig('current');  // 获取归一化后的 Rsbuild 配置。 // 该方法必须在 `modifyRsbuildConfig` 钩子执行完成后才能被调用。 // 等价于 `getNormalizedConfig` 方法。 getRsbuildConfig('normalized');
  • 示例:
const pluginFoo = () => ({  setup(api) {  const config = api.getRsbuildConfig();  console.log(config.html?.title);  }, });

api.getNormalizedConfig

获取归一化后的全部 Rsbuild 配置,或指定环境的 Rsbuild 配置。该方法必须在 modifyRsbuildConfig 钩子执行完成后才能被调用。

相较于 getRsbuildConfig 方法,该方法返回的配置经过了归一化处理,配置的类型定义会得到收敛,比如 config.htmlundefined 类型将被移除。

推荐优先使用该方法获取配置。

  • 类型:
/** 获取指定环境的 Rsbuild 配置 */ function GetNormalizedConfig(options: {  environment: string; }): Readonly<NormalizedEnvironmentConfig>;  /** 获取全部的 Rsbuild 配置 */ function GetNormalizedConfig(): Readonly<NormalizedConfig>;
  • 示例:
const pluginFoo = () => ({  setup(api) {  api.onBeforeBuild(({ bundlerConfigs }) => {  const config = api.getNormalizedConfig();  console.log(config.html.title);  });  }, });

api.isPluginExists

判断某个插件是否已经在当前 Rsbuild 实例中注册。

  • 类型:
function IsPluginExists(pluginName: string): boolean;
  • 示例:
export default () => ({  setup(api) {  console.log(api.isPluginExists('plugin-foo'));  }, });

api.transform

用于转换模块的代码。

  • 类型:
function Transform(  descriptor: TransformDescriptor,  handler: TransformHandler, ): void;

api.transform 接受两个参数:

  • descriptor:一个对象,用于描述模块的匹配条件。
  • handler:一个转换函数,接收模块当前的代码,并返回转换后的代码。

示例

比如匹配以 .pug 为后缀的模块,并转换为 JavaScript 代码:

import pug from 'pug';  const pluginPug = () => ({  name: 'my-pug-plugin',  setup(api) {  api.transform({ test: /\.pug$/ }, ({ code }) => {  const templateCode = pug.compileClient(code, {});  return `${templateCode}; module.exports = template;`;  });  }, });

descriptor 参数

descriptor 参数是一个对象,用于描述模块的匹配条件。

  • 类型:
type TransformDescriptor = {  test?: RuleSetCondition;  targets?: RsbuildTarget[];  environments?: string[];  resourceQuery?: RuleSetCondition;  raw?: boolean;  layer?: string; };

descriptor 参数支持设置以下匹配条件:

  • test:匹配模块的路径(不包含 query),等价于 Rspack 的 Rule.test
api.transform({ test: /\.pug$/ }, ({ code }) => {  // ... });
  • targets:匹配 Rsbuild output.target,仅对匹配的 targets 应用当前 transform 函数。
api.transform({ test: /\.pug$/, targets: ['web'] }, ({ code }) => {  // ... });
  • environments:匹配 Rsbuild environment name,仅对匹配的 environments 应用当前 transform 函数。
api.transform({ test: /\.pug$/, environments: ['web'] }, ({ code }) => {  // ... });
api.transform({ resourceQuery: /raw/ }, ({ code }) => {  // ... });
  • raw:如果 rawtrue,则 transform 函数将接收到 Buffer 类型的代码,而不是 string 类型。
api.transform({ test: /\.node$/, raw: true }, ({ code }) => {  // ... });

handler 参数

handler 参数是一个转换函数,接收模块当前的代码,并返回转换后的代码。

  • 类型:
type TransformContext = {  code: string;  context: string | null;  resource: string;  resourcePath: string;  resourceQuery: string;  environment: EnvironmentContext;  addDependency: (file: string) => void;  emitFile: Rspack.LoaderContext['emitFile'];  importModule: Rspack.LoaderContext['importModule'];  resolve: Rspack.LoaderContext['resolve']; };  type TransformResult =  | string  | {  code: string;  map?: string | Rspack.sources.RawSourceMap | null;  };  type TransformHandler = (  context: TransformContext, ) => MaybePromise<TransformResult>;

handler 函数提供以下参数:

  • code:模块的代码。
  • context:当前被处理的模块所在的目录路径。与 Rspack loader 的 this.context 相同。
  • resolve:解析一个模块标识符。与 Rspack loader 的 this.resolve 相同。
  • resource:模块的绝对路径,包含 query。
  • resourcePath:模块的绝对路径,不包含 query。
  • resourceQuery:模块路径上的 query。
  • environment: 当前构建的 environment 上下文.
  • addDependency:添加一个额外的文件作为依赖。该文件将被监听,并在发生变更时触发重新构建。与 Rspack loader 的 this.addDependency 相同。
  • emitFile:将一个文件输出到构建结果中。与 Rspack loader 的 this.emitFile 相同。
  • importModule:在构建时编译并执行一个模块。与 Rspack loader 的 this.importModule 相同。

比如:

api.transform(  { test: /\.pug$/ },  ({ code, resource, resourcePath, resourceQuery }) => {  console.log(code); // -> some code  console.log(resource); // -> '/home/user/project/src/template.pug?foo=123'  console.log(resourcePath); // -> '/home/user/project/src/template.pug'  console.log(resourceQuery); // -> '?foo=123'  }, );

与 loader 的区别

api.transform 可以理解为 Rspack loader 的一个轻量化实现,它提供了简单易用的 API,并在底层自动调用 Rspack loader 进行代码转换。

在 Rsbuild 插件中,你可以通过 api.transform 快速实现代码转换功能,能够满足大部分常见场景,而无须学习 Rspack loader 的编写方法。

注意,对于一些复杂的代码转换场景,api.transform 可能无法满足,此时你可以使用 Rspack loader 进行实现。

api.resolve

在模块解析开始之前,拦截并修改模块的请求信息。等价于 Rspack 的 normalModuleFactory.hooks.resolve hook。

  • 版本: >= 1.0.17
  • 类型:
function ResolveFn(handler: ResolveHandler): void;

示例

  • 修改 a.js 文件请求:
api.resolve(({ resolveData }) => {  if (resolveData.request === './a.js') {  resolveData.request = './b.js';  } });

handler 参数

handler 参数是一个回调函数,接收一个模块的请求信息,并允许你修改它。

  • 类型:
type ResolveHandler = (context: {  resolveData: Rspack.ResolveData;  compiler: Rspack.Compiler;  compilation: Rspack.Compilation;  environment: EnvironmentContext; }) => Promise<void> | void;

handler 函数提供以下参数:

  • resolveData:当前模块请求信息,详情可参考 Rspack - resolve 钩子
  • compiler:Rspack 的 Compiler 对象。
  • compilation:Rspack 的 Compilation 对象。
  • environment: 当前构建的 environment 上下文

api.processAssets

在输出产物之前对 assets 进行修改,等价于 Rspack 的 compilation.hooks.processAssets hook。

  • 版本: >= 1.0.0
  • 类型:
function processAssets(  descriptor: ProcessAssetsDescriptor,  handler: ProcessAssetsHandler, ): void;

api.processAssets 接受两个参数:

  • descriptor:一个对象,用于描述 processAssets 触发的 stage 和匹配条件。
  • handler:一个回调函数,接收 assets 对象并允许你修改它。

示例

  • additional 阶段输出一个新的 asset:
api.processAssets(  { stage: 'additional' },  ({ assets, sources, compilation }) => {  const source = new sources.RawSource('This is a new asset!');  compilation.emitAsset('new-asset.txt', source);  }, );
  • 更新一个已经存在的 asset:
api.processAssets(  { stage: 'additions' },  ({ assets, sources, compilation }) => {  const asset = assets['foo.js'];  if (!asset) {  return;  }   const oldContent = asset.source();  const newContent = oldContent + '\nconsole.log("hello world!")';  const source = new sources.RawSource(newContent);   compilation.updateAsset(assetName, source);  }, );
  • 移除一个 asset:
api.processAssets({ stage: 'optimize' }, ({ assets, compilation }) => {  const assetName = 'unwanted-script.js';  if (assets[assetName]) {  compilation.deleteAsset(assetName);  } });

descriptor 参数

descriptor 参数是一个对象,用于描述 processAssets 触发的 stage 和匹配条件。

  • 类型:
type ProcessAssetsDescriptor = {  stage: ProcessAssetsStage;  targets?: RsbuildTarget[];  environments?: string[]; };

descriptor 参数支持设置以下属性:

  • stage:Rspack 内部将 processAssets 划分为多个 stages(参考 process assets stages,你可以根据需要进行的操作来选择合适的 stage。
api.processAssets({ stage: 'additional' }, ({ assets }) => {  // ... });
  • targets:匹配 Rsbuild output.target,仅对匹配的 targets 应用当前 processAssets 函数。
api.processAssets({ stage: 'additional', targets: ['web'] }, ({ assets }) => {  // ... });
  • environments:匹配 Rsbuild environment name,仅对匹配的 environments 应用当前 processAssets 函数。
api.processAssets(  { stage: 'additional', environments: ['web'] },  ({ assets }) => {  // ...  }, );

handler 参数

handler 参数是一个回调函数,接收一个 assets 对象,并允许你修改它。

  • 类型:
type ProcessAssetsHandler = (context: {  assets: Record<string, Rspack.sources.Source>;  compiler: Rspack.Compiler;  compilation: Rspack.Compilation;  environment: EnvironmentContext;  sources: RspackSources; }) => Promise<void> | void;

handler 函数提供以下参数:

  • assets:一个对象,其中 key 是 asset 的路径名,值是由 Source 表示的 asset 数据。
  • compiler:Rspack 的 Compiler 对象。
  • compilation:Rspack 的 Compilation 对象。
  • environment: 当前构建的 environment 上下文.
  • sourcesRspack Sources 对象,它包含了多种表示 Sources 的 classes。

Process assets stages

下面是支持的 stage 列表,Rspack 会按由上至下的顺序依次执行这些 stages,请根据你需要进行的操作来选择合适的 stage。

  • additional — 在编译中添加额外的 asset。
  • pre-process — asset 进行了基础的预处理。
  • derived — 从现有 asset 中派生新的 asset。
  • additions — 为现有的 asset 添加额外的内容,例如 banner 或初始代码。
  • optimize — 以通用的方式优化现有 asset。
  • optimize-count — 优化现有 asset 的数量,例如,进行合并操作。
  • optimize-compatibility — 优化现有 asset 的兼容性,例如添加 polyfills 或者 vendor prefixes。
  • optimize-size — 优化现有 asset 的大小,例如进行压缩或者删除空格。
  • dev-tooling — 为 asset 添加开发者工具,例如,提取 source map。
  • optimize-inline — 将 asset 内联到其他 asset 中来优化现有 asset 数量。
  • summarize — 整理现有 asset 列表。
  • optimize-hash — 优化 asset 的 hash 值,例如,生成基于 asset 内容的真实 hash 值。
  • optimize-transfer — 优化已有 asset 的转换操作,例如对 asset 进行压缩,并作为独立的 asset。
  • analyse — 分析已有 asset。
  • report — 创建用于上报的 asset。

api.expose

用于插件间通信。

api.expose 可以显式暴露当前插件的一些属性或方法,其他插件可以通过 api.useExposed 来获取这些 API。

  • 类型:
/**  * @param id 唯一标识符,使用 Symbol 可以避免命名冲突  * @param api 需要暴露的属性或方法,建议使用对象格式  */ function expose<T = any>(id: string | symbol, api: T): void;
  • 示例:
const pluginParent = () => ({  name: 'plugin-parent',  setup(api) {  api.expose('plugin-parent', {  value: 1,  double: (val: number) => val * 2,  });  }, });

api.useExposed

用于插件间通信。

api.useExposed 可以获取到其他插件暴露的属性或方法。

  • 类型:
/**  * @param id 唯一标识符  * @returns 获取的属性或方法  */ function useExposed<T = any>(id: string | symbol): T | undefined;
  • 示例:
const pluginChild = () => ({  name: 'plugin-child',  pre: ['plugin-parent'],  setup(api) {  const parentApi = api.useExposed('plugin-parent');  if (parentApi) {  console.log(parentApi.value); // -> 1  console.log(parentApi.double(1)); // -> 2  }  }, });

标识符

你可以使用 Symbol 作为唯一标识符,从而避免潜在的命名冲突:

// pluginParent.ts export const PARENT_API_ID = Symbol('plugin-parent');  const pluginParent = () => ({  name: 'plugin-parent',  setup(api) {  api.expose(PARENT_API_ID, {  // some api  });  }, });  // pluginChild.ts import { PARENT_API_ID } from './pluginParent';  const pluginChild = () => ({  name: 'plugin-child',  setup(api) {  const parentApi = api.useExposed(PARENT_API_ID);  if (parentApi) {  console.log(parentApi);  }  }, });

类型声明

你可以通过函数的泛型来声明类型:

// pluginParent.ts export type ParentAPI = {  // ... };  // pluginChild.ts import type { ParentAPI } from './pluginParent';  const pluginChild = () => ({  name: 'plugin-child',  setup(api) {  const parentApi = api.useExposed<ParentAPI>(PARENT_API_ID);  if (parentApi) {  console.log(parentApi);  }  }, });

执行顺序

在进行插件间通信时,你需要留意插件的执行顺序。

比如,在上面的示例中,如果 pluginParent 未注册,或者注册顺序晚于 pluginChild,那么 api.useExposed('plugin-parent') 会返回一个 undefined

你可以使用插件对象的 prepost 选项,以及插件 hook 的 order 选项来保证顺序是正确的。