# Plugin develop
# initialize plugin
You can create a umi plugin scaffold in a simple way with create-umi:
$ yarn create umi --plugin
In umi, the plugin is actually a JS module, you need to define a plugin initialization method and export by default. The following example:
export default (api, opts) => {
// your plugin code here
};
It should be noted that if your plugin needs to be published as an npm package, then you need to compile before publishing, to ensure that the code in the release is ES5 code.
The initialization method will receive two parameters, the first parameter api
, the interface provided by the umi to the plugin is exposed through it. The second parameter, opts
, is filled in by the user when initializing the plugin.
# Introduction to the plugin interface
All of umi's plugin interfaces are provided through the api when the plugin is initialized. Divided into the following categories:
- Environment variables, some environment variables that can be used in the plugin
- System-level variables, variables or constants exposed by some plug-in systems
- Tools API, some commonly used tool class methods
- System level API, some core methods exposed by plugin systems
- Event class API, key event points provided by some plugin systems
- Application class API, API for implementing plugin function requirements, there are two methods of direct call and function callback
Note: All APIs are used by the api.[theApiName]
method, and the internal APIs are uniformly prefixed with _
.
Here's a basic usage example:
export default (api, opts) => {
api.onOptionChange(() => {
api.rebuildTmpFiles();
});
};
# Plugin demo
The following is an example of plugin example code refer to umi-plugin-locale
plugin code. For a complete example, see [source code](https://github.com/umijs/umi/blob/master/packages/umi- Plugin-locale/src/index.js).
export default (api, opts = {}) => {
const { paths } = api;
// Linstening plugin options changes
api.onOptionChange((newOpts) => {
opts = newOpts;
api.rebuildTmpFiles();
});
// add Provider wrapper
api.addRendererWrapperWithComponent(join(__dirname, './locale.js'));
api.addRendererWrapperWithComponent(() => {
if (opts.antd) {
return join(__dirname, './locale-antd.js'));
}
});
// add watcher on locale files
api.addPageWatcher(
join(paths.absSrcPath, config.singular ? 'locale' : 'locales'),
);
};
# Plugins order
The execution order of the plugins depends on the plugins
configuration item configured by the user in the configuration file .umirc.js
or config config.js
. The dependent plugin umi will check the order of the plugins through the plugin's dependence
configuration. A warning is issued, but currently umi does not modify the order of the users.
When the plugin calls api.applyPlugins
to trigger the hooks of the plugin, the execution order of the hooks corresponds to the order of plugins
. The order in which hooks are concerned is determined by the corresponding hooks.
# Environmental variable
# NODE_ENV
process.env.NODE_ENV
, Distinguish between development and production
# System level variable
# config
configuration in .umirc.js
or config/config.js
.
# paths
- outputPath
- absOutputPath
- pagesPath
- absPagesPath
- tmpDirPath
- absTmpDirPath
- absSrcPath
- cwd: project root
- absNodeModulesPath
# routes
umi processed routing information. The format is as follows:
const routes = [
{
path: '/xxx/xxx',
component: '',
},
];
It is recommended to call
api.routes
in the Event class, because the routes information obtained by initialization may be different from that returned in the Event class.
# System level API
# registerPlugin
Register a plugin, usually used for plugin set.
const demoPlugin = require('./demoPlugin');
api.registerPlugin({
id: 'plugin-id',
apply: demoPlugin,
opts: {},
});
# registerMethod
Register a plugin method to add a new method to the plugin for use by other plugins.
// Type usually corresponds to the method name addXxx modifyXxx onXxx afterXxx beforeXxx
api.registerMethod('addDvaRendererWrapperWithComponent', {
type: api.API_TYPE.ADD
type: api.API_TYPE.EVENT
type: api.API_TYPE.MODIFY
apply: () => {} // for custom type
});
For plugin methods of type api.API_TYPE.ADD
, you should return an item or return a multiple of the array, or you can return an empty array, such as:
api.addHTMLMeta({
/* ... */
});
api.addHTMLMeta([
{
/* ... */
},
{
/* ... */
},
]);
api.addHTMLMeta(() => {
if (opt === 'h5') {
return {
/* ... */
};
}
return [];
});
The type is a plugin method of api.API_TYPE.EVENT
, you should pass in a function and don't need to return anything.
The plugin method of type api.API_TYPE.MODIFY
returns the modified content.
You can also use apply
to customize the processing function. Your registered method may be used by multiple plugins. When you call applyPlugins
, the return value of these plugins will be processed by the reduce function inside umi. The apply
function you define determines how applyPlugins
handles the result of multiple plugins as its return value. Usually three types of built-in can meet your needs.
# applyPlugins
Trigger a method registered by the app via registerMethod
.
// If the type is api.API_TYPE.ADD wrappers an array of values returned by each plugin
// EVENT wrapper returns undefined
// MODIFY returns the last modified value
const wrappers = api.applyPlugins('wrapDvaRendererWithComponent');
# restart
api.restart('why');
rerun umi dev
.
# rebuildTmpFiles
api.rebuildTmpFiles('config dva changed');
regenerate bootstrap file (entryFile), this is the most commonly used method, plugins such as dva and locale will be used.
# refreshBrowser
refresh browser.
# rebuildHTML
Trigger HTML rebuild.
# changePluginOption
Set the options of the plugin, such as when you need to pass the dva configuration of the plugin set to the dva plugin in the umi-plugin-react.
api.changePluginOption('dva-plugin-id', {
immer: true,
});
# registerCommand
Register the umi xxx command line, as in the umi internal help command.
api.registerCommand(
'help',
{
hide: true,
},
args => {
// more code...
},
);
# _registerConfig
Register a configuration item, system method, usually do not use.
api._registerConfig(() => {
return () => {
return {
name: 'dva',
validate: validate,
onChange(newConfig, oldConfig) {
api.setPluginDefaultConfig('umi-plugin-dva', config);
},
};
};
});
# _modifyCommand
Modify command name and args.
// A demo for modify block npmClient to cnpm:
api._modifyCommand(({ name, args }) => {
if (name === 'block') {
args.npmClient = args.npmClient || 'cnpm';
}
return { name, args };
});
# Tool class API
# log
api.log.success('Done');
api.log.error('Error');
api.log.error(new Error('Error'));
api.log.debug('Hello', 'from', 'L59');
api.log.pending('Write release notes for %s', '1.2.0');
api.log.watch('Recursively watching build directory...');
Output various types of logs.
# winPath
api.winPath('/path/to.js');
Convert the file path to a path compatible with window to add code such as require('/xxx/xxx.js')
.
# debug
Same as debug, to view all plug-in logs, add the environment variable DEBUG=umi-plugin: *
, and detailed debug can be done according to the plugin file path.
api.debug('msg');
# findJS
xxx -> xxx.js xxx.ts xxx.jsx xxx.tsx
# findCSS
xxx -> xxx.css xxx.less xxx.scss xxx.sass
# compatDirname
Look for the user project directory first, then find the plugin dependencies.
# Event class API
The event class API follows the naming convention of onXxxXxx, beforeXxx, afterXxx and receives a parameter as a callback function.
# beforeDevServer
Before dev server start.
# afterDevServer
After dev server start.
api.afterDevServer(({ serve, devServerPort }) => {
// You can get the actual port number of the service monitor here.
console.log(devServerPort);
});
# onStart
Triggered when umi dev
or umi build
start.
# onExit
Triggered when killing process or ctrl-c in umi dev
.
# onDevCompileDone
Triggered after umi dev
compilation is complete.
api.onDevCompileDone(({ isFirstCompile, stats }) => {});
# onOptionChange
Triggered when the configuration of the plugin changes.
export default (api, defaultOpts = { immer: false }) => {
let opts = defaultOpts;
api.onOptionChange(newOpts => {
opts = newOpts;
api.rebuildFiles();
});
};
# beforeBuildCompileAsync
Before Umi call af-webpack/build
for a compilation
api.beforeBuildCompileAsync(async () => {
yield delay(1000);
});
# onBuildSuccess
When the umi build
was successful. Mainly do some processing of the construction products.
api.onBuildSuccess(({ stats }) => {
// handle with stats
});
# onBuildSuccessAsync
The async version of onBuildSuccess.
api.onBuildSuccessAsync(async ({ stats }) => {
await delay(1000);
console.log(stats);
});
# onBuildFail
When the umi build
failed.
# onHTMLRebuild
Triggered when the HTML is rebuilt.
# onGenerateFiles
The routing file is triggered when the entry file is generated.
# onPatchRoute
Triggered when getting the configuration of a single route, you can modify the route configuration route
here. For example, you can add a component path to Routes
to add a layer of encapsulation to the route.
api.onPatchRoute({ route } => {
// route:
// {
// path: '/xxx',
// Routes: []
// }
})
# Application class API
For the application class API, there are two ways to use: direct calling and function callback.
direct calling:
api.addRendererWrapperWithComponent('/path/to/component.js');
function callback:
api.addRendererWrapperWithComponent(() => {
if (opts.antd) {
return '/path/to/component.js';
}
});
Below is the specific API.
# modifyDefaultConfig
set umi default configuration.
api.modifyDefaultConfig(memo => {
return {
...memo,
singular: true,
};
});
# addPageWatcher
add watching files.
api.addPageWatcher(['xxx.js', '*.mock.js']);
# addHTMLMeta
add meta in HTML.
# addHTMLLink
add link in HTML.
# addHTMLStyle
add tyle in HTML.
# addHTMLScript
Add a script at the bottom of the HTML.
api.addHTMLScript({
content: '',
src: '',
// ...attrs
});
# addHTMLHeadScript
Add a script to the HTML head.
# modifyHTMLChunks 2.1.0+
Modify chunks in HTML, default ['umi']
.
# modifyHTMLWithAST
Modify the HTML, based on cheerio.
Options:
- route, current route
- getChunkPath 2.2.0+, get the full path of chunk, including publicPath and hash
e.g.
api.modifyHTMLWithAST(($, { route, getChunkPath }) => {
$('head').append(`<script src="${getChunkPath('a.js')}"></script>`);
});
# modifyHTMLContext
Modify the environment parameters when html ejs is rendered.
api.modifyHTMLContext((memo, { route }) => {
return {
...memo,
title: route.title, // The title plugin for umi-plugin-react contains similar logic
};
});
# modifyPublicPathStr
Modify the runtime public path variable string, which represents the value of window.publicPath
.
api.modifyPublicPathStr('window.__self_injected_public_path__');
It will make window.publicPath = window.__self_injected_public_path__
.
# modifyRoutes
Modify the routing configuration.
api.modifyRoutes(routes => {
return routes;
});
The format of the route configuration is as follows:
const route = {
path: '/xxx',
component: '/path/to/component',
Routes: ['/permissionControl.js'],
};
exports.routes = [
{
path: '/xxx',
workspace: false,
},
];
//permissionControl.js
export class Control extends Component (props) => {
componentDidount() => {
if(props.route.workspace === false) {
window.AntdCloudNav.set()
}
}
}
# addEntryImportAhead
add import at the top of the entry file.
api.addEntryImportAhead({
source: '/path/to/module',
specifier: 'name', // module name with import, can be ignored
});
# addEntryPolyfillImports
Same as addEntryImportAhead
, but as a polyfill, so add it first.
# addEntryImport
Import module in the entry file.
api.addEntryImport({
source: '/modulePath/xxx.js',
specifier: 'moduleName',
});
# addEntryCodeAhead
Add code before render.
api.addEntryCodeAhead(`
console.log('addEntryCodeAhead');
`);
# addEntryCode
Add code after render.
# addRouterImport
Add a module import to the routing file.
# addRouterImportAhead
Add a module to the header of the routing file to introduce.
# addRendererWrapperWithComponent
Wrapper a component outside the
# addRendererWrapperWithModule
Excute a module before mount
# addUmiExports
import from 'umi'
// export all
// genarate:export * from 'dva';
api.addUmiExports([
{
exportAll: true,
source: 'dva',
},
]);
// export certain
// genarate:export { connect } from 'dva';
api.addUmiExports([
{
specifiers: ['connect'],
source: 'dva',
},
]);
// support alias
// genarate:export { default as dva } from 'dva';
api.addUmiExports([
{
specifiers: [{ local: 'default', exported: 'dva' }],
source: 'dva',
},
]);
# modifyEntryRender
modifyEntryRender
# modifyEntryHistory
modifyEntryHistory
# modifyRouteComponent
modifyRouteComponent
# modifyRouterRootComponent
modifyRouterRootComponent
# chainWebpackConfig
Modify webpack configuration via webpack-chain.
// demo
api.chainWebpackConfig(memo => {
return memo;
});
# modifyAFWebpackOpts
Modify the af-webpack configuration.
// demo
api.modifyAFWebpackOpts(memo => {
return memo;
});
# addMiddleware
Append middleware to the dev server.
# addMiddlewareAhead
Add middleware to the front of the development server.
# addMiddlewareBeforeMock
Add middleware before the mock.
# addMiddlewareAfterMock
Add middleware after the mock.
# addVersionInfo
Added version information, displayed in umi -v
or umi version
.
# addRuntimePlugin
Add a runtime plugin with parameters as the absolute path to the file.
e.g.
api.addRuntimePlugin(require.resolve('./app.js'));
Then in app.js
:
export function render(oldRender) {
setTimeout(oldRender, 1000);
}
This implements a 1 second delayed rendering application.
# addRuntimePluginKey
Add a runtime configurable item.
# writeTmpFile
Add a file to the tmp directory pages/.umi
.
api.writeTmpFile('dva.js', tplContent);
# getRoutes
Get the latest routes.
api.getRoutes();