Open-source MIT Licensed | Copyright © 2019-present
Powered by self
Powered by self
dumi 是基于 Umi 打造的静态站点框架,所以同时也继承了 Umi 强大的插件机制,并在之上做了扩展,以满足更多的场景需求。
基于 dumi 的插件机制,你可以获得扩展项目的编译时和运行时的能力。你可以利用我们提供的 插件API 来自由地编写插件,进而实现修改代码打包配置、修改启动代码、约定目录结构、修改 HTML 等丰富的功能。
插件的本质就是一个方法,该方法接收了一个参数:api。在插件中,你可以调用 api 提供的方法进行一些 hook 的注册,随后 dumi 会在特定的时机执行这些 hook。
比如:
import { IApi } from 'dumi';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;});};
这个插件的作用是根据用户配置的 changeFavicon 值来更改配置中的 favicons,(一个很简单且没有实际用途的例子XD)。可以看到插件其实就是一个接收了参数 api 的方法。在这个方法中,我们调用了 api.modifyConfig
注册了一个 hook: (memo)=>{...}
。当你在配置中配置了 changeFavicon
之后, dumi 会注册该插件。在 dumi 收集配置的生命周期里,我们在插件里注册的 hook 将被执行,此时配置中的 favicon
就会被修改为用户配置中的 changeFavicon
。
preset 的作用是预设一些插件,它通常用来注册一批 presets 和 plugins。在 preset 中,上述提到的接受 api 的方法可以有返回值,该返回值是一个包含 plugins 和 presets 属性的对象,其作用就是注册相应的插件或者插件集。
比如:
import { IApi } from 'dumi';export default (api: IApi) => {return {plugins: ['./plugin_foo','./plugin_bar'],presets: ['./preset_foo']}};
它们的注册顺序是值得注意的:presets 始终先于 plugins 注册。dumi 维护了两个队列分别用来依次注册 presets 和 plugins,这个例子中的注册的 preset_foo
将被置于 presets 队列队首,而 plugin_foo
和 plugin_bar
将被依次置于 plugins 队列队尾。这里把 preset 放在队首的目的在于保证 presets 之间的顺序和关系是可控的。
另外一个值得注意的点是:在 plugin 中,你也可以 return 一些 plugins 或者 presets,但是 dumi 不会对它做任何事情。
每个插件都对应一个 id 和 key。
id 是插件所在路径的简写,作为插件的唯一标识;而 key 则是用于插件配置的键名。
比如插件 node_modules/@umijs/plugin-foo/index.js
,通常来说,它的 id 是 @umijs/plugin-foo
, key 是 foo
。此时就允许开发者在配置中来配置键名为 foo
的项,用来对插件进行配置。
插件有两种启用方式: 环境变量中启用和配置中启用。(与 dumi@1
不同,我们不再支持对 package.json
中依赖项的插件实现自动启用)
注意:这里的插件指的是第三方插件,dumi 的内置插件统一在配置中通过对其 key 进行配置来启用。
还可以通过环境变量 DUMI_PRESETS
和 DUMI_PLUGINS
注册额外插件。
比如:
$ DUMI_PRESETS = foo/preset.js dumi dev
注意: 项目里不建议使用,通常用于基于 dumi 的框架二次封装。
在配置里通过 presets
和 plugins
配置插件,比如:
export default {presets: ['./preset/foo','bar/presets'],plugins: ['./plugin', require.resolve('plugin_foo')]}
配置的内容为插件的路径。
dumi 插件的注册遵循一定的顺序:
有两种方式禁用插件
比如:
export default{mock: false}
会禁用 dumi 内置的 mock 插件。
可通过 api.skipPlugins(pluginId[])
的方式禁用,详见插件 API。
$ dumi plugin list
通过配置插件的 key 来配置插件,比如:
export default{mock: { exclude: ['./foo'] }}
这里 mock 就是 dumi 内置插件 mock 的 key。
再比如我们安装一个插件 dumi-plugin-bar
, 其 key 默认是 bar
, 就可以配置:
export default{bar: { ... }}
如果插件是一个包的话,key 的默认值将是去除前缀的包名。比如 @umijs/plugin-foo
的 key 默认为 foo
, @alipay/dumi-plugin-bar
的 key 默认为 bar
。值得注意的是,该默认规则要求你的包名符合 dumi 插件的命名规范。
如果插件不是一个包的话,key 的默认值将是插件的文件名。比如 ./plugins/foo.js
的 key 默认为 foo
。
为了避免不必要的麻烦,我们建议你为自己编写的插件显式地声明其 key。
.env
文件; require package.json
;加载用户的配置信息; resolve 所有的插件(内置插件、环境变量、用户配置依次进行)。return { presets, plugins }
来添加额外的插件。其中 presets 将添加到 presets 队列的队首,而 plugins 将被添加到 plugins 队列的队尾。return { presets, plugins }
,但是 dumi 不会对其进行任何操作。插件的 init 其实就是执行插件的代码(但是插件的代码本质其实只是调用 api 进行各种 hook 的注册,而 hook 的执行并非在此阶段执行,因此这里叫插件的注册)。config schema
的定义,然后执行插件的 modifyConfig
、modifyDefaultConfig
、 modifyPaths
等 hook,进行配置的收集。modifyAppData
hook,来维护 App 的元数据。( AppData
是 umi@4
新增的 api )onCheck
hook。onStart
hook。dumi dev
, 这里就会执行 dev command)dumi 的各种核心功能都在 command 中实现。包括我们的插件调用 api 注册的绝大多数 hook。以上就是 dumi 的插件机制的整体流程。
register()
、 registerMethod()
以及 applyPlugins()
register()
接收一个 key 和 一个 hook,它维护了一个 key-hook[]
的 map,每当调用 register()
的时候,就会为 key 额外注册一个 hook。
register()
注册的 hooks 供 applyPlugins 使用。 这些 hook 的执行顺序参照 tapable。
registerMethod()
接收一个 key 和 一个 fn,它会在 api 上注册一个方法。如果你没有向 registerMethod()
中传入 fn,那么 registerMethod()
会在 api 上注册一个“注册器”: 它会将 register()
传入 key 并柯里化后的结果作为 fn 注册到 api 上。这样就可以通过调用这个“注册器”,快捷地为 key 注册 hook 了。
关于上述 api 的更具体的使用,请参照插件 API。
dumi 会为每个插件赋予一个 PluginAPI 对象,这个对象引用了插件本身和 dumi 的 service。
dumi 为 PluginAPI 对象的 get()
方法进行了 proxy,具体规则如下:
pluginMethods[]
( 通过 registerMethod()
注册的方法 )中的方法,则返回这个方法。因此,dumi 提供给插件的 api 绝大多数都是依靠 registerMethod()
来实现的,你可以直接使用我们的这些 api 快速地在插件中注册 hook。这也是 dumi 将框架和功能进行解耦的体现: dumi 的 service 只提供插件的管理功能,而 api 都依靠插件来提供。