news 2026/9/14 20:47:04

Umi Plugin System: Enabling, Configuring, and Developing Plugins

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Umi Plugin System: Enabling, Configuring, and Developing Plugins

Umi Plugin System: Enabling, Configuring, and Developing Plugins

【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi

Umi 的插件机制是这座 React 框架的核心:通过插件,你可以在不改动框架源码的前提下,同时扩展项目的编译期(build-time)与运行期(runtime)能力。本文以官方指南 use-plugins 为骨架,完整覆盖"如何启用插件、如何配置插件、如何编写项目级插件、如何开发自己的插件"这条完整路径,并结合当前仓库的源码实现(packages/corepackages/pluginspackages/preset-umi)深入剖析插件的注册顺序、id/key命名规则、生命周期与 PluginAPI 的工作原理。读完本文,你将能够:在标准 Umi 应用中按需启用 Max 插件、通过plugin.ts快速定制项目、并使用 Plugin API 编写可复用的 Umi 插件。

一、为什么插件是 Umi 的灵魂

官方在 Developing Plugins 中直接点明:"Umi 的本质在于它的插件机制(The essence of Umi lies in its plugin mechanism)。"基于 Umi 的插件机制,你可以增强项目的编译期与运行期能力,自由使用官方提供的 Plugin API 实现丰富的功能,例如:

  • 修改代码打包配置(webpack / vite / mako)
  • 修改启动代码(entry 文件)
  • 约定新的目录结构
  • 修改 HTML 产物

一个插件本质上就是一个接收api参数的普通函数。在函数内部,你可以调用api提供的方法来注册各种钩子(hooks),Umi 会在特定的时机执行这些钩子。

二、在标准 Umi 应用中使用插件

默认不包含任何插件

与直觉相反:在标准 Umi 应用中,默认不包含任何插件。如果希望使用 Umi Max 的功能(如数据流、antd 等),你需要手动安装插件并启用。

先安装官方插件集合@umijs/plugins

pnpm add -D @umijs/plugins

然后以启用 antd 插件为例,在配置文件(.umirc.tsconfig/config.ts)中声明:

// .umirc.ts export default { plugins: ['@umijs/plugins/dist/antd'], antd: {}, }

Umi 与 Max 的区别

Umi 与 Max 的核心区别在于:Max 已经内置了大部分插件,例如数据流(initial-statemodel)、antd等。这些插件都可以从@umijs/plugins/dist/*加载并启用。当前仓库中@umijs/plugins的源码位于 packages/plugins/src,从中可以看到 Max 内置的插件全集,包括:

插件入口提供能力
access权限访问控制
analytics站点分析
antdantd 组件库集成
confetti彩带动效
dvadva 状态管理
initial-state应用初始化数据
layoutProLayout 布局
locale国际化
mf模块联邦
model数据流模型
moment2dayjsmoment 迁移 dayjs
qiankun微前端
react-queryReact Query 集成
request请求库封装
styled-componentsCSS-in-JS
tailwindcssTailwind CSS
unocssUnoCSS
valtioValtio 状态管理

关于 Max 各特性的详细配置说明,请参阅 Umi Max。

💡我应该选择 Max 吗?使用 Max 并不意味着你必须使用它的全部功能,你可以按需禁用插件。因此,当你需要 Max 的某些特性时,随时可以选择创建一个 Max 项目。

三、项目级插件(plugin.ts)

如果只想在项目中快速使用插件能力(例如自定义 HTML 产物),可以在项目根目录创建plugin.ts,编写一个项目级插件,Umi 会自动将它加载为插件。

根据 Directory Structure 的说明,项目根目录的plugin.ts即项目级 Umi 插件:当你有 Umi 定制需求时,通常需要使用 Plugin API(例如修改产物 HTML),此时创建该文件即可。一个典型的plugin.ts示例:

import type { IApi } from 'umi'; export default (api: IApi) => { api.onDevCompileDone((opts) => { opts; // console.log('> onDevCompileDone', opts.isFirstCompile); }); api.modifyHTML(($) => { $; }); api.chainWebpack((memo) => { memo; }); };

可以看到,项目级插件与普通插件没有任何区别——同样是一个接收api的默认导出函数。Umi 启动时会自动读取根目录下的plugin.ts并将其注册进插件队列,因此它非常适合承载"仅属于当前项目、不需要对外发布"的定制逻辑,例如调整 HTML、注入编译完成回调、修改 webpack 链式配置等。

四、开发插件:核心概念

若需要把定制能力沉淀为可复用插件,请参考 Developing Plugins。下面梳理其核心概念。

4.1 一个最小插件

插件的本质是一个接收api的方法,在方法内通过api注册钩子。以下插件的作用是:根据用户配置的changeFavicon值,修改配置中的 favicon:

import { IApi } from 'umi'; export default (api: IApi) => { api.describe({ key: 'changeFavicon', config: { schema(joi) { return joi.string(); }, }, enableBy: api.EnableBy.config, }); api.modifyConfig((memo) => { memo.favicons = api.userConfig.changeFavicon; return memo; }); };

在这段代码中,api.describe声明了插件的配置信息(keyconfig.schemaenableBy),api.modifyConfig注册了一个(memo) => {...}钩子。当用户在配置中写了changeFavicon后,Umi 才注册该插件(EnableBy.config语义);在 Umi 收集配置的生命周期里,这个钩子被执行,从而把favicon改为用户配置的changeFavicon

4.2 plugin 与 preset

**preset(预设)**的作用是预置一批插件,通常用于一次性注册一批 presets 和 plugins。在 preset 中,上述接收api的方法可以拥有返回值,返回值是一个包含pluginspresets属性的对象,用于注册对应的插件或插件集合:

import { IApi } from 'umi'; export default (api: IApi) => { return { plugins: ['./plugin_foo', './plugin_bar'], presets: ['./preset_foo'], }; };

注册顺序值得注意:presets 永远在 plugins 之前注册。Umi 维护两个队列按顺序注册 presets 和 plugins:示例中注册的preset_foo会被放到 presets 队列的头部,而plugin_fooplugin_bar会被依次追加到 plugins 队列的尾部。将 preset 放在队首,是为了保证 preset 之间的顺序与依赖关系可控。

另一点值得注意的是:在插件(plugin)中同样可以返回一些 plugins 或 presets,但Umi 不会对此做任何处理

4.3 插件的 id 与 key

每个插件都对应一个id与一个key

  • id:插件路径的缩写,是插件的唯一标识;
  • key:插件在配置中使用的键名。

例如插件node_modules/@umijs/plugin-foo/index.js,其id通常是@umijs/plugin-fookeyfoo。这样开发者就能在配置中用键名foo来配置该插件。

这一规则在当前仓库的 Plugin 实现 中可以得到印证。getKey()的推导逻辑是:

// e.g. // initial-state -> initialState // webpack.css-loader -> webpack.cssLoader function nameToKey(name: string) { return name .split('.') .map((part) => lodash.camelCase(part)) .join('.'); } return nameToKey( opts.isPkgEntry ? Plugin.stripNoneUmiScope(opts.pkg.name).replace(RE[this.type], '') : basename(this.path, extname(this.path)), );

其中RE是识别 Umi 插件命名规范的正则:

const RE = { plugin: /^(@umijs\/|umi-)plugin-/, preset: /^(@umijs\/|umi-)preset-/, };

也就是说:如果是包入口,则去掉@umijs/umi-等作用域前缀以及plugin-/preset-前缀得到 key;如果不是包,则取文件名(去掉扩展名)作为 key,再经过 camelCase 转换。例如@alipay/umi-plugin-bar的默认 key 是bar./plugins/foo.js的默认 key 是foo。为避免不必要的麻烦,官方建议为自己的插件显式声明 key(通过api.describekey字段)。

id的推导见getId():包入口直接用包名;位于项目目录内则用相对路径(./xxx);否则拼接包名与相对路径。此外id还会把@umijs/preset-umi/lib/plugins替换为@@并去掉.js后缀。

五、启用插件

Umi 4 中插件有两种启用方式:环境变量启用配置启用。(与umi@3不同,Umi 4 不再支持自动启用package.json依赖中以@umijs/preset-@umijs/plugin-umi-preset-umi-plugin-开头的插件/预设,这一点在 directory-structure 中也有明确说明。若需自定义插件/预设,必须手动在配置中声明。)

注意:这里讨论的是第三方插件,Umi 内置插件普遍通过配置中的 key 来启用。

5.1 环境变量启用

可以通过环境变量UMI_PRESETSUMI_PLUGINS注册额外的插件:

$ UMI_PRESETS=foo/preset.js umi dev

官方特别提醒:这种方式不推荐在项目中使用,通常用于基于 Umi 框架做二次封装(如脚手架 CLI)的场景。

5.2 配置启用

在配置中通过presetsplugins字段启用插件,配置的内容是插件的路径:

export default { presets: ['./preset/foo', 'bar/presets'], plugins: ['./plugin', require.resolve('plugin_foo')], };

源码层面的收集顺序在 getPluginsAndPresets 中可以看到:依次合并命令行 opts 传入的 presets/plugins → 环境变量(UMI_PRESETS/UMI_PLUGINS)→ 用户配置中的presets/plugins,再逐个resolve.sync解析路径(支持.tsx/.ts/.mjs/.jsx/.js扩展名)并构造Plugin实例。

5.3 插件的注册顺序

Umi 插件的注册遵循一定顺序:

  • 所有 presets 都在 plugins 之前注册;
  • 内置插件 → 环境变量中的插件 → 用户配置中的插件;
  • 同一时刻(同一数组中)注册的插件按顺序注册;
  • preset 中注册的 presets 立即执行,而注册的 plugins 在最后执行。

六、禁用插件

禁用插件有两种方式:

6.1 将 key 配置为 false

export default { mock: false, };

这样会禁用 Umi 内置的 mock 插件。

6.2 在插件中禁用其他插件

通过api.skipPlugins(pluginId[])实现,详见 Plugin API。skipPlugins接收插件 key 的数组,源码实现(pluginAPI.ts)会校验:不能跳过自己、被跳过的 key 必须已被某个插件注册,随后把对应插件的 id 加入service.skipPluginIds集合。

七、查看插件注册状态

通过命令行查看当前注册了哪些插件:

$ umi plugin list

八、配置插件

通过插件的 key 来配置插件:

export default { mock: { exclude: ['./foo'] }, };

这里mock是 Umi 内置 mock 插件的 key。再比如安装一个插件umi-plugin-bar,其默认 key 是bar,则可以这样配置:

export default { bar: { ... }, };

默认命名规则小结

  • 若插件是,默认 key 为去掉前缀后的包名:@umijs/plugin-foofoo@alipay/umi-plugin-barbar(前提是包名符合 Umi 插件命名规范);
  • 若插件不是包,默认 key 为插件文件名:./plugins/foo.jsfoo
  • 推荐显式声明 key,避免歧义。

九、插件机制与生命周期

Umi 的插件机制整体运行过程如下(生命周期状态在 types.ts 中以ServiceStage枚举定义,各阶段依次推进):

阶段作用
init加载各种配置信息:加载.env文件、读取package.json、加载用户配置,并按序解析所有插件(内置插件、环境变量、用户配置)
initPresets注册 presets。preset 可通过返回{ presets, plugins }注册更多插件:presets 追加到 presets 队列头部,plugins 追加到 plugins 队列尾部
initPlugins注册插件(含上一阶段 preset 追加的插件)。注意:插件即使返回{ presets, plugins },Umi 也不会处理。插件 init 的本质是执行插件代码——而插件代码只是在调用 api 注册各种钩子,钩子此时并不会执行,因此这一阶段被称为"插件注册"
resolveConfig汇总各插件声明的config schema,然后执行modifyConfigmodifyDefaultConfigmodifyPaths等钩子收集配置
collectAppData执行modifyAppData钩子,维护 App 的元数据(AppDataumi@4新增的 api)
onCheck执行onCheck钩子
onStart执行onStart钩子
runCommand运行当前要执行的 CLI 命令(如umi dev)。Umi 的各种核心功能都在命令中实现,包括我们插件注册的大部分钩子

9.1register()registerMethod()applyPlugins()

  • register():接收一个 key 和一个 hook,维护key → hook[]的映射。每次调用都会为同一个 key 追加注册一个 hook。注册的钩子供applyPlugins使用,执行顺序遵循 tapable 的规则。register还支持stage(默认 0,负数提前执行、正数延后执行)与before(指定在某 hook 之前执行)调整顺序。
  • registerMethod():接收一个 name 和可选的 fn,在 api 上注册一个方法。若不传 fn,则会注册一个"注册器"到 api 上:该注册器把传入的 fn 与 name 作为 key 封装成一次register()调用。例如api.registerMethod({ name: 'addFoo' })之后,每次调用api.addFoo(fn)等价于api.register({ key: 'addFoo', fn })。源码实现(pluginAPI.ts)会把方法存入service.pluginMethods供 Proxy 取用。

applyPlugins根据类型聚合钩子的执行结果,三种类型与默认规则如下:

  • add:按钩子顺序把返回值拼接成数组,fn接收argsinitialValue必须是数组(默认空数组)。当 key 以add开头且未显式声明 type 时默认采用此类型。
  • modify:按钩子顺序依次修改initialValue,因此必须传initialValuefn第一个参数是memo(前面钩子修改后的累积结果),需要返回修改后的 memo。当 key 以modify开头时默认采用此类型。
  • event:按顺序执行,不需要initialValuefn无需返回值。当 key 以on开头时默认采用此类型。

9.2 PluginAPI 的原理

Umi 为每个插件分配一个 PluginAPI 对象,它同时引用插件自身与 Umi 的 service。Umi 按照如下规则对 PluginAPI 对象的get()方法做了 Proxy 代理(见 proxyPluginAPI):

  1. pluginMethod:若属性是 Umi 维护的pluginMethods[]中的方法(通过registerMethod()注册的),返回该方法;
  2. service props:若属性在serviceProps数组中(Umi 允许插件直接访问的 service 属性),返回 service 的对应属性;
  3. static props:若属性在staticProps数组中(静态变量,如类型定义和常量),返回它;
  4. 否则,返回 api 自身的属性。

因此,Umi 提供给插件的大部分 api 都依赖registerMethod()实现,你可以直接使用这些 api 快速注册钩子。这也是 Umi"框架与功能解耦"的体现:Umi 的 service 只负责插件管理,而所有 api 都依赖插件来提供。

9.3 preset-umi

umi-core(即 packages/core)提供了一套插件注册与管理机制,而 Umi 的核心功能全部由 preset-umi 实现。preset-umi本质上是一个内置插件集,提供三类插件:

  • registerMethods:注册前面提到的大量"注册器",供开发者快速注册钩子,占据 PluginAPI 的大部分;
  • features:为 Umi 提供各种特性,如appDatalowImportmock等;
  • commands:注册各类命令,提供 Umi CLI 的各种功能。Umi 能在终端正常运行,正是依赖 commands 提供的功能。

十、从使用到开发的完整路径

总结一下,围绕"插件"这条主线,你可以按需选择使用方式:

  1. 标准 Umi 项目:安装@umijs/plugins并在配置中声明plugins: ['@umijs/plugins/dist/antd']等条目,即可按需启用 Max 特性;
  2. 项目级定制:在项目根目录创建plugin.ts,Umi 自动加载,配合 Plugin API 快速修改 HTML、webpack 配置、开发编译回调等;
  3. 沉淀为可复用插件/预设:编写一个导出(api) => {}的模块,用api.describe声明 key 与配置 schema,用register/registerMethod注册钩子,遵循plugin-/preset-命名规范后即可通过配置或环境变量在任何 Umi 项目中启用;
  4. 调试与验证:使用umi plugin list查看插件注册状态,使用api.skipPlugins禁用不需要的插件。

无论你处于哪一步,插件的核心心智模型始终不变:插件是函数,api 是入口,钩子是时机。理解了这个模型,Umi 的绝大部分能力都在你的掌控之中。

【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 20:46:53

C#上位机与STM32协同设计:通信、协议与UI工程实践

1. 这不是“写个串口界面”——C#上位机在STM32项目中的真实定位与价值边界很多人看到“C#上位机 STM32”,第一反应是:“哦,不就是用SerialPort控件读个串口、画几个按钮和曲线图?”——这种理解放在2015年或许勉强及格&#xff…

作者头像 李华
网站建设 2026/9/14 20:46:51

C++实现量子计算模拟的核心技术与优化策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:46:27

Python音频服务性能优化:pydub与pandas工程避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:44:24

课程达成度评价系统设计:从评价模型到自动化报告生成

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:43:11

OpenClaw多智能体架构与文生图模型部署实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华