Open-source MIT Licensed | Copyright © 2019-present
Powered by self
Powered by self
service 和 PluginAPI 里定义的方法。
api.applyPlugins({ key: string, type?: api.ApplyPluginsType, initialValue?: any, args?: any })
取得 register()
注册的 hooks 执行后的数据,这是一个异步函数,因此它返回的将是一个 Promise。这个方法的例子和详解见 register api
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
之后才会启用。
api.isPluginEnable( key:string)
判断插件是否启用,传入的参数是插件的 key
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 }});
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
模式下不执行配置文件的校验检查。api.registerMethod({ name: string, fn? })
往 api 上注册一个名为 'name'
的方法。
registerMethod
会将 name
作为 api.register
的 key
并且将其柯里化后作为 fn
。这种情况下相当于注册了一个 register
的快捷调用方式,便于注册 hook。注意:
dumi@1
, dumi@2
去除了 exitsError 参数。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 的值。
api.registerPresets( presets: string[] )
注册插件集,参数为路径数组。该 api 必须在 initPresets stage 执行,即只可以在 preset 中注册其他 presets
e.g.
api.registerPresets(['./preset',require.resolve('./preset_foo')])
api.registerPlugins( plugins: string[] )
注册插件,参数为路径数组。该 api 必须在 initPresets 和 initPlugins stage 执行。
e.g.
api.registerPlugins(['./plugin',require.resolve('./plugin_foo')])
注意: 相较于 dumi@1
,dumi@2
不再支持在 registerPresets
和 registerPlugins
中直接传入插件对象了,现在只允许传入插件的路径。
注册微生成器用来快捷生成模板代码。
示例:
import { GeneratorType } from '@umijs/core';import { logger } from '@umijs/utils';import { join } from 'path';import { writeFileSync } from 'fs';api.registerGenerator({key: 'editorconfig',name: 'Create .editorconfig',description: 'Setup editorconfig config',type: GeneratorType.generate,fn: () => {const configFilePath = join(api.cwd, '.editorconfig')if (existsSync(configFilePath)) {logger.info(`The .editorconfig file already exists.`)return}writeFileSync(configFilePath,`# 🎨 http://editorconfig.orgroot = true[*]indent_style = spaceindent_size = 2end_of_line = lfcharset = utf-8trim_trailing_whitespace = trueinsert_final_newline = true[*.md]trim_trailing_whitespace = false`.trimStart(),'utf-8')logger.info(`Generate .editorconfig file successful.`)}})
更多示例见 已有生成器源码
。
api.skipPlugins( keys: string[])
声明哪些插件需要被禁用,参数为插件 key 的数组
增加页面 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');}));
获取当前项目的资产元数据,通常配合其他插件钩子使用。
api.onBuildComplete(async () => {const data = await api.getAssetsMetadata();// do something});
修改当前项目的资产元数据。执行 dumi build --assets
时会产出资产元数据文件 assets.json
,通过该 API 可对这份数据文件进行修改。
api.modifyAssetsMetadata((data) => {// 覆盖默认的组件库产品名称data.name = 'Ant Design';return data;});
修改主题解析结果。
api.modifyTheme((memo) => {// 修改国际化文案console.log(memo.locales);// 修改全局组件console.log(memo.builtins);// 修改插槽console.log(memo.slots);// 修改 GlobalLayout/DocLayout/DemoLayoutconsole.log(memo.layouts);return memo;});
需要注意的是,该 API 的修改结果仍会被本地主题覆盖,因为本地主题的优先级最高。
注册其他技术栈,用于扩展 Vue.js、小程序等技术栈的 demo 编译能力。如何添加一个完整的技术栈支持,可查看添加技术栈。
目前提供两种 API 实现技术栈:
defineTechStack
推荐import { defineTechStack } from 'dumi/tech-stack-utils';const CustomTechStack = defineTechStack({name: 'custom',runtimeOpts: {compilePath: '...',rendererPath: '...',pluginPath: '...',},isSupported(node, lang) {return ['vue'].includes(lang);},onBlockLoad(args) {// ...},transformCode(raw, opts) {// ...},});api.registerTechStack(() => CustomTechStack);
IDumiTechStack
抽象类// CustomTechStack.tsimport type {IDumiTechStack,IDumiTechStackRuntimeOpts,} from 'dumi/tech-stack-utils';class CustomTechStack implements IDumiTechStack {/*** 技术栈名称,确保唯一*/name = 'custom';/*** 是否支持编译改节点,返回 true 的话才会调用后续方法*/isSupported(node: hastElement, lang: string) {return lang === 'jsx';}/*** 默认情况下, dumi 只支持js/jsx/ts/tsx模块的依赖分析,* 所以对于无法识别的文件类型需要通过此方法将文件转换为js模块*/onBlockLoad?(args: IDumiTechStackOnBlockLoadArgs,): IDumiTechStackOnBlockLoadResult {// do something}/*** 代码转换函数* @note https://github.com/umijs/dumi/tree/master/src/types.ts#L57*/transformCode(raw: string, opts) {// do somethingreturn 'function Demo() { return ... }';}/*** 运行时选项*/runtimeOpts: IDumiTechStackRuntimeOpts = {/*** IDemoCancelableFn函数所在的模块路径* 操作(挂载/卸载)第三方框架组件*/rendererPath: '',/*** 运行时编译功能模块的路径*/compilePath: '',/*** 该技术堆栈的运行时插件的路径*/pluginPath: '',};/*** 生成 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
增加额外的 Babel 插件。传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
api.addBeforeBabelPlugins(() => {// 返回一个 Babel 插件(来源于 Babel 官网的例子)return () => {visitor: {Identifier(path) {const name = path.node.name;path.node.name = name.split("").reverse().join("");}}}})
增加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集( presets )或插件集数组。
api.addBeforeBabelPresets(() => {// 返回一个 Babel 插件集return () => {return {plugins: ["Babel_Plugin_A","Babel_Plugin_B"]}}})
在 webpack-dev-middleware 之前添加中间件。传入的 fn 不需要参数,且需要返回一个 express 中间件或其数组。
api.addBeforeMiddlewares(() => {return (req, res, next) => {if(false) {res.end('end');}next();}})
在入口文件的最后面添加代码(render 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCode(() => `console.log('I am after render!')`);
在入口文件的最前面添加代码(render 前,import 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCodeAhead(() => `console.log('I am before render!')`)
在入口文件中添加 import 语句 (import 最后面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string}
或其数组。
api.addEntryImports(() => ({source: '/modulePath/xxx.js',specifier: 'moduleName'}))
在入口文件中添加 import 语句 (import 最前面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string}
或其数组。
api.addEntryImportsAhead(() => ({source: 'anyPackage'}))
添加额外的 Babel 插件。 传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
添加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集或其数组。
往 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')`)
往 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}
往 HTML 里添加 Meta 标签。 传入的 fn 不需要参数,返回的对象或其数组接口如下:
{content?: string,'http-equiv'?: string,name?: string,scheme?: string}
往 HTML 尾部添加 Script。 传入的 fn 不需要参数,返回的对象接口同 addHTMLHeadScripts
往 HTML 里添加 Style 标签。 传入的 fn 不需要参数,返回一个 string (style 标签里的代码)或者 { type?: string, content?: string }
,或者它们的数组。
添加全局 layout 组件。 传入的 fn 不需要参数,返回 { id?: string, file: string }
添加中间件,在 route 中间件之后。 传入的 fn 不需要参数,返回 express 中间件。
添加补丁 import,在整个应用的最前面执行。 传入的 fn 不需要参数,返回 { source: string, specifier?:string }
添加运行时插件,传入的 fn 不需要参数,返回 string ,表示插件的路径。
添加运行时插件的 Key, 传入的 fn 不需要参数,返回 string ,表示插件的路径。
添加监听路径,变更时会重新生成临时文件。传入的 fn 不需要参数,返回 string,表示要监听的路径。
添加按需安装的依赖,他们会在项目启动时检测是否安装:
api.addOnDemandDeps(() => [{ name: '@swc/core', version: '^1.0.0', dev: true }])
通过 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
新增)修改 app 元数据。传入的 fn 接收 appData 并且返回它。
api.modifyAppData((memo) => {memo.foo = 'foo';return memo;})
修改配置,相较于用户的配置,这份是最终传给 dumi 使用的配置。传入的 fn 接收 config 作为第一个参数,并且返回它。另外 fn 可以接收 { paths }
作为第二个参数。paths
保存了 dumi 的各个路径。
api.modifyConfig((memo, { paths }) => {memo.alias = {...memo.alias,'@': paths.absSrcPath}return memo;})
修改默认配置。传入的 fn 接收 config 并且返回它。
修改 HTML,基于 cheerio 的 ast。传入的 fn 接收 cheerioAPI 并且返回它。另外 fn 还可以接收{ path }
作为它的第二个参数,该参数代表路由的 path
api.modifyHTML(($, { path }) => {$('h2').addClass('welcome');return $;})
修改 HTML 的 favicon 路径。 传入的 fn 接收原本的 favicon 路径(string 类型)并且返回它。
修改 paths,比如 absOutputPath、absTmpPath。传入的 fn 接收 paths 并且返回它。
paths 的接口如下:
paths:{cwd?: string;absSrcPath?: string;absPagesPath?: string;absTmpPath?: string;absNodeModulesPath?: string;absOutputPath?: string;}
修改 renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
修改 server renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
修改路由。 传入的 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;})
修改临时目录下的 tsconfig 文件内容。
api.modifyTSConfig((memo) => {memo.compilerOptions.paths['foo'] = ['bar'];return memo;});
修改 webpack 最终配置。传入的 fn 接收 webpack 的 Config 对象作为第一个参数并且返回它。另外 fn 还可以接收 { webpack, env }
作为第二个参数,其中 webpack 是 webpack 实例,env 代表当前环境。
api.modifyWebpackConfig((memo, { webpack, env }) => {// do somethingreturn memo;})
generate 之后,webpack / vite compiler 之前。传入的 fn 不接收任何参数。
提供在服务器内部执行所有其他中间件之前执行自定义中间件的能力, 这可以用来定义自定义处理程序, 例如:
api.onBeforeMiddleware(({ app }) => {app.get('/some/path', function (req, res) {res.json({ custom: 'response' });});});
build 完成时。传入的 fn 接收 { isFirstCompile: boolean, stats, time: number, err?: Error }
作为参数。
build 完成且 html 完成构建之后。
检查时,在 onStart 之前执行。传入的 fn 不接收任何参数
检查代码时。传入的 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[];}
检查 config 时。传入的 fn 接收 { config, userConfig }
作为参数,它们分别表示实际的配置和用户的配置。
检查 package.json 时。传入的 fn 接收 {origin?, current}
作为参数。它们的类型都是 package.json 对象
dev 完成时。传入的 fn 接收的参数接口如下:
args: {isFirstCompile: boolean;stats: any;time: number;}
生成临时文件时,随着文件变化会频繁触发,有缓存。 传入的 fn 接收的参数接口如下:
args: {isFirstTime?: boolean;files?: {event: string;path: string;} | null;}
匹配单个路由,可以修改路由,给路由打补丁
package.json 变更时。传入的 fn 接收 {origin?, current}
作为参数。它们的类型都是 package.json 对象
启动时。传入的 fn 不接收任何参数。
api.writeTmpFile()
的 type 参数的类型。
从 api 可以直接访问到的属性,这些属性有一部分来自于 service
命令行参数,这里去除了命令本身。
e.g.
$ dumi dev --foo
, args 为 { _:[], foo: true }
$ dumi g page index --typescript --less
, args 为 { _: [ 'page', 'index''], typescript: true, less: true }
最终的配置(取决于你访问的时机,可能是当前收集到的最终配置)
当前路径
即 process.env.NODE_ENV
可能有 development
、production
和 test
插件日志对象,包含 { log, info, debug, error, warn, profile }
,他们都是方法。其中 api.logger.profile
可用于性能耗时记录。
api.logger.profile('barId');setTimeout(() => {api.logger.profile('barId');})// profile - barId Completed in 6254ms
当前命令的名称,例如 $ dumi dev
, name
就是 dev
项目相关的路径:
absNodeModulesPath
,node_modules 目录绝对路径absOutputPath
,输出路径,默认是 ./distabsPagesPath
,pages 目录绝对路径absSrcPath
,src 目录绝对路径,需注意 src 目录是可选的,如果没有 src 目录,absSrcPath 等同于 cwdabsTmpPath
,临时目录绝对路径cwd
,当前路径注意: 注册阶段不能获取到。因此不能在插件里直接获取,要在 hook 里使用。
当前项目的 package.json
对象
当前项目的 package.json
的绝对路径。
当前插件的对象。
type
插件类型,有 preset 和 plugin 两种path
插件路径id
插件 idkey
插件 keyconfig
插件的配置enableBy
插件的启用方式注意: 注册阶段使用的 plugin 对象是你 describe
之前的对象。
dumi 的 Service
实例。通常不需要用到,除非你知道为什么。
用户的配置,从 .umirc
或 config/config
中读取的内容,没有经过 defaultConfig 以及插件的任何处理。可以在注册阶段使用。
api.applyPlugins()
的 type 参数的类型。包含
为 api.describe()
提供 config.onChange
的类型,目前包含两种:
插件的启用方式,包含三种:
dumi service 的运行阶段。有如下阶段: