Plugin Hooks

本章节介绍 Rsbuild 插件可用的 plugin hooks。

总览

Common Hooks

Dev Hooks

仅在执行 rsbuild dev 命令或 rsbuild.startDevServer() 方法时调用。

仅在 Rsbuild restart 时,或是执行 rsbuild.startDevServer()close() 方法时调用。

Build Hooks

仅在执行 rsbuild build 命令或 rsbuild.build() 方法时调用。

  • onBeforeBuild:在执行生产模式构建前调用。
  • onAfterBuild:在执行生产模式构建后调用,可以获取到构建结果信息。

仅在 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 配置合并;
  • onBeforeStartDevServeronAfterStartDevServer 和 Rsbuild dev server 启动流程相关,所有 environments 共享 Rsbuild 的 dev server、middlewares、Web Socket。

与之对应的,有一些插件 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 对象,也可以返回一个新的对象来替换传入的对象。

WARNING

modifyRsbuildConfig 为全局 hook。如果你希望你开发的插件支持仅在特定的 environment 下生效,应避免使用 modifyRsbuildConfig,可使用 modifyEnvironmentConfig 代替。

  • 类型:
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;
};

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';
      }
    });
  },
});

modifyBundlerChain

rspack-chain 是一个用于配置 Rspack 的工具库。它提供了链式 API,使得配置 Rspack 变得更加灵活。通过使用 rspack-chain,你可以更方便地修改和扩展 Rspack 配置,而不需要直接操作复杂的配置对象。

modifyBundlerChain 用于调用 rspack-chain 来修改 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: {
    BannerPlugin: rspack.BannerPlugin;
    DefinePlugin: rspack.DefinePlugin;
    IgnorePlugin: rspack.IgnorePlugin;
    ProvidePlugin: rspack.ProvidePlugin;
    HotModuleReplacementPlugin: rspack.HotModuleReplacementPlugin;
  };
};

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);
    });
  },
});

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.startDevServerrsbuild.buildrsbuild.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.startDevServerrsbuild.buildrsbuild.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

在启动开发服务器前调用。

  • 类型:
function OnBeforeStartDevServer(
  callback: (params: {
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • 示例:
const myPlugin = () => ({
  setup: (api) => {
    api.onBeforeStartDevServer(() => {
      console.log('before start!');
    });
  },
});

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;
  • 示例:
rsbuild.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!');
    });
  },
});