插件 API
核心 API
service 和 PluginAPI 里定义的方法。
applyPlugins
api.applyPlugins({ key: string, type?: api.ApplyPluginsType, initialValue?: any, args?: any })
取得 register()
注册的 hooks 执行后的数据,这是一个异步函数,因此它返回的将是一个 Promise。这个方法的例子和详解见 register api
describe
api.describe({ key?:string, config?: { default , schema, onChange }, enableBy? })
在插件注册阶段( initPresets or initPlugins stage )执行,用于描述插件或者插件集的 key、配置信息和启用方式等。
key
是配置中该插件配置的键名config.default
是插件配置的默认值,当用户没有在配置中配置 key 时,默认配置将生效。config.schema
用于声明配置的类型,基于 joi 。 如果你希望用户进行配置,这个是必须的 ,否则用户的配置无效config.onChange
是 dev 模式下,配置被修改后的处理机制。默认值为api.ConfigChangeType.reload
,表示在 dev 模式下,配置项被修改时会重启 dev 进程。 你也可以修改为api.ConfigChangeType.regenerateTmpFiles
, 表示只重新生成临时文件。你还可以传入一个方法,来自定义处理机制。enableBy
是插件的启用方式,默认是api.EnableBy.register
,表示注册启用,即插件只要被注册就会被启用。可以更改为api.EnableBy.config
,表示配置启用,只有配置插件的配置项才启用插件。你还可以自定义一个返回布尔值的方法( true 为启用 )来决定其启用时机,这通常用来实现动态生效。
e.g.
api.describe({key: 'foo',config: {schema(joi){return joi.string();},onChange: api.ConfigChangeType.regenerateTmpFiles,},enableBy: api.EnableBy.config,})
这个例子中,插件的 key
为 foo
,因此配置中的键名为 foo
,配置的类型是字符串,当配置 foo
发生变化时,dev 只会重新生成临时文件。该插件只有在用户配置了 foo
之后才会启用。
isPluginEnable
api.isPluginEnable( key:string)
判断插件是否启用,传入的参数是插件的 key
register
api.register({ key: string, fn, before?: string, stage?: number})
为 api.applyPlugins
注册可供其使用的 hook。
key
是注册的 hook 的类别名称,可以多次使用register
向同一个key
注册 hook,它们将会依次执行。这个key
也同样是使用applyPlugins
收集 hooks 数据时使用的key
。注意: 这里的 key 和 插件的 key 没有任何联系。fn
是 hook 的定义,可以是同步的,也可以是异步的(返回一个 Promise 即可)stage
用于调整执行顺序,默认为 0,设为 -1 或更少会提前执行,设为 1 或更多会后置执行。before
同样用于调整执行的顺序,传入的值为注册的 hook 的名称。注意:register
注册的 hook 的名称是所在 dumi 插件的 id。 stage 和 before 的更多用法参考 tapable
注意: 相较于 dumi@1
, dumi@2
去除了 pluginId
参数。
fn 的写法需要结合即将使用的 applyPlugins 的 type 参数来确定:
api.ApplyPluginsType.add
applyPlugins
将按照 hook 顺序来将它们的返回值拼接成一个数组。此时fn
需要有返回值,fn
将获取applyPlugins
的参数args
来作为自己的参数。applyPlugins
的initialValue
必须是一个数组,它的默认值是空数组。当key
以'add'
开头且没有显式地声明type
时,applyPlugins
会默认按此类型执行。api.ApplyPluginsType.modify
applyPlugins
将按照 hook 顺序来依次更改applyPlugins
接收的initialValue
, 因此此时initialValue
是必须的 。此时fn
需要接收一个memo
作为自己的第一个参数,而将会把applyPlugins
的参数args
来作为自己的第二个参数。memo
是前面一系列 hook 修改initialValue
后的结果,fn
需要返回修改后的memo
。当key
以'modify'
开头且没有显式地声明type
时,applyPlugins
会默认按此类型执行。api.ApplyPluginsType.event
applyPlugins
将按照 hook 顺序来依次执行。此时不用传入initialValue
。fn
不需要有返回值,并且将会把applyPlugins
的参数args
来作为自己的参数。当key
以'on'
开头且没有显式地声明type
时,applyPlugins
会默认按此类型执行。
e.g.1 add 型
api.register({key: 'addFoo',// 同步fn: (args) => args});api.register({key: 'addFoo',// 异步fn: async (args) => args * 2})api.applyPlugins({key: 'addFoo',// key 是 add 型,不用显式声明为 api.ApplyPluginsType.addargs: 1}).then((data)=>{console.log(data); // [1,2]})
e.g.2 modify 型
api.register({key: 'foo',fn: (memo, args) => ({ ...memo, a: args})})api.register({key: 'foo',fn: (memo) => ({...memo, b: 2})})api.applyPlugins({key: 'foo',type: api.ApplyPluginsType.modify,// 必须有 initialValueinitialValue: {a: 0,b: 0},args: 1}).then((data) => {console.log(data); // { a: 1, b: 2 }});
registerCommand
api.registerCommand({name: string,description? : string,options? : string,details? : string,fn,alias? : string | string[]resolveConfigMode? : 'strict' | 'loose'})
注册命令。
alias
为别名,比如 generate 的别名 gfn
的参数为{ args }
, args 的格式同 yargs 的解析结果,需要注意的是_
里的 command 本身被去掉了,比如执行dumi generate page foo
,args._
为['page','foo']
resolveConfigMode
参数控制执行命令时配置解析的方式,strict
模式下强校验 dumi 项目的配置文件内容,如果有非法内容中断命令执行;loose
模式下不执行配置文件的校验检查。
registerMethod
api.registerMethod({ name: string, fn? })
往 api 上注册一个名为 'name'
的方法。
- 当传入了 fn 时,执行 fn
- 当没有传入 fn 时,
registerMethod
会将name
作为api.register
的key
并且将其柯里化后作为fn
。这种情况下相当于注册了一个register
的快捷调用方式,便于注册 hook。
注意:
- 相较于
dumi@1
,dumi@2
去除了 exitsError 参数。 - 通常不建议注册额外的方法,因为它们不会有 ts 提示,直接使用
api.register()
是一个更安全的做法。
e.g.1
api.registerMethod({name: foo,// 有 fnfn: (args) => {console.log(args);}})api.foo('hello, dumi!'); // hello, dumi!
该例子中,我们往api上注册了一个 foo 方法,该方法会把参数 console 到控制台。
e.g.2
import api from './api';api.registerMethod({name: 'addFoo'// 没有 fn})api.addFoo( args => args );api.addFoo( args => args * 2 );api.applyPlugins({key: 'addFoo',args: 1}).then((data)=>{console.log(data); // [ 1, 2 ]});
该例子中,我们没有向 api.registerMethod
中传入 fn。此时,我们相当于往 api 上注册了一个"注册器":addFoo
。每次调用该方法都相当于调用了 register({ key: 'addFoo', fn })
。因此当我们使用 api.applyPlugins
的时候(由于我们的方法是 add 型的,可以不用显式声明其 type )就可以获取刚刚注册的 hook 的值。
registerPresets
api.registerPresets( presets: string[] )
注册插件集,参数为路径数组。该 api 必须在 initPresets stage 执行,即只可以在 preset 中注册其他 presets
e.g.
api.registerPresets(['./preset',require.resolve('./preset_foo')])
registerPlugins
api.registerPlugins( plugins: string[] )
注册插件,参数为路径数组。该 api 必须在 initPresets 和 initPlugins stage 执行。
e.g.
api.registerPlugins(['./plugin',require.resolve('./plugin_foo')])
注意: 相较于 dumi@1
,dumi@2
不再支持在 registerPresets
和 registerPlugins
中直接传入插件对象了,现在只允许传入插件的路径。
registerGenerator
skipPlugins
api.skipPlugins( keys: string[])
声明哪些插件需要被禁用,参数为插件 key 的数组
重点方法
addContentTab
增加页面 Tab,关于页面 Tab 的介绍可查看 指南 - 页面 Tab。
api.addContentTab(() => ({/*** tab key,也会作为 URL 的 query 参数值*/key: 'playground';/*** id,用于区分多个 tab,仅在自动 id 冲突时需要配置*/id?: string;/*** 生效路由的正则,匹配的路由才会应用该 Tab,未配置时则对所有路由生效*/test?: /\/components\//;/*** 用于自定义 tab 名称*/title?: string;/*** tab 名称文案国际化,通过传入国际化文案 key 来实现。优先级高于 title 配置项* 可通过 api.modifyTheme 来配置国际化文案 key 对应的文案*/titleIntlId?: string;/*** 页面 Tab 的 React 组件*/component: require.resolve('/path/to/tab.tsx');}));
getAssetsMetadata
获取当前项目的资产元数据,通常配合其他插件钩子使用。
api.onBuildComplete(async () => {const data = await api.getAssetsMetadata();// do something});
modifyAssetsMetadata
修改当前项目的资产元数据。执行 dumi build --assets
时会产出资产元数据文件 assets.json
,通过该 API 可对这份数据文件进行修改。
api.modifyAssetsMetadata((data) => {// 覆盖默认的组件库产品名称data.name = 'Ant Design';return data;});
modifyTheme
修改主题解析结果。
api.modifyTheme((memo) => {// 修改国际化文案console.log(memo.locales);// 修改全局组件console.log(memo.builtins);// 修改插槽console.log(memo.slots);// 修改 GlobalLayout/DocLayout/DemoLayoutconsole.log(memo.layouts);return memo;});
WARNING
需要注意的是,该 API 的修改结果仍会被本地主题覆盖,因为本地主题的优先级最高。
registerTechStack
注册其他技术栈,用于扩展 Vue.js、小程序等技术栈的 demo 编译能力,可参考内置的 React 技术栈 实现。dumi 官方的 Vue.js 编译方案正在研发中,敬请期待。
// CustomTechStack.tsimport type { ITechStack } from 'dumi';class CustomTechStack implements IDumiTechStack {/*** 技术栈名称,确保唯一*/name = 'custom';/*** 是否支持编译改节点,返回 true 的话才会调用后续方法*/isSupported(node: hastElement, lang: string) {return lang === 'jsx';}/*** 代码转换函数,返回值必须是 React 组件代码字符串* @note https://github.com/umijs/dumi/tree/master/src/types.ts#L57*/transformCode(raw: string, opts) {// do somethingreturn 'function Demo() { return ... }';}/*** 生成 demo 资产元数据(可选,通常仅在 dumi 无法分析出元数据时使用,例如非 JS 模块)* @note https://github.com/umijs/dumi/tree/master/src/types.ts#L64*/generateMetadata(asset: ExampleBlockAsset) {// do somethingreturn asset;}/*** 生成 demo 预览器的组件属性,在需要覆盖默认属性时使用* @note https://github.com/umijs/dumi/tree/master/src/types.ts#L70*/generatePreviewerProps(props: IPreviewerProps, opts) {// do somethingreturn props;}}// plugin.tsapi.registerTechStack(() => new CustomTechStack());
扩展方法
通过api.registerMethod()
扩展的方法,它们的作用都是注册一些 hook 以供使用,因此都需要接收一个 fn。这些方法中的大部分都按照 add-
modify-
on-
的方式命名,它们分别对应了 api.ApplyPluginsType
的三种方式,不同方式接收的 fn 不太相同,详见 register 一节。
注意: 下文提到的所有 fn 都可以是同步的或者异步的(返回一个 Promise 即可)。fn 都可以被
{fn,name?: string,before?: string | string[],stage: number,}
代替。其中各个参数的作用详见 tapable
addBeforeBabelPlugins
增加额外的 Babel 插件。传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
api.addBeforeBabelPlugins(() => {// 返回一个 Babel 插件(来源于 Babel 官网的例子)return () => {visitor: {Identifier(path) {const name = path.node.name;path.node.name = name.split("").reverse().join("");}}}})
addBeforeBabelPresets
增加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集( presets )或插件集数组。
api.addBeforeBabelPresets(() => {// 返回一个 Babel 插件集return () => {return {plugins: ["Babel_Plugin_A","Babel_Plugin_B"]}}})
addBeforeMiddlewares
在 webpack-dev-middleware 之前添加中间件。传入的 fn 不需要参数,且需要返回一个 express 中间件或其数组。
api.addBeforeMiddlewares(() => {return (req, res, next) => {if(false) {res.end('end');}next();}})
addEntryCode
在入口文件的最后面添加代码(render 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCode(() => `console.log('I am after render!')`);
addEntryCodeAhead
在入口文件的最前面添加代码(render 前,import 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCodeAhead(() => `console.log('I am before render!')`)
addEntryImports
在入口文件中添加 import 语句 (import 最后面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string}
或其数组。
api.addEntryImports(() => ({source: '/modulePath/xxx.js',specifier: 'moduleName'}))
addEntryImportsAhead
在入口文件中添加 import 语句 (import 最前面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string}
或其数组。
api.addEntryImportsAhead(() => ({source: 'anyPackage'}))
addExtraBabelPlugins
添加额外的 Babel 插件。 传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
addExtraBabelPresets
添加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集或其数组。
addHTMLHeadScripts
往 HTML 的 <head>
元素里添加 Script。传入的 fn 不需要参数,且需要返回一个 string(想要加入的代码) 或者 { async?: boolean, charset?: string, crossOrigin?: string | null, defer?: boolean, src?: string, type?: string, content?: string }
或者它们的数组。
api.addHTMLHeadScripts(() => `console.log('I am in HTML-head')`)
addHTMLLinks
往 HTML 里添加 Link 标签。 传入的 fn 不需要参数,返回的对象或其数组接口如下:
{as?: string, crossOrigin: string | null,disabled?: boolean,href?: string,hreflang?: string,imageSizes?: string,imageSrcset?: string,integrity?: string,media?: string,referrerPolicy?: string,rel?: string,rev?: string,target?: string,type?: string}
addHTMLMetas
往 HTML 里添加 Meta 标签。 传入的 fn 不需要参数,返回的对象或其数组接口如下:
{content?: string,'http-equiv'?: string,name?: string,scheme?: string}
addHTMLScripts
往 HTML 尾部添加 Script。 传入的 fn 不需要参数,返回的对象接口同 addHTMLHeadScripts
addHTMLStyles
往 HTML 里添加 Style 标签。 传入的 fn 不需要参数,返回一个 string (style 标签里的代码)或者 { type?: string, content?: string }
,或者它们的数组。
addLayouts
添加全局 layout 组件。 传入的 fn 不需要参数,返回 { id?: string, file: string }
addMiddlewares
添加中间件,在 route 中间件之后。 传入的 fn 不需要参数,返回 express 中间件。
addPolyfillImports
添加补丁 import,在整个应用的最前面执行。 传入的 fn 不需要参数,返回 { source: string, specifier?:string }
addPrepareBuildPlugins
addRuntimePlugin
添加运行时插件,传入的 fn 不需要参数,返回 string ,表示插件的路径。
addRuntimePluginKey
添加运行时插件的 Key, 传入的 fn 不需要参数,返回 string ,表示插件的路径。
addTmpGenerateWatcherPaths
添加监听路径,变更时会重新生成临时文件。传入的 fn 不需要参数,返回 string,表示要监听的路径。
addOnDemandDeps
添加按需安装的依赖,他们会在项目启动时检测是否安装:
api.addOnDemandDeps(() => [{ name: '@swc/core', version: '^1.0.0', dev: true }])
chainWebpack
通过 webpack-chain 的方式修改 webpack 配置。传入一个fn,该 fn 不需要返回值。它将接收两个参数:
memo
对应 webpack-chain 的 configargs:{ webpack, env }
arg.webpack
是 webpack 实例,args.env
代表当前的运行环境。
e.g.
api.chainWebpack(( memo, { webpack, env}) => {// set aliasmemo.resolve.alias.set('a','path/to/a');// Delete progess bar pluginmemo.plugins.delete('progess');})
dumi@2
新增)
modifyAppData (修改 app 元数据。传入的 fn 接收 appData 并且返回它。
api.modifyAppData((memo) => {memo.foo = 'foo';return memo;})
modifyConfig
修改配置,相较于用户的配置,这份是最终传给 dumi 使用的配置。传入的 fn 接收 config 作为第一个参数,并且返回它。另外 fn 可以接收 { paths }
作为第二个参数。paths
保存了 dumi 的各个路径。
api.modifyConfig((memo, { paths }) => {memo.alias = {...memo.alias,'@': paths.absSrcPath}return memo;})
modifyDefaultConfig
修改默认配置。传入的 fn 接收 config 并且返回它。
modifyHTML
修改 HTML,基于 cheerio 的 ast。传入的 fn 接收 cheerioAPI 并且返回它。另外 fn 还可以接收{ path }
作为它的第二个参数,该参数代表路由的 path
api.modifyHTML(($, { path }) => {$('h2').addClass('welcome');return $;})
modifyHTMLFavicon
修改 HTML 的 favicon 路径。 传入的 fn 接收原本的 favicon 路径(string 类型)并且返回它。
modifyPaths
修改 paths,比如 absOutputPath、absTmpPath。传入的 fn 接收 paths 并且返回它。
paths 的接口如下:
paths:{cwd?: string;absSrcPath?: string;absPagesPath?: string;absTmpPath?: string;absNodeModulesPath?: string;absOutputPath?: string;}
modifyRendererPath
修改 renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
modifyServerRendererPath
修改 server renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
modifyRoutes
修改路由。 传入的 fn 接收 id-route 的 map 并且返回它。其中 route 的接口如下:
interface IRoute {path: string;file?: string;id: string;parentId?: string;[key: string]: any;}
e.g.
api.modifyRoutes((memo) => {Object.keys(memo).forEach((id) => {const route = memo[id];if(route.path === '/'){route.path = '/redirect'}});return memo;})
modifyTSConfig
修改临时目录下的 tsconfig 文件内容。
api.modifyTSConfig((memo) => {memo.compilerOptions.paths['foo'] = ['bar'];return memo;});
modifyWebpackConfig
修改 webpack 最终配置。传入的 fn 接收 webpack 的 Config 对象作为第一个参数并且返回它。另外 fn 还可以接收 { webpack, env }
作为第二个参数,其中 webpack 是 webpack 实例,env 代表当前环境。
api.modifyWebpackConfig((memo, { webpack, env }) => {// do somethingreturn memo;})
onBeforeCompiler
generate 之后,webpack / vite compiler 之前。传入的 fn 不接收任何参数。
onBeforeMiddleware
提供在服务器内部执行所有其他中间件之前执行自定义中间件的能力, 这可以用来定义自定义处理程序, 例如:
api.onBeforeMiddleware(({ app }) => {app.get('/some/path', function (req, res) {res.json({ custom: 'response' });});});
onBuildComplete
build 完成时。传入的 fn 接收 { isFirstCompile: boolean, stats, time: number, err?: Error }
作为参数。
onBuildHtmlComplete
build 完成且 html 完成构建之后。
onCheck
检查时,在 onStart 之前执行。传入的 fn 不接收任何参数
onCheckCode
检查代码时。传入的 fn 接收的参数接口如下:
args: {file: string;code: string;isFromTmp: boolean;imports: {source: string;loc: any;default: string;namespace: string;kind: babelImportKind;specifiers: Record<string, { name: string; kind: babelImportKind }>;}[];exports: any[];cjsExports: string[];}
onCheckConfig
检查 config 时。传入的 fn 接收 { config, userConfig }
作为参数,它们分别表示实际的配置和用户的配置。
onCheckPkgJSON
检查 package.json 时。传入的 fn 接收 {origin?, current}
作为参数。它们的类型都是 package.json 对象
onDevCompileDone
dev 完成时。传入的 fn 接收的参数接口如下:
args: {isFirstCompile: boolean;stats: any;time: number;}
onGenerateFiles
生成临时文件时,随着文件变化会频繁触发,有缓存。 传入的 fn 接收的参数接口如下:
args: {isFirstTime?: boolean;files?: {event: string;path: string;} | null;}
onPatchRoute
匹配单个路由,可以修改路由,给路由打补丁
onPkgJSONChanged
package.json 变更时。传入的 fn 接收 {origin?, current}
作为参数。它们的类型都是 package.json 对象
onPrepareBuildSuccess
onStart
启动时。传入的 fn 不接收任何参数。
writeTmpFile
api.writeTmpFile()
的 type 参数的类型。
- content: 写入的文本内容,有内容就不会使用模板。
- context: 模板上下文。
- noPluginDir: 是否使用插件名做为目录。
- path: 写入文件的路径。
- tpl: 使用模板字符串,没有模板路径会使用它。
- tplPath: 使用模板文件的路径。
属性
从 api 可以直接访问到的属性,这些属性有一部分来自于 service
appData
args
命令行参数,这里去除了命令本身。
e.g.
$ dumi dev --foo
, args 为{ _:[], foo: true }
$ dumi g page index --typescript --less
, args 为{ _: [ 'page', 'index''], typescript: true, less: true }
config
最终的配置(取决于你访问的时机,可能是当前收集到的最终配置)
cwd
当前路径
env
即 process.env.NODE_ENV
可能有 development
、production
和 test
logger
插件日志对象,包含 { log, info, debug, error, warn, profile }
,他们都是方法。其中 api.logger.profile
可用于性能耗时记录。
api.logger.profile('barId');setTimeout(() => {api.logger.profile('barId');})// profile - barId Completed in 6254ms
name
当前命令的名称,例如 $ dumi dev
, name
就是 dev
paths
项目相关的路径:
absNodeModulesPath
,node_modules 目录绝对路径absOutputPath
,输出路径,默认是 ./distabsPagesPath
,pages 目录绝对路径absSrcPath
,src 目录绝对路径,需注意 src 目录是可选的,如果没有 src 目录,absSrcPath 等同于 cwdabsTmpPath
,临时目录绝对路径cwd
,当前路径
注意: 注册阶段不能获取到。因此不能在插件里直接获取,要在 hook 里使用。
pkg
当前项目的 package.json
对象
pkgPath
当前项目的 package.json
的绝对路径。
plugin
当前插件的对象。
type
插件类型,有 preset 和 plugin 两种path
插件路径id
插件 idkey
插件 keyconfig
插件的配置enableBy
插件的启用方式
注意: 注册阶段使用的 plugin 对象是你 describe
之前的对象。
service
dumi 的 Service
实例。通常不需要用到,除非你知道为什么。
userConfig
用户的配置,从 .umirc
或 config/config
中读取的内容,没有经过 defaultConfig 以及插件的任何处理。可以在注册阶段使用。
ApplyPluginsType
api.applyPlugins()
的 type 参数的类型。包含
- add
- modify
- event
ConfigChangeType
为 api.describe()
提供 config.onChange
的类型,目前包含两种:
- restart,重启 dev 进程,是默认值
- regenerateTmpFiles,重新生成临时文件
EnableBy
插件的启用方式,包含三种:
- register
- config
ServiceStage
dumi service 的运行阶段。有如下阶段:
- uninitialized
- init
- initPresets
- initPlugins
- resolveConfig
- collectAppData
- onCheck
- onStart
- runCommand