Plugin hooks

This chapter introduces the plugin hooks available for Rsbuild plugins.

Overview

Common hooks

Dev hooks

Called only when running the rsbuild dev command or the rsbuild.startDevServer() method.

Called only when Rsbuild is restarted or when the close() method of rsbuild.build() is executed.

Build hooks

Called only when running the rsbuild build command or the rsbuild.build() method.

  • onBeforeBuild: Called before running the production build.
  • onAfterBuild: Called after running the production build. You can get the build result information.

Called only when Rsbuild is restarted or when the close() method of rsbuild.build() is executed.

Preview hooks

Called only when running the rsbuild preview command or the rsbuild.preview() method.

Hooks order

Dev hooks

When the rsbuild dev command or rsbuild.startDevServer() method is executed, Rsbuild will execute the following hooks in order:

When rebuilding, the following hooks will be triggered again:

Build hooks

When the rsbuild build command or rsbuild.build() method is executed, Rsbuild will execute the following hooks in order:

When rebuilding, the following hooks will be triggered again:

Preview hooks

When executing the rsbuild preview command or rsbuild.preview() method, Rsbuild will execute the following hooks in order:

Global hooks vs environment hooks

In Rsbuild, some of the plugin hooks are global hooks. The execution of these hooks is often related to Rsbuild's own startup process or global logic and is shared under all environments. Such as:

  • modifyRsbuildConfig is used to modify the basic configuration of Rsbuild. The basic configuration will eventually be merged with the environment configuration;
  • onBeforeStartDevServer and onAfterStartDevServer are related to the Rsbuild dev server startup process, all environments share Rsbuild's dev server, middlewares, and WebSocket.

Correspondingly, there are some plugin hooks that are related to the current environment. These hooks are executed with a specific environment context and are triggered multiple times depending on the environment.

Global hooks

Environment hooks

Callback order

Default behavior

If multiple plugins register the same hook, the callback functions of the hook will execute in the order in which they were registered.

In the following example, the console will output '1' and '2' in sequence:

const plugin1 = () => ({
  setup(api) {
    api.modifyRsbuildConfig(() => console.log('1'));
  },
});

const plugin2 = () => ({
  setup(api) {
    api.modifyRsbuildConfig(() => console.log('2'));
  },
});

rsbuild.addPlugins([plugin1, plugin2]);

order Field

When registering a hook, you can declare the order of hook through the order field.

type HookDescriptor<T extends (...args: any[]) => any> = {
  handler: T;
  order: 'pre' | 'post' | 'default';
};

In the following example, the console will sequentially output '2' and '1', because order was set to pre when plugin2 called modifyRsbuildConfig.

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

Modify the config passed to the Rsbuild, you can directly modify the config object, or return a new object to replace the previous object.

WARNING

modifyRsbuildConfig is a global hook. To add support for your plugin as an environment-specific plugin, you should use modifyEnvironmentConfig instead of modifyRsbuildConfig.

  • Type:
type ModifyRsbuildConfigUtils = {
  mergeRsbuildConfig: typeof mergeRsbuildConfig;
};

function ModifyRsbuildConfig(
  callback: (
    config: RsbuildConfig,
    utils: ModifyRsbuildConfigUtils,
  ) => MaybePromise<RsbuildConfig | void>,
): void;
  • Example: Setting a default value for a specific config option:
const myPlugin = () => ({
  setup(api) {
    api.modifyRsbuildConfig((config) => {
      config.html ||= {};
      config.html.title = 'My Default Title';
    });
  },
});
  • Example: Using mergeRsbuildConfig to merge config objects, and return the merged object.
import type { RsbuildConfig } from '@rsbuild/core';

const myPlugin = () => ({
  setup(api) {
    api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => {
      const extraConfig: RsbuildConfig = {
        source: {
          // ...
        },
        output: {
          // ...
        },
      };

      // extraConfig will override fields in userConfig,
      // If you do not want to override the fields in userConfig,
      // you can adjust to `mergeRsbuildConfig(extraConfig, userConfig)`
      return mergeRsbuildConfig(userConfig, extraConfig);
    });
  },
});
TIP

modifyRsbuildConfig cannot be used to register additional Rsbuild plugins. This is because at the time modifyRsbuildConfig is executed, Rsbuild has already initialized all plugins and started executing the callbacks of the hooks.

For details, please refer to Plugin registration phase.

modifyEnvironmentConfig

Modify the Rsbuild configuration of a specific environment.

In the callback function, the config object in the parameters has already been merged with the common Rsbuild configuration. You can directly modify this config object, or you can return a new object to replace it.

  • Type:
type ArrayAtLeastOne<A, B> = [A, ...Array<A | B>] | [...Array<A | B>, A];

type ModifyEnvironmentConfigUtils = {
  /** Current environment name */
  name: string;
  mergeEnvironmentConfig: (
    ...configs: ArrayAtLeastOne<MergedEnvironmentConfig, EnvironmentConfig>
  ) => EnvironmentConfig;
};

function ModifyEnvironmentConfig(
  callback: (
    config: EnvironmentConfig,
    utils: ModifyEnvironmentConfigUtils,
  ) => MaybePromise<EnvironmentConfig | void>,
): void;
  • Example: Set a default value for the Rsbuild config of a specified environment:
const myPlugin = () => ({
  setup(api) {
    api.modifyEnvironmentConfig((config, { name }) => {
      if (name !== 'web') {
        return config;
      }
      config.html.title = 'My Default Title';
    });
  },
});
  • Example: Using mergeEnvironmentConfig to merge config objects, and return the merged object.
import type { EnvironmentConfig } from '@rsbuild/core';

const myPlugin = () => ({
  setup(api) {
    api.modifyEnvironmentConfig((userConfig, { mergeEnvironmentConfig }) => {
      const extraConfig: EnvironmentConfig = {
        source: {
          // ...
        },
        output: {
          // ...
        },
      };

      // extraConfig will override fields in userConfig,
      // If you do not want to override the fields in userConfig,
      // you can adjust to `mergeEnvironmentConfig(extraConfig, userConfig)`
      return mergeEnvironmentConfig(userConfig, extraConfig);
    });
  },
});

modifyRspackConfig

To modify the Rspack config, you can directly modify the config object, or return a new object to replace the previous object.

TIP

modifyRspackConfig is executed earlier than tools.rspack. Therefore, the modifications made by tools.rspack cannot be obtained in modifyRspackConfig.

  • Type:
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;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.modifyRspackConfig((config, utils) => {
      if (utils.env === 'development') {
        config.devtool = 'eval-cheap-source-map';
      }
    });
  },
});

The second parameter utils of the callback function is an object, which contains some utility functions and properties, see tools.rspack - Utils for more details.

modifyBundlerChain

rspack-chain is a utility library for configuring Rspack. It provides a chaining API, making the configuration of Rspack more flexible. By using rspack-chain, you can more easily modify and extend Rspack configurations without directly manipulating the complex configuration object.

modifyBundlerChain allows you to modify the Rspack configuration using the rspack-chain API, providing the same functionality as tools.bundlerChain.

  • Type:
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;
  • Example:
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);
    });
  },
});

The second parameter utils of the callback function is an object, which contains some utility functions and properties, see tools.bundlerChain - Utils for more details.

modifyHTMLTags

Modify the tags that are injected into the HTML.

  • Type:
type HtmlBasicTag = {
  // Tag name
  tag: string;
  // Attributes of the tag
  attrs?: Record<string, string | boolean | null | undefined>;
  // innerHTML of the tag
  children?: string;
};

type HTMLTags = {
  // Tags group inserted into <head>
  headTags: HtmlBasicTag[];
  // Tags group inserted into <body>
  bodyTags: HtmlBasicTag[];
};

type Context = {
  /**
   * The Compiler object of Rspack.
   */
  compiler: Rspack.Compiler;
  /**
   * The Compilation object of Rspack.
   */
  compilation: Rspack.Compilation;
  /**
   * URL prefix of assets.
   * @example 'https://example.com/'
   */
  assetPrefix: string;
  /**
   * The name of the HTML file, relative to the dist directory.
   * @example 'index.html'
   */
  filename: string;
  /**
   * The environment context for current build.
   */
  environment: EnvironmentContext;
};

function ModifyHTMLTags(
  callback: (tags: HTMLTags, context: Context) => MaybePromise<HTMLTags>,
): void;
  • Example:
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

A callback function that is triggered after the Compiler instance has been created, but before the build process begins. This hook is called when you run rsbuild.startDevServer, rsbuild.build, or rsbuild.createCompiler.

You can access the Rspack configuration array through the bundlerConfigs parameter. The array may contain one or more Rspack configurations. It depends on whether multiple environments are configured.

  • Type:
function OnBeforeCreateCompiler(
  callback: (params: {
    bundlerConfigs: Rspack.Configuration[];
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onBeforeCreateCompiler(({ bundlerConfigs }) => {
      console.log('the bundler config is ', bundlerConfigs);
    });
  },
});

onAfterCreateCompiler

A callback function that is triggered after the compiler instance has been created, but before the build process. This hook is called when you run rsbuild.startDevServer, rsbuild.build, or rsbuild.createCompiler.

You can access the Compiler instance through the compiler parameter:

  • Type:
function OnAfterCreateCompiler(callback: (params: {
  compiler: Compiler | MultiCompiler;
  environments: Record<string, EnvironmentContext>;
}) => Promise<void> | void;): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterCreateCompiler(({ compiler }) => {
      console.log('the compiler is ', compiler);
    });
  },
});

onBeforeEnvironmentCompile

A callback function that is triggered before the compilation of a single environment.

You can access the Rspack configuration array through the bundlerConfigs parameter. The array may contain one or more Rspack configurations. It depends on whether multiple environments are configured.

Moreover, you can use isWatch to determine whether it is dev or build watch mode, and use isFirstCompile to determine whether it is the first build.

  • Type:
function OnBeforeEnvironmentCompile(
  callback: (params: {
    isWatch: boolean;
    isFirstCompile: boolean;
    bundlerConfig?: Rspack.Configuration;
    environment: EnvironmentContext;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onBeforeEnvironmentCompile(({ bundlerConfig, environment }) => {
      console.log(
        `the bundler config for the ${environment.name} is `,
        bundlerConfig,
      );
    });
  },
});

onAfterEnvironmentCompile

A callback function that is triggered after the compilation of a single environment. You can access the build result information via the stats parameter.

Moreover, you can use isWatch to determine whether it is dev or build watch mode, and use isFirstCompile to determine whether it is the first build.

  • Type:
function OnAfterEnvironmentCompile(
  callback: (params: {
    isFirstCompile: boolean;
    isWatch: boolean;
    stats?: Stats;
    environment: EnvironmentContext;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterEnvironmentCompile(({ isFirstCompile, stats }) => {
      console.log(stats?.toJson(), isFirstCompile);
    });
  },
});

Build hooks

onBeforeBuild

A callback function that is triggered before the production build is executed.

You can access the Rspack configuration array through the bundlerConfigs parameter. The array may contain one or more Rspack configurations. It depends on whether multiple environments are configured.

Moreover, you can use isWatch to determine whether it is watch mode, and use isFirstCompile to determine whether it is the first build on watch mode.

  • Type:
function OnBeforeBuild(
  callback: (params: {
    isWatch: boolean;
    isFirstCompile: boolean;
    bundlerConfigs?: Rspack.Configuration[];
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onBeforeBuild(({ bundlerConfigs }) => {
      console.log('the bundler config is ', bundlerConfigs);
    });
  },
});

onAfterBuild

A callback function that is triggered after running the production build. You can access the build result information via the stats parameter.

Moreover, you can use isWatch to determine whether it is watch mode, and use isFirstCompile to determine whether it is the first build on watch mode.

  • Type:
function OnAfterBuild(
  callback: (params: {
    isFirstCompile: boolean;
    isWatch: boolean;
    stats?: Stats | MultiStats;
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterBuild(({ isFirstCompile, stats }) => {
      console.log(stats?.toJson(), isFirstCompile);
    });
  },
});

onCloseBuild

Called when closing the build instance. Can be used to perform cleanup operations when the building is closed.

Rsbuild CLI will automatically call this hook after running rsbuild build, while users of the JavaScript API need to manually call the rsbuild.close() method to trigger this hook.

  • Type:
function onCloseBuild(callback: () => Promise<void> | void): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onCloseBuild(() => {
      console.log('close build!');
    });
  },
});

Dev hooks

onBeforeStartDevServer

Called before starting the dev server.

Use the server parameter to get the dev server instance, see Dev server API for more information.

  • Type:
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;
  • Example:
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);
    });
  },
});

Register middlewares

A common usage scenario is to register custom middlewares in onBeforeStartDevServer:

const myPlugin = () => ({
  setup(api) {
    api.onBeforeStartDevServer(({ server }) => {
      server.middlewares.use((req, res, next) => {
        next();
      });
    });
  },
});

When onBeforeStartDevServer is called, the default middlewares of Rsbuild are not registered yet, so the middlewares you add will run before the default middlewares.

onBeforeStartDevServer allows you to return a callback function, which will be called when the default middlewares of Rsbuild are registered. The middlewares you register in the callback function will run after the default middlewares.

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

Store server instance

If you need to access server in other hooks, you can store the server instance through api.onBeforeStartDevServer, and then access it in the hooks executed later. Note that you cannot access server in hooks that are executed earlier than onBeforeStartDevServer.

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

Called after starting the dev server, you can get the port number with the port parameter, and the page routes info with the routes parameter.

  • Type:
type Routes = Array<{
  entryName: string;
  pathname: string;
}>;

function OnAfterStartDevServer(
  callback: (params: {
    port: number;
    routes: Routes;
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterStartDevServer(({ port, routes }) => {
      console.log('this port is: ', port);
      console.log('this routes is: ', routes);
    });
  },
});

onAfterEnvironmentCompile

A callback function that is triggered after the compilation of a single environment. You can access the build result information via the stats parameter.

Moreover, you can use isWatch to determine whether it is dev or build watch mode, and use isFirstCompile to determine whether it is the first build.

  • Type:
function OnAfterEnvironmentCompile(
  callback: (params: {
    isFirstCompile: boolean;
    isWatch: boolean;
    stats?: Stats;
    environment: EnvironmentContext;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterEnvironmentCompile(({ isFirstCompile }) => {
      if (isFirstCompile) {
        console.log('first compile!');
      } else {
        console.log('re-compile!');
      }
    });
  },
});

onDevCompileDone

Called after each development mode build, you can use isFirstCompile to determine whether it is the first build.

  • Type:
function OnDevCompileDone(
  callback: (params: {
    isFirstCompile: boolean;
    stats: Stats | MultiStats;
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onDevCompileDone(({ isFirstCompile }) => {
      if (isFirstCompile) {
        console.log('first compile!');
      } else {
        console.log('re-compile!');
      }
    });
  },
});

onCloseDevServer

Called when closing the dev server. Can be used to perform cleanup operations when the dev server is closed.

  • Type:
function onCloseDevServer(callback: () => Promise<void> | void): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onCloseDevServer(async () => {
      console.log('close dev server!');
    });
  },
});

Preview hooks

onBeforeStartProdServer

Called before starting the production preview server.

  • Type:
function OnBeforeStartProdServer(callback: () => Promise<void> | void): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onBeforeStartProdServer(() => {
      console.log('before start!');
    });
  },
});

onAfterStartProdServer

Called after starting the production preview server, you can get the port number with the port parameter, and the page routes info with the routes parameter.

  • Type:
type Routes = Array<{
  entryName: string;
  pathname: string;
}>;

function OnAfterStartProdServer(
  callback: (params: {
    port: number;
    routes: Routes;
    environments: Record<string, EnvironmentContext>;
  }) => Promise<void> | void,
): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onAfterStartProdServer(({ port, routes }) => {
      console.log('this port is: ', port);
      console.log('this routes is: ', routes);
    });
  },
});

Other hooks

onExit

Called when the process is going to exit, this hook can only execute synchronous code.

  • Type:
function OnExit(callback: () => void): void;
  • Example:
const myPlugin = () => ({
  setup(api) {
    api.onExit(() => {
      console.log('exit!');
    });
  },
});