Plugin hooks
本章节介绍 Rsbuild 插件可用的 plugin hooks。
总览
Common hooks
Dev hooks
仅在执行 rsbuild dev
命令或 rsbuild.startDevServer()
方法时调用。
仅在 Rsbuild restart 时,或是执行 rsbuild.startDevServer()
的 close() 方法时调用。
Build hooks
仅在执行 rsbuild build
命令或 rsbuild.build()
方法时调用。
仅在 Rsbuild restart 时,或是执行 rsbuild.build()
的 close() 方法时调用。
Preview hooks
仅在执行 rsbuild preview
命令或 rsbuild.preview()
方法时调用。
Hooks 顺序
Dev hooks
执行 rsbuild dev
命令或 rsbuild.startDevServer()
方法时,Rsbuild 会依次执行以下 hooks:
当 rebuild 时,以下 hooks 会再次触发:
Build hooks
执行 rsbuild build
命令或 rsbuild.build()
方法时,Rsbuild 会依次执行以下 hooks:
当 rebuild 时,以下 hooks 会再次触发:
Preview hooks
执行 rsbuild preview
命令或 rsbuild.preview()
方法时,Rsbuild 会依次执行以下 hooks:
Global hooks vs environment hooks
在 Rsbuild 中,有一些插件 hooks 是全局 hooks,这些 hook 的执行往往和 Rsbuild 自身的启动流程或全局逻辑相关,在所有 environment 下共享。如:
modifyRsbuildConfig
用来修改 Rsbuild 的基础配置,基础配置最终会和 environment 配置合并;
onBeforeStartDevServer
、onAfterStartDevServer
和 Rsbuild dev server 启动流程相关,所有 environments 共享 Rsbuild 的开发服务器、中间件、WebSocket。
与之对应的,有一些插件 hooks 是和当前 environment 相关的 hook,这些 hook 执行时会带有特定的 environment 上下文,并根据 environment 的不同而触发多次。
Global hooks
Environment hooks
回调函数顺序
默认行为
如果多个插件注册了相同的 hook,那么 hook 的回调函数会按照注册时的顺序执行。
在以下例子中,控制台会依次输出 '1'
和 '2'
:
const plugin1 = () => ({
setup(api) {
api.modifyRsbuildConfig(() => console.log('1'));
},
});
const plugin2 = () => ({
setup(api) {
api.modifyRsbuildConfig(() => console.log('2'));
},
});
rsbuild.addPlugins([plugin1, plugin2]);
order 字段
在注册 hook 时,可以通过 order
字段来声明 hook 的顺序。
type HookDescriptor<T extends (...args: any[]) => any> = {
handler: T;
order: 'pre' | 'post' | 'default';
};
在以下例子中,控制台会依次输出 '2'
和 '1'
,因为 plugin2 在调用 modifyRsbuildConfig 时设置了 order 为 pre
。
const plugin1 = () => ({
setup(api) {
api.modifyRsbuildConfig(() => console.log('1'));
},
});
const plugin2 = () => ({
setup(api) {
api.modifyRsbuildConfig({
handler: () => console.log('2'),
order: 'pre',
});
},
});
rsbuild.addPlugins([plugin1, plugin2]);
Common hooks
modifyRsbuildConfig
修改传递给 Rsbuild 的配置项,你可以直接修改传入的 config 对象,也可以返回一个新的对象来替换传入的对象。
type ModifyRsbuildConfigUtils = {
mergeRsbuildConfig: typeof mergeRsbuildConfig;
};
function ModifyRsbuildConfig(
callback: (
config: RsbuildConfig,
utils: ModifyRsbuildConfigUtils,
) => MaybePromise<RsbuildConfig | void>,
): void;
const myPlugin = () => ({
setup(api) {
api.modifyRsbuildConfig((config) => {
config.html ||= {};
config.html.title = 'My Default Title';
});
},
});
- 示例: 通过
mergeRsbuildConfig
合并配置多个对象,并返回合并后的对象。
import type { RsbuildConfig } from '@rsbuild/core';
const myPlugin = () => ({
setup(api) {
api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => {
const extraConfig: RsbuildConfig = {
source: {
// ...
},
output: {
// ...
},
};
// extraConfig 会覆盖 userConfig 里的字段,
// 如果你不希望覆盖 userConfig,可以调整为 `mergeRsbuildConfig(extraConfig, userConfig)`
return mergeRsbuildConfig(userConfig, extraConfig);
});
},
});
TIP
modifyRsbuildConfig
不能用于注册额外的 Rsbuild 插件。这是因为在执行 modifyRsbuildConfig
时,Rsbuild 已经初始化了所有插件,并开始执行 hooks 的回调函数。详情可参考 插件注册时机。
modifyEnvironmentConfig
修改特定 environment 的 Rsbuild 配置。
在回调函数中,入参里的 config 对象已经合并了公共的 Rsbuild 配置,你可以直接修改这个 config 对象,也可以返回一个新的对象来替换它。
type ArrayAtLeastOne<A, B> = [A, ...Array<A | B>] | [...Array<A | B>, A];
type ModifyEnvironmentConfigUtils = {
/** 当前 environment 名称 */
name: string;
mergeEnvironmentConfig: (
...configs: ArrayAtLeastOne<MergedEnvironmentConfig, EnvironmentConfig>
) => EnvironmentConfig;
};
function ModifyEnvironmentConfig(
callback: (
config: EnvironmentConfig,
utils: ModifyEnvironmentConfigUtils,
) => MaybePromise<EnvironmentConfig | void>,
): void;
- 示例: 为指定 environment 的 Rsbuild config 设置一个默认值:
const myPlugin = () => ({
setup(api) {
api.modifyEnvironmentConfig((config, { name }) => {
if (name !== 'web') {
return config;
}
config.html.title = 'My Default Title';
});
},
});
- 示例: 通过
mergeEnvironmentConfig
合并配置多个对象,并返回合并后的对象。
import type { EnvironmentConfig } from '@rsbuild/core';
const myPlugin = () => ({
setup(api) {
api.modifyEnvironmentConfig((userConfig, { mergeEnvironmentConfig }) => {
const extraConfig: EnvironmentConfig = {
source: {
// ...
},
output: {
// ...
},
};
// extraConfig 会覆盖 userConfig 里的字段
// 如果你不希望覆盖 userConfig,可以调整为 `mergeEnvironmentConfig(extraConfig, userConfig)`
return mergeEnvironmentConfig(userConfig, extraConfig);
});
},
});
modifyRspackConfig
修改 Rspack 配置,你可以直接修改传入的 config 对象,也可以返回一个新的对象来替换传入的对象。
TIP
modifyRspackConfig
的执行时机早于 tools.rspack。因此,无法在 modifyRspackConfig
中获取到 tools.rspack
所做的修改。
type ModifyRspackConfigUtils = {
environment: EnvironmentContext;
env: string;
isDev: boolean;
isProd: boolean;
target: RsbuildTarget;
isServer: boolean;
isWebWorker: boolean;
rspack: Rspack;
HtmlPlugin: typeof import('html-rspack-plugin');
// more...
};
function ModifyRspackConfig(
callback: (
config: Rspack.Configuration,
utils: ModifyRspackConfigUtils,
) => Promise<RspackConfig | void> | Rspack.Configuration | void,
): void;
const myPlugin = () => ({
setup(api) {
api.modifyRspackConfig((config, utils) => {
if (utils.env === 'development') {
config.devtool = 'eval-cheap-source-map';
}
});
},
});
回调函数的第二个参数 utils
是一个对象,包含了一些工具函数和属性,详见 tools.rspack - 工具对象。
modifyBundlerChain
rspack-chain 是一个用于配置 Rspack 的工具库。它提供了链式 API,使得配置 Rspack 变得更加灵活。通过使用 rspack-chain
,你可以更方便地修改和扩展 Rspack 配置,而不需要直接操作复杂的配置对象。
modifyBundlerChain
允许你使用 rspack-chain
API 来修改 Rspack 的配置,它的用法与 tools.bundlerChain 相同。
type ModifyBundlerChainUtils = {
environment: EnvironmentContext;
env: string;
isDev: boolean;
isProd: boolean;
target: RsbuildTarget;
isServer: boolean;
isWebWorker: boolean;
CHAIN_ID: ChainIdentifier;
HtmlPlugin: typeof import('html-rspack-plugin');
bundler: {
// some Rspack built-in plugins
};
};
function ModifyBundlerChain(
callback: (
chain: RspackChain,
utils: ModifyBundlerChainUtils,
) => Promise<void> | void,
): void;
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
const myPlugin = () => ({
setup(api) {
api.modifyBundlerChain((chain, utils) => {
if (utils.env === 'development') {
chain.devtool('eval');
}
chain.plugin('bundle-analyze').use(BundleAnalyzerPlugin);
});
},
});
回调函数的第二个参数 utils
是一个对象,包含了一些工具函数和属性,详见 tools.bundlerChain - 工具对象。
modifyHTML
修改最终的 HTML 内容。该钩子接收一个 HTML 字符串和上下文对象,你可以返回一个新的 HTML 字符串来替换原始内容。
这个钩子在 modifyHTMLTags 钩子之后触发。
type Context = {
/**
* Rspack 的 Compiler 对象
*/
compiler: Rspack.Compiler;
/**
* Rspack 的 Compilation 对象
*/
compilation: Rspack.Compilation;
/**
* HTML 文件的名称,相对于 dist 目录
* @example 'index.html'
*/
filename: string;
/**
* 当前构建的 environment 上下文
*/
environment: EnvironmentContext;
};
function ModifyHTML(
callback: (html: string, context: Context) => MaybePromise<string>,
): void;
const myPlugin = () => ({
setup(api) {
api.modifyHTML((html) => {
return html.replace('foo', 'bar');
});
},
});
基于 filename
来修改 HTML 内容:
const myPlugin = () => ({
setup(api) {
api.modifyHTML((html, { filename }) => {
if (filename === 'foo.html') {
return html.replace('foo', 'bar');
}
return html;
});
},
});
与直接操作 HTML 字符串相比,你可以借助 cheerio 或 htmlparser2 等库来更便捷地修改 HTML 内容。
以 cheerio
为例,它提供了类似 jQuery 的 API 来操作 HTML:
import cheerio from 'cheerio';
const myPlugin = () => ({
setup(api) {
api.modifyHTML((html) => {
const $ = cheerio.load(html);
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
return $.html();
});
},
});
modifyHTMLTags
修改注入到 HTML 中的标签。这个钩子在 modifyHTML 钩子之前触发。
type HtmlBasicTag = {
// 标签名
tag: string;
// 标签的属性
attrs?: Record<string, string | boolean | null | undefined>;
// 标签的 innerHTML
children?: string;
};
type HTMLTags = {
// 插入到 <head> 的标签组
headTags: HtmlBasicTag[];
// 插入到 <body> 的标签组
bodyTags: HtmlBasicTag[];
};
type Context = {
/**
* Rspack 的 Compiler 对象
*/
compiler: Rspack.Compiler;
/**
* Rspack 的 Compilation 对象
*/
compilation: Rspack.Compilation;
/**
* 静态资源的 URL 前缀
* @example 'https://example.com/'
*/
assetPrefix: string;
/**
* HTML 文件的名称,相对于 dist 目录
* @example 'index.html'
*/
filename: string;
/**
* 当前构建的 environment 上下文
*/
environment: EnvironmentContext;
};
function ModifyHTMLTags(
callback: (tags: HTMLTags, context: Context) => MaybePromise<HTMLTags>,
): void;
const tagsPlugin = () => ({
name: 'tags-plugin',
setup(api) {
api.modifyHTMLTags(({ headTags, bodyTags }) => {
// 在 <head> 中插入一个标签,位于其他标签之前
headTags.unshift({
tag: 'script',
attrs: { src: 'https://example.com/foo.js' },
});
// 在 <head> 中插入一个标签,位于其他标签之后
headTags.push({
tag: 'script',
attrs: { src: 'https://example.com/bar.js' },
});
// 在 <body> 中插入一个标签,位于其他标签之前
bodyTags.unshift({
tag: 'div',
children: 'before other body tags',
});
// 在 <body> 中插入一个标签,位于其他标签之后
bodyTags.push({
tag: 'div',
children: 'after other body tags',
});
return { headTags, bodyTags };
});
},
});
onBeforeCreateCompiler
onBeforeCreateCompiler
是在创建底层 Compiler 实例前触发的回调函数,当你执行 rsbuild.startDevServer
、rsbuild.build
或 rsbuild.createCompiler
时,都会调用此钩子。
你可以通过 bundlerConfigs
参数获取到 Rspack 配置数组,数组中可能包含一份或多份 Rspack 配置,这取决于是否配置了多个 environments。
function OnBeforeCreateCompiler(
callback: (params: {
bundlerConfigs: Rspack.Configuration[];
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onBeforeCreateCompiler(({ bundlerConfigs }) => {
console.log('the bundler config is ', bundlerConfigs);
});
},
});
onAfterCreateCompiler
onAfterCreateCompiler
是在创建 Compiler 实例后、执行构建前触发的回调函数,当你执行 rsbuild.startDevServer
、rsbuild.build
或 rsbuild.createCompiler
时,都会调用此钩子。
你可以通过 compiler
参数获取到 Compiler 实例对象:
function OnAfterCreateCompiler(callback: (params: {
compiler: Compiler | MultiCompiler;
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void;): void;
const myPlugin = () => ({
setup(api) {
api.onAfterCreateCompiler(({ compiler }) => {
console.log('the compiler is ', compiler);
});
},
});
onBeforeEnvironmentCompile
onBeforeEnvironmentCompile
是在执行单个 environment 的构建前触发的回调函数。
你可以通过 bundlerConfig
参数获取到当前 environment 对应的 Rspack 配置。
另外,你可以通过 isWatch
判断是否是 dev 或者 build watch 模式,并在 watch 模式下通过 isFirstCompile
来判断是否为首次构建。
function OnBeforeEnvironmentCompile(
callback: (params: {
isWatch: boolean;
isFirstCompile: boolean;
bundlerConfig?: Rspack.Configuration;
environment: EnvironmentContext;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onBeforeEnvironmentCompile(({ bundlerConfig, environment }) => {
console.log(
`the bundler config for the ${environment.name} is `,
bundlerConfig,
);
});
},
});
onAfterEnvironmentCompile
onAfterEnvironmentCompile
是在执行单个 environment 的构建后触发的回调函数,你可以通过 stats 参数获取到构建结果信息。
另外,你可以通过 isWatch
判断是否是 dev 或者 build watch 模式,并通过 isFirstCompile
来判断是否为首次构建。
function OnAfterEnvironmentCompile(
callback: (params: {
isFirstCompile: boolean;
isWatch: boolean;
stats?: Stats;
environment: EnvironmentContext;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onAfterEnvironmentCompile(({ isFirstCompile, stats }) => {
console.log(stats?.toJson(), isFirstCompile);
});
},
});
Build hooks
onBeforeBuild
onBeforeBuild
是在执行生产模式构建前触发的回调函数。
你可以通过 bundlerConfigs
参数获取到 Rspack 配置数组,数组中可能包含一份或多份 Rspack 配置,这取决于是否配置了多个 environments。
另外,你可以通过 isWatch
判断是否是 watch 模式,并在 watch 模式下通过 isFirstCompile
来判断是否为首次构建。
function OnBeforeBuild(
callback: (params: {
isWatch: boolean;
isFirstCompile: boolean;
bundlerConfigs?: Rspack.Configuration[];
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onBeforeBuild(({ bundlerConfigs }) => {
console.log('the bundler config is ', bundlerConfigs);
});
},
});
onAfterBuild
onAfterBuild
是在执行生产模式构建后触发的回调函数,你可以通过 stats 参数获取到构建结果信息。
另外,你可以通过 isWatch
判断是否是 watch 模式,并在 watch 模式下通过 isFirstCompile
来判断是否为首次构建。
function OnAfterBuild(
callback: (params: {
isFirstCompile: boolean;
isWatch: boolean;
stats?: Stats | MultiStats;
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onAfterBuild(({ isFirstCompile, stats }) => {
console.log(stats?.toJson(), isFirstCompile);
});
},
});
onCloseBuild
在关闭构建时调用,可用于在构建关闭时执行清理操作。
Rsbuild CLI 会在执行 rsbuild build 完成后自动调用此钩子,使用 JavaScript API 的用户需要手动调用 build.close() 方法来触发此钩子。
function onCloseBuild(callback: () => Promise<void> | void): void;
const myPlugin = () => ({
setup(api) {
api.onCloseBuild(() => {
console.log('close build!');
});
},
});
Dev hooks
onBeforeStartDevServer
在启动开发服务器前调用。
通过 server
参数可以获取到开发服务器实例,参考 Dev server API 了解更多。
type OnBeforeStartDevServerFn = (params: {
/**
* The dev server instance, the same as the return value of `createDevServer`.
*/
server: RsbuildDevServer;
/**
* A read-only object that provides some context information about different environments.
*/
environments: Record<string, EnvironmentContext>;
}) => MaybePromise<void>;
function OnBeforeStartDevServer(callback: OnBeforeStartDevServerFn): void;
const myPlugin = () => ({
setup(api) {
api.onBeforeStartDevServer(({ server, environments }) => {
console.log('before starting dev server.');
console.log('the server is ', server);
console.log('the environments contexts are: ', environments);
});
},
});
注册中间件
一个常见的使用场景是在 onBeforeStartDevServer
中注册自定义的中间件:
const myPlugin = () => ({
setup(api) {
api.onBeforeStartDevServer(({ server }) => {
server.middlewares.use((req, res, next) => {
next();
});
});
},
});
当 onBeforeStartDevServer
被调用时,Rsbuild 内置的中间件还未注册,因此你添加的中间件会早于内置中间件执行。
onBeforeStartDevServer
允许你返回一个回调函数,当 Rsbuild 内置的中间件注册完成后,会执行你返回的回调函数,在回调函数中注册的中间件会晚于内置中间件执行。
const myPlugin = () => ({
setup(api) {
api.onBeforeStartDevServer(({ server }) => {
// the returned callback will be called when the default
// middleware are registered
return () => {
server.middlewares.use((req, res, next) => {
next();
});
};
});
},
});
保存 server 实例
如果你需要在其他 hooks 中访问 server
,可以通过 onBeforeStartDevServer
来存储 server
实例,并在执行后续的 hooks 时访问它。注意你不能在执行时机早于 onBeforeStartDevServer
的 hooks 中访问 server
。
import type { RsbuildDevServer } from '@rsbuild/core';
const myPlugin = () => ({
setup(api) {
let devServer: RsbuildDevServer | null = null;
api.onBeforeStartDevServer(({ server, environments }) => {
devServer = server;
});
api.transform({ test: /\.foo$/ }, ({ code }) => {
if (devServer) {
// access server API
}
return code;
});
api.onCloseDevServer(() => {
devServer = null;
});
},
});
onAfterStartDevServer
在启动开发服务器后调用。你可以通过 port
参数获得开发服务器监听的端口号,通过 routes
获得页面路由信息。
type Routes = Array<{
entryName: string;
pathname: string;
}>;
function OnAfterStartDevServer(
callback: (params: {
port: number;
routes: Routes;
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onAfterStartDevServer(({ port, routes }) => {
console.log('this port is: ', port);
console.log('this routes is: ', routes);
});
},
});
onAfterEnvironmentCompile
onAfterEnvironmentCompile
是在执行单个 environment 的构建后触发的回调函数,你可以通过 stats 参数获取到构建结果信息。
另外,你可以通过 isWatch
判断是否是 dev 或者 build watch 模式,并通过 isFirstCompile
来判断是否为首次构建。
function OnAfterEnvironmentCompile(
callback: (params: {
isFirstCompile: boolean;
isWatch: boolean;
stats?: Stats;
environment: EnvironmentContext;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onAfterEnvironmentCompile(({ isFirstCompile }) => {
if (isFirstCompile) {
console.log('first compile!');
} else {
console.log('re-compile!');
}
});
},
});
onDevCompileDone
在每次开发模式构建结束后调用,你可以通过 isFirstCompile
来判断是否为首次构建。
function OnDevCompileDone(
callback: (params: {
isFirstCompile: boolean;
stats: Stats | MultiStats;
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onDevCompileDone(({ isFirstCompile }) => {
if (isFirstCompile) {
console.log('first compile!');
} else {
console.log('re-compile!');
}
});
},
});
onCloseDevServer
关闭开发服务器时调用,可用于在开发服务器关闭时执行清理操作。
Rsbuild CLI 会自动在合适的时机调用此钩子,使用 JavaScript API 的用户需要手动调用 server.close() 方法来触发此钩子。
function onCloseDevServer(callback: () => Promise<void> | void): void;
const myPlugin = () => ({
setup(api) {
api.onCloseDevServer(async () => {
console.log('close dev server!');
});
},
});
Preview hooks
onBeforeStartProdServer
在启动生产预览服务器前调用。
function OnBeforeStartProdServer(callback: () => Promise<void> | void): void;
const myPlugin = () => ({
setup(api) {
api.onBeforeStartProdServer(() => {
console.log('before start!');
});
},
});
onAfterStartProdServer
在启动生产预览服务器后调用,你可以通过 port
参数获得生产服务器监听的端口号,通过 routes
获得页面路由信息。
type Routes = Array<{
entryName: string;
pathname: string;
}>;
function OnAfterStartProdServer(
callback: (params: {
port: number;
routes: Routes;
environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void,
): void;
const myPlugin = () => ({
setup(api) {
api.onAfterStartProdServer(({ port, routes }) => {
console.log('this port is: ', port);
console.log('this routes is: ', routes);
});
},
});
Other hooks
onExit
在进程即将退出时调用,这个钩子只能执行同步代码。
function OnExit(callback: (context: { exitCode: number }) => void): void;
const myPlugin = () => ({
setup(api) {
api.onExit(({ exitCode }) => {
console.log('exit: ', exitCode);
});
},
});