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 的 dev server、middlewares、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 - 工具对象。
modifyHTMLTags
修改注入到 HTML 中的标签。
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 myPlugin = () => ({ setup(api) { api.modifyHTMLTags(({ headTags, bodyTags }) => { headTags.push({ tag: 'script', attrs: { src: 'https://example.com/foo.js' }, }); bodyTags.push({ tag: 'script', children: 'console.log("hello world!");', }); 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 的用户需要手动调用 rsbuild.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 // middlewares 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
关闭开发服务器时调用,可用于在开发服务器关闭时执行清理操作。
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: () => void): void;
const myPlugin = () => ({ setup(api) { api.onExit(() => { console.log('exit!'); }); }, });