本文档详细介绍了 Modern.js CLI 插件的 API。CLI 插件允许您在 Modern.js 项目的构建和开发过程中扩展和定制功能。
CLI 插件需要通过 modern.config.ts 中的 plugins 字段配置。
一个典型的 CLI 插件结构如下:
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;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 | 项目产物输出目录的绝对路径 | modifyResolvedConfig 之后 |
sharedDirectory | string | 公共模块目录的绝对路径 | - |
nodeModulesDirectory | string | node_modules 目录的绝对路径 | - |
ip | string | 当前机器的 IPv4 地址 | - |
packageName | string | 项目 package.json 中的 name 字段 | - |
plugins | object[] | 当前已注册的插件列表 | - |
entrypoints | object[] | 页面入口信息 | - |
serverRoutes | object[] | 服务端路由信息 | - |
bundlerType | webpack | rspack | 当前使用的打包工具类型 (webpack 或 rspack) | onPrepare 之后 |
metaName | string | 框架内部名称 | - |
apiDirectory | string | API 模块目录的绝对路径 (BFF 使用) | - |
lambdaDirectory | string | Lambda 模块目录的绝对路径 (BFF 使用) | - |
runtimeConfigFile | string | 运行时配置文件的名称 | - |
checkedEntries | string[] | 指定的入口信息 | - |
apiOnly | boolean | 是否为 apiOnly 模式 | - |
api.onPrepare(() => {
const appContext = api.getAppContext();
console.log(`当前项目运行在 ${appContext.isProd ? '生产' : '开发'} 模式`);
console.log(`打包工具: ${appContext.bundlerType}`);
});
getAppContext 返回的上下文信息是只读的,无法直接进行修改。
api.getConfig获取用户在 modern.config.ts 文件中定义的配置。
api.onPrepare(() => {
const userConfig = api.getConfig();
if (userConfig.output) {
console.log('用户自定义了 output 配置');
}
});api.getNormalizedConfig获取经过 Modern.js 内部处理和插件修改后的最终配置(归一化配置)。
modifyResolvedConfig 钩子之后使用。api.modifyResolvedConfig(resolvedConfig => {
// ... 修改配置 ...
return resolvedConfig;
});
api.onBeforeBuild(() => {
const finalConfig = api.getNormalizedConfig();
console.log('最终构建配置:', finalConfig);
});api.isPluginExists检查指定的插件是否已注册。
pluginName: string: 要检查的插件名称。boolean 值,表示插件是否存在。if (api.isPluginExists('@modern-js/plugin-bff')) {
console.log('BFF 插件已启用');
}api.getHooks获取所有已注册的钩子函数。
const hooks = api.getHooks();
// 手动触发 onPrepare 钩子
hooks.onPrepare.call();在自定义插件中,只能手动调用对应插件注册的钩子,不能调用官方钩子,以免影响正常应用的执行顺序。
api.config修改 Modern.js 的初始配置。
api.config(configFn: () => UserConfig | Promise<UserConfig>)configFn: 一个返回配置对象或 Promise 的函数。modern.config.ts 中的配置之后。api.config(() => {
return {
output: {
disableTsChecker: true, // 关闭 TypeScript 类型检查
},
};
});配置合并优先级(从高到低):
modern.config.* 文件中定义的配置。api.config() 注册的配置。api.modifyBundlerChain使用 chain API 修改 webpack 或者 Rspack 配置。
api.modifyBundlerChain(modifyFn: (chain: WebpackChain | RspackChain, utils: WebpackUtils | RspackUtils) => void | Promise<void>)modifyFn: 修改函数,接收 webpack-chain 或 RspackChain 实例和实用工具作为参数。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 或不返回(直接修改原对象)。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 或不返回(直接修改原对象)。api.modifyRspackConfig((config, utils) => {
config.builtins.minify = {
enable: true,
implementation: utils.rspack.SwcJsMinimizerRspackPlugin,
}
});api.modifyWebpackChain使用 webpack-chain 修改 Webpack 配置。(当使用 Webpack 作为打包工具时)
api.modifyWebpackChain(modifyFn: (chain: WebpackChain, utils: WebpackUtils) => void | Promise<void>)modifyFn: 修改函数,接收 webpack-chain 实例和实用工具作为参数。api.modifyWebpackChain((chain, utils) => {
// 添加一个自定义的 Webpack loader
chain.module
.rule('my-loader')
.test(/\.my-ext$/)
.use('my-loader')
.loader(require.resolve('./my-loader'));
});api.modifyWebpackConfig直接修改 Webpack 配置对象。(当使用 Webpack 作为打包工具时)
api.modifyWebpackConfig(modifyFn: (config: WebpackConfig, utils: WebpackUtils) => WebpackConfig | Promise<WebpackConfig> | void)modifyFn: 修改函数,接收 Webpack 配置对象和实用工具作为参数,可以返回修改后的配置对象、Promise 或不返回(直接修改原对象)。api.modifyWebpackConfig((config, utils) => {
// 禁用 source map
config.devtool = false;
});构建配置修改顺序
modifyRsbuildConfig
modifyBundlerChain
tools.bundlerChain
modifyRspackConfig
tools.rspackmodifyBundlerChain
tools.bundlerChain
modifyWebpackChain
tools.webpackChain
modifyWebpackConfig
tools.webpackapi.modifyServerRoutes修改服务器路由配置。
api.modifyServerRoutes(transformFn: (routes: ServerRoute[]) => ServerRoute[])transformFn: 转换函数,接收当前服务器路由数组作为参数,返回修改后的数组。prepare 阶段)。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三个方法。prepare 阶段)。api.modifyHtmlPartials(({ entrypoint, partials }) => {
// 在所有页面的 <head> 标签中添加一个 meta 标签
if (partials.head && partials.head.append) {
partials.head.append(`<meta name="my-custom-meta" content="value">`);
}
});当使用完全自定义模板时,该钩子函数将不会执行。
api.onPrepare在 Modern.js 准备阶段添加额外的逻辑。
api.onPrepare(prepareFn: () => void | Promise<void>)prepareFn: 准备函数,无参数,可异步。api.onPrepare(async () => {
// 执行一些初始化操作,例如检查环境、下载依赖等
await prepareMyCustomEnvironment();
});api.addCommand添加自定义的 CLI 命令。
api.addCommand(commandFn: ({ program: Command }) => void)commandFn: 接收 commander 的 program 对象作为参数,用于定义新的命令。prepare 钩子运行完成后。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 钩子运行完成之后。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: 是否为框架内部文件。api.onFileChanged(({ filename, eventType }) => {
if (eventType === 'change' && filename.endsWith('.ts')) {
console.log('TypeScript 文件发生变化,可能需要重新编译...');
}
});api.onBeforeBuild在构建开始之前添加额外的逻辑。
api.onBeforeBuild(buildFn: () => void | Promise<void>)buildFn: 构建前执行的函数,无参数,可异步。api.onBeforeBuild(() => {
// 构建前做一些环境检查
});api.onAfterBuild在构建完成后添加额外的逻辑。
api.onAfterBuild(buildFn: () => void | Promise<void>)buildFn: 构建后执行的函数,无参数,可异步。api.onAfterBuild(() => {
// 构建后上传sourceMap
});api.onDevCompileDone在开发服务器编译完成后添加额外的逻辑。
api.onDevCompileDone(compileFn: () => void | Promise<void>)compileFn: 编译完成后执行的函数。api.onDevCompileDone(() => {
// 首次编译完成,打开浏览器
});api.onBeforeCreateCompiler在创建编译器实例之前添加额外的逻辑。
api.onBeforeCreateCompiler(createFn: () => void | Promise<void>)createFn: 创建前执行的函数,无参数,可异步。api.onBeforeCreateCompiler(() => {
// 可以获取 compiler 相关配置
});api.onAfterCreateCompiler在创建编译器实例之后添加额外的逻辑。
api.onAfterCreateCompiler(createFn: () => void | Promise<void>)createFn: 创建后执行的函数,无参数,可异步。api.onAfterCreateCompiler(() => {
// 可以获取 compiler 实例
});api.onBeforeDev在开发服务器启动前添加额外的逻辑。
api.onBeforeDev(devFn: () => void | Promise<void>)devFn: 开发服务器启动前执行的函数,无参数,可异步。dev 命令启动开发服务器之前。api.onBeforeDev(async () => {
// 检查端口是否被占用
await checkPortAvailability(3000);
});api.onAfterDev在开发服务器启动后添加额外的逻辑。
api.onAfterDev(devFn: () => void | Promise<void>)devFn: 开发服务器启动后执行的函数。api.onAfterDev(() => {
// 上报 dev 相关信息
});api.onBeforeExit在进程退出前添加额外的逻辑。
api.onBeforeExit(exitFn: () => void | Promise<void>)exitFn: 进程退出前执行的函数,无参数,可异步。api.onBeforeExit(async () => {
// 执行一些清理操作,例如关闭数据库连接、删除临时文件等
await cleanupMyResources();
});api.onBeforePrintInstructions在打印成功信息前添加额外的逻辑。
api.onBeforePrintInstructions(printFn: ({instructions: string}) => {instructions: string} | Promise<{instructions: string}>)printFn: 修改打印信息的函数, 返回修改后的信息。api.onBeforePrintInstructions(({ instructions }) => {
// do something
return { instructions };
});