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.html
的 undefined
类型将被移除。
推荐优先使用该方法获取配置。
/** 获取指定环境的 Rsbuild 配置 */
function GetNormalizedConfig(options: {
environment: string;
}): Readonly<NormalizedEnvironmentConfig>;
/** 获取全部的 Rsbuild 配置 */
function GetNormalizedConfig(): Readonly<NormalizedConfig>;
const pluginFoo = () => ({
setup(api) {
const config = api.getNormalizedConfig();
console.log(config.html.title);
},
});
api.isPluginExists
判断某个插件是否已经被注册。
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
:如果 raw
为 true
,则 transform 函数将接收到 Buffer 类型的代码,而不是 string 类型。
api.transform({ test: /\.node$/, raw: true }, ({ code }) => {
// ...
});
handler 参数
handler 参数是一个转换函数,接收模块当前的代码,并返回转换后的代码。
type TransformContext = {
code: string;
resource: string;
resourcePath: string;
resourceQuery: string;
environment: EnvironmentContext;
addDependency: (file: string) => void;
emitFile: (
name: string,
content: string | Buffer,
sourceMap?: string,
assetInfo?: Record<string, any>,
) => void;
};
type TransformResult =
| string
| {
code: string;
map?: string | Rspack.sources.RawSourceMap | null;
};
type TransformHandler = (
context: TransformContext,
) => MaybePromise<TransformResult>;
handler 函数提供以下参数:
code
:模块的代码。
resource
:模块的绝对路径,包含 query。
resourcePath
:模块的绝对路径,不包含 query。
resourceQuery
:模块路径上的 query。
environment
: 当前构建的 environment 上下文.
addDependency
:添加一个额外的文件作为依赖。该文件将被监听,并在发生变更时触发重新构建。与 Rspack loader 的 this.addDependency 相同。
emitFile
:将一个文件输出到构建结果中。与 Rspack loader 的 this.emitFile 相同。
比如:
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。
function ResolveFn(handler: ResolveHandler): void;
示例
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 函数提供以下参数:
api.processAssets
在输出产物之前对 assets 进行修改,等价于 Rspack 的 compilation.hooks.processAssets hook。
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);
},
);
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);
},
);
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 参数支持设置以下属性:
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 上下文.
sources
:Rspack 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
。
你可以使用插件对象的 pre
、post
选项,以及插件 hook 的 order
选项来保证顺序是正确的。