如何编写插件
上一小节介绍了 Modern.js 插件的 Hook 模型,这一小节介绍如何编写插件。
实现插件
Modern.js 插件是一个对象,对象包含以下属性:
name
: 插件的名称,唯一标识符。
setup
: 插件初始化函数,只会执行一次。setup 函数可以返回一个 Hooks 对象,Modern.js 会在特定的时机执行这些 Hooks。
const myPlugin = {
name: 'my-plugin',
setup() {
// 执行一些初始化逻辑
const foo = '1';
// 返回一个 Hooks 对象
return {
afterBuild: () => {
// 在构建完成后执行逻辑
},
};
},
};
另外,在插件中,允许配置与其他插件的执行顺序。详情可以参考插件关系。
插件类型
Modern.js 支持多种工程开发,如应用开发(Modern.js Framework), 模块开发(Modern.js Module)等。
为了兼顾不同工程开发的差异和通性,Modern.js 将插件如下图进行组织:
从图可以看出,Modern.js 将插件大致分为两类:
-
通用插件: 插件只会包含一些基础的 Hooks
-
工程插件: 不同的工程开发会在通用插件的基础上扩展出自己的 Hooks, Config 等类型。
使用 TypeScript 时,可以引入内置的 CliPlugin
等类型,为插件提供正确的类型推导。
import type { CliPlugin } from '@modern-js/core';
const myPlugin: CliPlugin = {
name: 'my-plugin',
setup() {
const foo = '1';
return {
afterBuild: () => {
// 在构建完成后执行逻辑
},
};
},
};
上述代码为通用插件,只包含一些基础的 Hooks。 Modern.js 支持通过泛型对插件的定义进行扩展:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';
const myPlugin: CliPlugin<AppTools> = {
name: 'my-plugin',
setup() {
const foo = '1';
return {
afterBuild: () => {
// 在构建完成后执行逻辑
},
};
},
};
如果仔细观察 AppTools
这个类型,可以发现 AppTools
由 3 种类型构成.
type AppTools = {
hooks: AppToolsHooks;
userConfig: AppToolsUserConfig;
normalizedConfig: AppToolsNormalizedConfig;
};
当编写插件时,插件通过泛型扩展在不同的基础上扩展自己的 Hooks 等类型:
// 通用插件上扩展
import type { CliPlugin } from '@modern-js/core';
import type { MyPluginHook } from 'xxx';
const myPlugin: CliPlugin<{ hooks: MyPluginHook }> = {};
// 在 @modern-js/app-tools 基础上扩展
import type { CliPlugin, AppTools } from '@modern-js/app-tools';
import type { MyPluginHook } from 'xxx';
const myPlugin: CliPlugin<AppTools & { hooks: MyPluginHook }> = {};
详细说明,请参考 扩展 Hook。
插件配置项
建议将插件写成函数的形式,使插件能通过函数入参来接收配置项:
import type { CliPlugin } from '@modern-js/core';
type MyPluginOptions = {
foo: string;
};
const myPlugin = (options: MyPluginOptions): CliPlugin => ({
name: 'my-plugin',
setup() {
console.log(options.foo);
},
});
插件 API
插件的 setup
函数会接收一个 api 入参,你可以调用 api 上提供的一些方法来获取到配置、应用上下文等信息。
import type { CliPlugin } from '@modern-js/core';
export const myPlugin = (): CliPlugin => ({
name: 'my-plugin',
setup(api) {
// 获取应用原始配置
const config = api.useConfigContext();
// 获取应用运行上下文
const appContext = api.useAppContext();
// 获取解析之后的最终配置
const resolvedConfig = api.useResolvedConfigContext();
},
});
插件 API 的详细说明,请参考 Plugin API。
异步 setup
CLI 插件的 setup 可以是一个异步函数,在初始化过程中执行异步逻辑。
import type { CliPlugin } from '@modern-js/core';
export const myPlugin = (): CliPlugin => ({
name: 'my-plugin',
async setup(api) {
await doSomething();
},
});
注意,只有当前插件的 setup 异步函数执行完毕,才会继续执行下一个插件的 setup 函数。因此,你需要避免在 setup 函数中进行耗时过长的异步操作,防止影响 CLI 启动性能。
添加插件
自定义插件的使用方式可以查看:plugins (框架插件)。下面会介绍 Modern.js 中推荐的插件实现方法。
开发本地插件
本地插件推荐写在 config/plugin
目录下,并通过 export default
导出:
config/plugin/myPlugin.ts
import type { CliPlugin } from '@modern-js/core';
export const myPlugin = (): CliPlugin => ({
name: 'my-plugin',
setup() {
// 插件初始化
},
});
然后在 modern.config.ts
中注册对应的插件:
modern.config.ts
import { defineConfig } from '@modern-js/app-tools';
import { myPlugin } from './config/plugin/myPlugin';
export default defineConfig({
plugins: [myPlugin()],
});
在 npm 上发布插件
如果你需要将 Modern.js 插件发布到 npm,推荐使用 Modern.js Module 来管理和构建。
首先创建一个空的 Modern.js Module 项目,调整 npm 包名称:
{
"name": "my-plugin"
...
}
然后新建对应的插件文件:
src/index.ts
import type { CliPlugin } from '@modern-js/core';
export const myPlugin = (): CliPlugin => ({
name: 'my-plugin',
setup() {
// 插件初始化
},
});
发布之后,安装到需要使用的项目 pnpm add my-plugin
,这里以一个应用项目为例,然后在 modern.config.ts
中添加:
modern.config.ts
import { defineConfig } from '@modern-js/app-tools';
import { myPlugin } from 'my-plugin';
export default defineConfig({
plugins: [myPlugin()],
});
如果你发现目前 Modern.js 存在无法满足的场景,欢迎通过编写自定义插件的方式来一起建设 Modern.js 生态。