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

# 插件 API

Modern.js 的 CLI 插件允许您在 Modern.js 项目的构建和开发过程中扩展和定制功能。

:::info

CLI 插件需要通过 `modern.config.ts` 中的 [`plugins`](/zh/configure/app/plugins.md) 字段配置。

:::

## 插件基础结构

一个典型的 CLI 插件结构如下：

```typescript
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

const myCliPlugin = (): CliPlugin<AppTools> => ({
  name: '@my-org/my-plugin', // 插件名称，确保唯一性
  setup: api => {
    // 在这里使用 API 注册钩子、添加命令等
    api.onBeforeBuild(() => {
      console.log('构建即将开始...');
    });
  },
});

export default myCliPlugin;
```

- `name`： 插件的唯一标识符。
- `setup` 函数接收一个 `api` 对象，该对象提供了所有可用的 CLI 插件 API。

## 信息获取

#### `api.getAppContext`

获取 Modern.js 应用的上下文信息。

- **返回值：** `AppContext` 对象，包含以下字段：

| 字段名                    | 类型         | 描述                                       | 何时可用             |
| ---------------------- | ---------- | ---------------------------------------- | ---------------- |
| `command`              | `string`   | 当前执行的命令 (e.g.， `dev`， `build`， `deploy`) | -                |
| `port`                 | `number`   | 开发服务器端口号                                 | `onPrepare` 之后   |
| `configFile`           | `string`   | 配置文件的绝对路径                                | -                |
| `isProd`               | `boolean`  | 是否为生产模式                                  | -                |
| `appDirectory`         | `string`   | 项目根目录的绝对路径                               | -                |
| `srcDirectory`         | `string`   | 项目源码目录的绝对路径                              | -                |
| `distDirectory`        | `string`   | 项目产物输出目录的绝对路径                            | `onPrepare`（及之后） |
| `sharedDirectory`      | `string`   | 公共模块目录的绝对路径                              | -                |
| `nodeModulesDirectory` | `string`   | `node_modules` 目录的绝对路径                   | -                |
| `ip`                   | `string`   | 当前机器的 IPv4 地址                            | -                |
| `packageName`          | `string`   | 项目 `package.json` 中的 `name` 字段           | -                |
| `plugins`              | `object[]` | 当前已注册的插件列表                               | -                |
| `entrypoints`          | `object[]` | 页面入口信息                                   | -                |
| `serverRoutes`         | `object[]` | 服务端路由信息                                  | -                |
| `bundlerType`          | `rspack`   | 当前使用的打包工具类型                              | `onPrepare` 之后   |
| `metaName`             | `string`   | 框架内部名称                                   | -                |
| `apiDirectory`         | `string`   | API 模块目录的绝对路径 (BFF 使用)                   | -                |
| `lambdaDirectory`      | `string`   | Lambda 模块目录的绝对路径 (BFF 使用)                | -                |
| `runtimeConfigFile`    | `string`   | 运行时配置文件的名称                               | -                |
| `checkedEntries`       | `string[]` | 指定的入口信息                                  | -                |
| `apiOnly`              | `boolean`  | 是否为 `apiOnly` 模式                         | -                |

- **示例：**

```typescript
api.onPrepare(() => {
  const appContext = api.getAppContext();
  console.log(`当前项目运行在 ${appContext.isProd ? '生产' : '开发'} 模式`);
  console.log(`打包工具： ${appContext.bundlerType}`);
});
```

:::info
`getAppContext` 返回的上下文信息是只读的，无法直接进行修改。
:::

***

#### `api.getConfig`

获取用户在 `modern.config.ts` 文件中定义的配置。

- **返回值：** 用户定义的配置对象。
- **示例：**

```typescript
api.onPrepare(() => {
  const userConfig = api.getConfig();
  if (userConfig.output) {
    console.log('用户自定义了 output 配置');
  }
});
```

***

#### `api.getNormalizedConfig`

获取经过 Modern.js 内部处理和插件修改后的最终配置（归一化配置）。

- **返回值：** 归一化后的配置对象。
- **何时可用：** 必须在 `onPrepare`（及之后的 hook，例如 `onBeforeBuild`）中使用。
- **示例：**

```typescript
api.onPrepare(() => {
  const finalConfig = api.getNormalizedConfig();
  console.log('最终配置：', finalConfig);
});
```

***

#### `api.isPluginExists`

检查指定的插件是否已注册。

- **参数：**
  - `pluginName: string`： 要检查的插件名称。
- **返回值：** `boolean` 值，表示插件是否存在。
- **示例：**

```typescript
if (api.isPluginExists('@modern-js/plugin-bff')) {
  console.log('BFF 插件已启用');
}
```

***

#### `api.getHooks`

获取所有已注册的钩子函数。

- **返回值：** 包含所有钩子函数的对象。
- **示例：**

```typescript
const hooks = api.getHooks();
// 手动触发 onPrepare 钩子
hooks.onPrepare.call();
```

:::warning
在自定义插件中，只能手动调用对应插件注册的钩子，不能调用官方钩子，以免影响正常应用的执行顺序。
:::

***

## 配置修改

#### `api.config`

修改 Modern.js 的初始配置。

- **类型：** `api.config(configFn: () => UserConfig | Promise<UserConfig>)`
- **参数：**
  - `configFn`： 一个返回配置对象或 Promise 的函数。
- **执行阶段：** 解析完 `modern.config.ts` 中的配置之后。
- **示例：**

```typescript
api.config(() => {
  return {
    output: {
      disableTsChecker: true, // 关闭 TypeScript 类型检查
    },
  };
});
```

**配置合并优先级**（从高到低）：

1. 用户在 `modern.config.*` 文件中定义的配置。
2. 插件通过 `api.config()` 注册的配置。
3. Modern.js 默认配置。

***

#### `api.modifyBundlerChain`

使用 chain API 修改 Rspack 配置。

- **类型：** `api.modifyBundlerChain(modifyFn: (chain: RspackChain, utils: RspackUtils) => void | Promise<void>)`
- **参数：**
  - `modifyFn`： 修改函数，接收 `RspackChain` 实例和实用工具作为参数。
- **执行阶段：** 在生成最终的 Rspack 配置时。
- **对应 Rsbuild Hook**： [modifybundlerchain](https://v2.rsbuild.rs/zh/plugins/dev/hooks#modifybundlerchain)
- **示例：**

```typescript
api.modifyBundlerChain((chain, utils) => {
  if (utils.env === 'development') {
    chain.devtool('eval');
  }
  chain.plugin('bundle-analyze').use(BundleAnalyzerPlugin);
});
```

#### `api.modifyRsbuildConfig`

修改 Rsbuild 的配置。

- **类型：** `api.modifyRsbuildConfig(modifyFn: (config: RsbuildConfig, utils: RsbuildUtils) => RsbuildConfig | Promise<RsbuildConfig> | void)`
- **参数：**
  - `modifyFn`： 修改函数，接收 Rsbuild 配置对象和实用工具作为参数，可以返回修改后的配置对象、Promise 或不返回（直接修改原对象）。
- **执行阶段：** 在生成最终的 Rsbuild 配置时。
- **对应 Rsbuild Hook**： [modifyRsbuildConfig](https://v2.rsbuild.rs/zh/plugins/dev/hooks#modifyrsbuildconfig)
- **示例：**

```typescript
api.modifyRsbuildConfig((config, utils) => {
  // 添加一个自定义的 Rsbuild 插件
  config.plugins.push(myCustomRsbuildPlugin());
});
```

***

#### `api.modifyRspackConfig`

修改 Rspack 的配置。(当使用 Rspack 作为打包工具时)

- **类型：** `api.modifyRspackConfig(modifyFn: (config: RspackConfig, utils: RspackUtils) => RspackConfig | Promise<RspackConfig> | void)`
- **参数：**
  - `modifyFn`： 修改函数，接收 Rspack 配置对象和实用工具作为参数，可以返回修改后的配置对象、Promise 或不返回（直接修改原对象）。
- **执行阶段：** 在生成最终的 Rspack 配置时。
- **对应 Rsbuild Hook**： [modifyRspackConfig](https://v2.rsbuild.rs/zh/plugins/dev/hooks#modifyrspackconfig)
- **示例：**

```typescript
api.modifyRspackConfig((config, utils) => {
  config.builtins.minify = {
    enable: true,
    implementation: utils.rspack.SwcJsMinimizerRspackPlugin,
  };
});
```

***

**构建配置修改顺序**

```
modifyRsbuildConfig
modifyBundlerChain
tools.bundlerChain
modifyRspackConfig
tools.rspack
```

***

#### `api.modifyServerRoutes`

修改服务器路由配置。

- **类型** `api.modifyServerRoutes(transformFn: (routes: ServerRoute[]) => ServerRoute[])`
- **参数：**
  - `transformFn`： 转换函数，接收当前服务器路由数组作为参数，返回修改后的数组。
- **执行阶段：** 在生成服务器路由文件之前 ( `prepare` 阶段)。
- **示例：**

```typescript
api.modifyServerRoutes(routes => {
  // 添加一个新的 API 路由
  routes.push({
    urlPath: '/api',
    isApi: true,
    entryPath: '',
    isSPA: false,
    isSSR: false,
  });
  return routes;
});
```

***

#### `api.modifyHtmlPartials`

修改 HTML 模板片段。

- **类型** `api.modifyHtmlPartials(modifyFn: (partials: HtmlPartials, entrypoint: Entrypoint) => void)`
- **参数：**
  - `modifyFn`： 修改函数，接收 HTML 模板片段对象和当前入口点信息作为参数。
    - `partials`： 包含 `top`， `head`， `body` 三个部分，每个部分都有`append`， `prepend`， `replace`三个方法。
- **执行阶段：** 在生成 HTML 文件之前 ( `prepare` 阶段)。
- **示例：**

```typescript
api.modifyHtmlPartials(({ entrypoint, partials }) => {
  // 在所有页面的 <head> 标签中添加一个 meta 标签
  if (partials.head && partials.head.append) {
    partials.head.append(`<meta name="my-custom-meta" content="value">`);
  }
});
```

:::warning
当使用[完全自定义模板](/zh/guides/basic-features/html.md#完全自定义-html-模板)时，该钩子函数将不会执行。
:::

***

## 构建流程控制

#### `api.onPrepare`

在 Modern.js 准备阶段添加额外的逻辑。

- **类型** `api.onPrepare(prepareFn: () => void | Promise<void>)`
- **参数：**
  - `prepareFn`: 准备函数，无参数，可异步。
- **执行阶段：** 在 Modern.js 完成配置校验之后。
- **示例：**

```typescript
api.onPrepare(async () => {
  // 执行一些初始化操作，例如检查环境、下载依赖等
  await prepareMyCustomEnvironment();
});
```

***

#### `api.addCommand`

添加自定义的 CLI 命令。

- **类型** `api.addCommand(commandFn: ({ program: Command }) => void)`
- **参数：**
  - `commandFn`： 接收 `commander` 的 `program` 对象作为参数，用于定义新的命令。
- **执行阶段：** `prepare` 钩子运行完成后。
- **示例：**

```typescript
api.addCommand(({ program }) => {
  program
    .command('my-command')
    .description('我的自定义命令')
    .action(async () => {
      // 执行命令逻辑
      console.log('执行自定义命令...');
    });
});
```

***

#### `api.addWatchFiles`

添加额外的文件监听列表（用于开发模式）。

- **类型** `api.addWatchFiles(watchFn: () => string[] | { files: string[]; isPrivate: boolean; })`
- **参数：**
  - `watchFn`： 返回一个包含文件路径的数组，或一个包含 `files` 和 `isPrivate` 属性的对象。
    - `files`： 要监听的文件路径数组。
    - `isPrivate`： 是否为框架内部文件（影响文件变更时的行为）。
- **执行阶段：** `addCommand` 钩子运行完成之后。
- **示例：**

```typescript
api.addWatchFiles(() => {
  // 监听项目根目录下的 .env 文件
  return [path.resolve(api.getAppContext().appDirectory, '.env')];
});
```

***

#### `api.onFileChanged`

在监听文件发生变化时添加额外的逻辑（用于开发模式）。

- **类型** `api.onFileChanged(changeFn: (params: { filename: string; eventType: 'add' | 'change' | 'unlink'; isPrivate: boolean; }) => void)`
- **参数：**
  - `changeFn`： 文件变化处理函数，接收以下参数：
    - `filename`： 发生变化的文件路径。
    - `eventType`： 变化类型 (`add`， `change`， `unlink`)。
    - `isPrivate`： 是否为框架内部文件。
- **执行阶段：** 监听文件变化之后。
- **示例：**

```typescript
api.onFileChanged(({ filename, eventType }) => {
  if (eventType === 'change' && filename.endsWith('.ts')) {
    console.log('TypeScript 文件发生变化，可能需要重新编译...');
  }
});
```

***

#### `api.onBeforeBuild`

在构建开始之前添加额外的逻辑。

- **类型** `api.onBeforeBuild(buildFn: () => void | Promise<void>)`
- **参数：**
  - `buildFn`： 构建前执行的函数，无参数，可异步。
- **执行阶段：** 在执行构建流程之前。
- **对应 Rsbuild Hook**： [onBeforeBuild](https://v2.rsbuild.rs/zh/plugins/dev/hooks#onbeforebuild)
- **示例：**

```typescript
api.onBeforeBuild(() => {
  // 构建前做一些环境检查
});
```

***

#### `api.onAfterBuild`

在构建完成后添加额外的逻辑。

- **类型：** `api.onAfterBuild(buildFn: () => void | Promise<void>)`
- **参数：**
  - `buildFn`： 构建后执行的函数，无参数，可异步。
- **执行阶段：** 在执行构建流程之后。
- **对应 Rsbuild Hook**： [onAfterBuild](https://v2.rsbuild.rs/zh/plugins/dev/hooks#onafterbuild)
- **示例：**

```typescript
api.onAfterBuild(() => {
  // 构建后上传sourceMap
});
```

***

#### `api.onDevCompileDone`

在开发服务器编译完成后添加额外的逻辑。

- **类型：** `api.onDevCompileDone(compileFn: () => void | Promise<void>)`
- **参数：**
  - `compileFn`： 编译完成后执行的函数。
- **执行阶段：** 开发服务器启动，且首次编译完成后。
- **对应 Rsbuild Hook**： [onDevCompileDone](https://v2.rsbuild.rs/zh/plugins/dev/hooks#ondevcompiledone)
- **示例：**

```typescript
api.onDevCompileDone(() => {
  // 首次编译完成，打开浏览器
});
```

***

#### `api.onBeforeCreateCompiler`

在创建编译器实例之前添加额外的逻辑。

- **类型：** `api.onBeforeCreateCompiler(createFn: () => void | Promise<void>)`
- **参数：**
  - `createFn`： 创建前执行的函数，无参数，可异步。
- **执行阶段：** 在创建 Rspack 编译器实例之前。
- **对应 Rsbuild Hook**： [onBeforeCreateCompiler](https://v2.rsbuild.rs/zh/plugins/dev/hooks#onbeforecreatecompiler)
- **示例：**

```typescript
api.onBeforeCreateCompiler(() => {
  // 可以获取 compiler 相关配置
});
```

***

#### `api.onAfterCreateCompiler`

在创建编译器实例之后添加额外的逻辑。

- **类型：** `api.onAfterCreateCompiler(createFn: () => void | Promise<void>)`
- **参数：**
  - `createFn`： 创建后执行的函数，无参数，可异步。
- **执行阶段：** 在创建 Rspack 编译器实例之后。
- **对应 Rsbuild Hook**： [onAfterCreateCompiler](https://v2.rsbuild.rs/zh/plugins/dev/hooks#onaftercreatecompiler)
- **示例：**

```typescript
api.onAfterCreateCompiler(() => {
  // 可以获取 compiler 实例
});
```

***

#### `api.onBeforeDev`

在开发服务器启动前添加额外的逻辑。

- **类型：** `api.onBeforeDev(devFn: () => void | Promise<void>)`
- **参数：**
  - `devFn`： 开发服务器启动前执行的函数，无参数，可异步。
- **执行阶段：** 在执行 `dev` 命令启动开发服务器之前。
- **示例：**

```typescript
api.onBeforeDev(async () => {
  // 检查端口是否被占用
  await checkPortAvailability(3000);
});
```

***

#### `api.onAfterDev`

在开发服务器启动后添加额外的逻辑。

- **类型：** `api.onAfterDev(devFn: () => void | Promise<void>)`
- **参数：**
  - `devFn`： 开发服务器启动后执行的函数。
- **执行阶段：** 开发服务器成功启动后。
- **对应 Rsbuild Hook**： [onAfterStartDevServer](https://v2.rsbuild.rs/zh/plugins/dev/hooks#onafterstartdevserver)
- **示例：**

```typescript
api.onAfterDev(() => {
  // 上报 dev 相关信息
});
```

***

#### `api.onBeforeExit`

在进程退出前添加额外的逻辑。

- **类型：** `api.onBeforeExit(exitFn: () => void | Promise<void>)`
- **参数：**
  - `exitFn`： 进程退出前执行的函数，无参数，可异步。
- **执行阶段：** 在 Modern.js 进程即将退出时（例如，用户按下 Ctrl+C）。
- **示例：**

```typescript
api.onBeforeExit(async () => {
  // 执行一些清理操作，例如关闭数据库连接、删除临时文件等
  await cleanupMyResources();
});
```

***

#### `api.onBeforePrintInstructions`

在打印成功信息前添加额外的逻辑。

- **类型：** `api.onBeforePrintInstructions(printFn: ({instructions: string}) => {instructions: string} | Promise<{instructions: string}>)`
- **参数：**
  - `printFn`： 修改打印信息的函数， 返回修改后的信息。
- **执行阶段：** 打印成功信息前。
- **示例：**

```typescript
api.onBeforePrintInstructions(({ instructions }) => {
  // do something
  return { instructions };
});
```

## 其他说明

- 可以参考 [CLI 插件生命周期](/zh/plugin/cli-plugins/life-cycle.md) 了解插件钩子的执行顺序。
