> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 插件开发

插件系统是 Rsbuild 架构的核心，Rsbuild 的大部分功能都是通过插件实现的，这种设计让核心保持轻量，同时提供了灵活的扩展性。

Rsbuild 插件是一个函数，它可以在不同阶段注册钩子，监听事件并执行自定义逻辑。无论你想要修改默认行为、添加新功能，还是集成第三方工具，插件都提供了丰富的 API 来实现这些需求。

## 对比其他插件

在开发 Rsbuild 插件之前，你可能已经接触过 webpack、Vite、esbuild 等工具的插件系统。

总体而言，Rsbuild 的插件 API 和 esbuild 相似，与 webpack 或 Rspack 插件相比，Rsbuild 的插件 API 更加简洁和容易上手。

```ts
// esbuild plugin
const esbuildPlugin = {
  name: 'example',
  setup(build) {
    build.onEnd(() => console.log('done'));
  },
};

// Rsbuild plugin
const rsbuildPlugin = () => ({
  name: 'example',
  setup(api) {
    api.onAfterBuild(() => console.log('done'));
  },
});

// Rspack plugin
class RspackExamplePlugin {
  apply(compiler) {
    compiler.hooks.done.tap('RspackExamplePlugin', () => {
      console.log('done');
    });
  }
}
```

从功能上看，Rsbuild 的插件 API 主要围绕 Rsbuild 的运行流程和构建配置，并提供一些 hooks 用于扩展。而 Rspack 的插件 API 则更加复杂和丰富，能够修改打包过程的每一个环节。

Rsbuild 插件中可以集成 Rspack 插件，如果 Rsbuild 提供的 hooks 无法满足你的需求，你也可以通过 Rspack 插件来实现功能，并在 Rsbuild 插件中注册 Rspack 插件：

```ts
const rsbuildPlugin = () => ({
  name: 'example',
  setup(api) {
    api.modifyRspackConfig((config) => {
      config.plugins.push(new RspackExamplePlugin());
    });
  },
});
```

## 开发插件

插件提供类似 `(options?: PluginOptions) => RsbuildPlugin` 的函数作为入口。

### 插件示例

```ts title="pluginFoo.ts"
import type { RsbuildPlugin } from '@rsbuild/core';

export type PluginFooOptions = {
  message?: string;
};

export const pluginFoo = (options: PluginFooOptions = {}): RsbuildPlugin => ({
  name: 'plugin-foo',

  setup(api) {
    api.onAfterStartDevServer(() => {
      const msg = options.message || 'hello!';
      console.log(msg);
    });
  },
});
```

注册插件：

```ts title="rsbuild.config.ts"
import { pluginFoo } from './pluginFoo';

export default {
  plugins: [pluginFoo({ message: 'world!' })],
};
```

### 插件结构

函数形式的插件可以 **接受选项对象** 并 **返回插件实例**，并通过闭包机制管理内部状态。

其中各部分的作用分别为：

- `name` 属性用于标注插件名称。
- `setup` 作为插件逻辑的主入口。
- `api` 对象包含了各类钩子和工具函数。

### 命名规范

插件的命名规范如下：

- 插件的函数命名为 `pluginAbc`，并通过具名导出。
- 插件的 `name` 采用 `scope:foo-bar` 或 `plugin-foo-bar` 格式，添加 `scope:` 可以避免和其他插件产生命名冲突。

下面是一个例子：

```ts title="pluginFooBar.ts"
import type { RsbuildPlugin } from '@rsbuild/core';

export const pluginFooBar = (): RsbuildPlugin => ({
  name: 'scope:foo-bar',
  setup() {},
});
```

:::tip
Rsbuild 官方插件的 `name` 统一使用 `rsbuild:` 作为前缀，比如 `rsbuild:react` 对应 `@rsbuild/plugin-react`。
:::

### 模板仓库

[rsbuild-plugin-template](https://github.com/rstackjs/rsbuild-plugin-template) 是一个最小的 Rsbuild 插件模板仓库，你可以基于该仓库来开发你的 Rsbuild 插件。

### Environment 插件 \{#environment-plugin}

Rsbuild 支持同时为多个环境构建产物，并支持某个插件[仅在指定环境下运行](/zh/guide/advanced/environments.md#plugins-specified-environment)。

当你希望你开发的插件支持作为 Environment 插件使用时，需要注意以下几点：

1. 每个 environment 有自身的 Rsbuild 配置：
   - 使用 [environment 上下文](/zh/guide/advanced/environments.md#environment-context) 代替 `getRsbuildConfig` 获取 environment 信息。
   - 修改特定 environment 的 Rsbuild 配置时，优先使用 [modifyEnvironmentConfig](/zh/plugins/dev/hooks.md#modifyenvironmentconfig) 代替 [modifyRsbuildConfig](/zh/plugins/dev/hooks.md#modifyrsbuildconfig) ，以避免对其他 environments 产生影响。
2. 避免副作用，你的插件代码可能执行多次：
   - 当同一个插件在不同环境下注册多次时，会被视为多个 Rsbuild 插件（哪怕它们指向同一个插件实例），这是因为它们带有不同的 Rsbuild environment 上下文。

下面是一个 Environment 插件例子：

```ts title="pluginFoo.ts"
import type { RsbuildPlugin } from '@rsbuild/core';

export type PluginFooOptions = {
  title?: string;
};

export const pluginFoo = (options: PluginFooOptions = {}): RsbuildPlugin => ({
  name: 'plugin-foo',

  setup(api) {
    api.modifyEnvironmentConfig((config) => {
      config.html.title = options.title || 'My Default Title';
    });
    api.modifyBundlerChain((chain, { environment }) => {
      chain.name(environment.config.html.title);
    });
  },
});
```

### 引用其他插件

Rsbuild 的 [plugins](/zh/config/plugins.md) 配置项支持传入一个嵌套的数组，这意味着你可以通过这种方式在插件内部引用其他 Rsbuild 插件。

例如，在 `pluginFoo` 内部引用并注册 `pluginBar`：

```ts
import { pluginBar } from 'rsbuild-plugin-bar';

export const pluginFoo = (): RsbuildPlugin => {
  const foo = {
    name: 'plugin-foo',
    setup(api) {
      // ...
    },
  };
  return [foo, pluginBar()];
};
```

## 生命周期钩子

Rsbuild 在内部按照约定的生命周期进行任务调度，插件可以通过注册钩子来介入工作流程的任意阶段，并实现自己的功能。

Rsbuild 生命周期钩子的完整列表参考 [API 文档](/zh/plugins/dev/hooks.md)。

Rsbuild 不会接管底层 Rspack 的生命周期，相关生命周期钩子的使用方式见对应文档：[Rspack Plugin API](https://rspack.rs/zh/api/plugin-api/)。

## 迁移 Vite 插件

参考 [迁移 Vite 插件](/zh/guide/migration/vite-plugin.md) 了解如何迁移一个 Vite 插件到 Rsbuild 插件。

## 读写 Rsbuild 配置 \{#read-and-modify-rsbuild-config}

当插件需要读取或修改项目的 Rsbuild 配置时，可以使用 Rsbuild 提供的配置 API。

### 修改基础配置 \{#modify-the-base-config}

在 `setup` 中注册 [api.modifyRsbuildConfig](/zh/plugins/dev/hooks.md#modifyrsbuildconfig)，可以在基础配置与各个 environment 的配置合并前对其进行修改：

```ts
api.modifyRsbuildConfig((config) => {
  config.output.minify = false;
});
```

`modifyRsbuildConfig` 是全局 hook。如果修改仅针对部分 environment，或需要根据当前 environment 调整配置，建议改用 [api.modifyEnvironmentConfig](/zh/plugins/dev/hooks.md#modifyenvironmentconfig)。详细说明请参考[全局 hooks 与 environment hooks](/zh/plugins/dev/hooks.md#global-hooks-vs-environment-hooks)。

### 读取规范化后的配置 \{#read-the-normalized-config}

配置修改 hooks 执行完毕后，可以无参数调用 [api.getNormalizedConfig](/zh/plugins/dev/core.md#apigetnormalizedconfig)，获取包含所有 environment 的完整配置。该配置已经过规范化处理并包含默认值，类型也比 [api.getRsbuildConfig](/zh/plugins/dev/core.md#apigetrsbuildconfig) 的返回值更明确。

```ts
api.onBeforeBuild(() => {
  const config = api.getNormalizedConfig();
  console.log(Object.keys(config.environments));
});
```

如果当前没有 environment context，但需要读取某个 environment 的配置，可以将其名称传给 `getNormalizedConfig`：

```ts
api.onBeforeBuild(() => {
  const config = api.getNormalizedConfig({ environment: 'web' });
  console.log(config.output.target);
});
```

返回值类型请参考 [NormalizedConfig](/zh/api/javascript-api/types.md#normalizedconfig) 和 [NormalizedEnvironmentConfig](/zh/api/javascript-api/types.md#normalizedenvironmentconfig)。

### 读取当前环境的配置 \{#current-environment-config}

当 hook 的回调参数中包含 [environment context](/zh/api/javascript-api/environment-api.md#environment-context) 时，建议通过 `environment.config` 获取配置。该配置由基础配置与[当前 environment 的配置](/zh/guide/advanced/environments.md)合并并经过规范化处理后得到。

```ts
api.onBeforeEnvironmentCompile(({ environment }) => {
  const { name, config } = environment;
  console.log(`${name}: ${config.output.target}`);
});
```

### 读取所有环境的配置 \{#read-all-environment-configs}

[onBeforeBuild](/zh/plugins/dev/hooks.md#onbeforebuild) 和 [onAfterBuild](/zh/plugins/dev/hooks.md#onafterbuild) 等全局 hooks 会提供 `environments`，其中包含所有 environment 的上下文。当插件需要读取每个 environment 的配置时，可以遍历该对象：

```ts
api.onBeforeBuild(({ environments }) => {
  for (const { name, config } of Object.values(environments)) {
    console.log(`${name}: ${config.output.distPath.root}`);
  }
});
```

多环境配置的详细说明请参考[多环境构建](/zh/guide/advanced/environments.md)。

## 修改 Rspack 配置

Rsbuild 插件允许你修改内置的 Rspack 配置，包括：

- [api.modifyRspackConfig](/zh/plugins/dev/hooks.md#modifyrspackconfig)：修改 Rspack 配置对象。
- [api.modifyBundlerChain](/zh/plugins/dev/hooks.md#modifybundlerchain) 通过 [rspack-chain](https://github.com/rstackjs/rspack-chain) 来修改 Rspack 配置。

### 示例

比如，通过 Rsbuild 插件来注册 [eslint-rspack-plugin](https://github.com/rstackjs/eslint-rspack-plugin)：

```ts
import type { RsbuildPlugin } from '@rsbuild/core';
import ESLintRspackPlugin from 'eslint-rspack-plugin';

export const pluginEslint = (options?: Options): RsbuildPlugin => ({
  name: 'plugin-eslint',
  setup(api) {
    api.modifyRspackConfig((config) => {
      config.plugins.push(
        new ESLintRspackPlugin({
          // plugins options
        }),
      );
    });
  },
});
```

## 扩展插件 API

当你基于 Rsbuild 的 [JavaScript API](/zh/api/start/index.md) 来实现自定义的工具时，可能希望在现有插件 API 的基础上，提供更多能力，例如添加工具方法或共享上下文对象。

此时，你可以使用 Rsbuild 实例上的 [rsbuild.expose()](/zh/api/javascript-api/instance.md#rsbuildexpose) 方法。它的作用与插件的 [api.expose()](/zh/plugins/dev/core.md#apiexpose) 一致，用于向 Rsbuild 插件暴露自定义的方法或对象。

例如，向插件暴露 `getState` 和 `setCount` 方法：

```ts title="myToolkit.ts"
import { createRsbuild } from '@rsbuild/core';

export const MY_TOOLKIT_ID = 'my-toolkit';

const rsbuild = await createRsbuild({
  // ...
});

const state = {
  count: 0,
};

rsbuild.expose(MY_TOOLKIT_ID, {
  getState() {
    return state;
  },
  setCount(count: number) {
    state.count = count;
  },
});
```

然后，插件可以通过 [api.useExposed()](/zh/plugins/dev/core.md#apiuseexposed) 方法访问这些扩展 API：

```ts title="myPlugin.ts"
import { MY_TOOLKIT_ID } from './myToolkit';

const myPlugin = {
  name: 'my-plugin',
  setup(api) {
    const toolkitApi = api.useExposed(MY_TOOLKIT_ID);
    if (toolkitApi) {
      const { count } = toolkitApi.getState();
      toolkitApi.setCount(count + 1);
    }
  },
};
```

## 依赖声明

发布 Rsbuild 插件时，应该在 `package.json` 中声明 `@rsbuild/core` 的 `peerDependencies`，并在 `devDependencies` 中安装它用于开发：

```json
{
  "peerDependencies": {
    "@rsbuild/core": "^2.0.0"
  },
  "devDependencies": {
    "@rsbuild/core": "^2.0.0"
  }
}
```

如果插件只引用了 `@rsbuild/core` 的类型导出，可以将其声明为 optional peer dependency：

```json
{
  "peerDependencies": {
    "@rsbuild/core": "^2.0.0"
  },
  "peerDependenciesMeta": {
    "@rsbuild/core": {
      "optional": true
    }
  }
}
```

这种情况下，插件在被基于 Rsbuild 的上层工具（如 Rslib 或 Rspress）使用时，不会产生不必要的 peer dependency 警告。
