umi 是一个基于插件和插件机的架构,所有模块和功能通过插件的方式组合在一起,架构清晰、可插拔、扩展性好。类似有插件或者插件集的代码组织方法还有很多项目在用,比如 babel、webpack、egg等等。
插件入口
代码比较简单,引入一些实参
- cwd 根目录(命令执行所在的目录)
- pkg 根 pacKage.json
- plugins是插件,包含必须的插件
generatePlugin和从外部引入的插件 plugins。这个在《02.从一个命令说起 umi dev》的 service中有解释 - presets 是插件集,包含在《02.从一个命令说起 umi dev》的 service中引入的
@umijs/preset-umi和servicePlugin
const { plugins, presets } = Plugin.getPluginsAndPresets({
cwd: this.cwd,
pkg,
plugins: [require.resolve('./generatePlugin')].concat(
this.opts.plugins || [],
),
presets: [require.resolve('./servicePlugin')].concat(
this.opts.presets || [],
),
userConfig: this.userConfig,
prefix,
});
插件的核心方法
稍微复杂一点,但是也很好理解。主要是获取分散在各处的插件或者插件机,然后实例化。
static getPluginsAndPresets(opts: {
cwd: string;
pkg: any;
userConfig: any;
plugins?: string[];
presets?: string[];
prefix: string;
}) {
function get(type: 'plugin' | 'preset') {
const types = `${type}s` as 'plugins' | 'presets';
return [
// opts getPluginsAndPresets实参传进来的 presets 或者 plugins
...(opts[types] || []),
// env 环境变量中传进来的 presets 或者 plugins 例如:umi_plugins=xxx
...(process.env[`${opts.prefix}_${types}`.toUpperCase()] || '')
.split(',')
.filter(Boolean),
// dependencies
// ...Object.keys(opts.pkg.devDependencies || {})
// .concat(Object.keys(opts.pkg.dependencies || {}))
// .filter(Plugin.isPluginOrPreset.bind(null, type)),
// user config .umrc.ts 中的 plugins:[] 或者 presets: [],
...(opts.userConfig[types] || []),
].map((path) => {
assert(
typeof path === 'string',
`Invalid plugin ${path}, it must be string.`,
);
let resolved;
// 这一步是为了获取插件所在的路径
try {
resolved = resolve.sync(path, {
basedir: opts.cwd,
extensions: ['.tsx', '.ts', '.mjs', '.jsx', '.js'],
});
} catch (_e) {
throw new Error(`Invalid plugin ${path}, can not be resolved.`);
}
return new Plugin({
path: resolved,
type,
cwd: opts.cwd,
});
});
}
return {
presets: get('preset'),
plugins: get('plugin'),
};
}
我们看返回值 return {presets: get('preset'), plugins: get('plugin'),},重点就是get方法,通过get 方法处理插件和插件机。最终通过 new Plugin() 方法实例化插件。
我们先做一个简单的总结,稍后再看 new Plugin()
综合《02.从一个命令说起 umi dev》的 service中和本篇介绍,umi中的插件来源有以下几个方面
- .umirc.ts中的 plugins或者 presets
- 项目根目录下的 plugins.js 或者 plugins.ts 文件
- 命令行运行时获取的命令 例如:umi_plugins=xxx 或者 umi_presets=xxx
- 还有
generatePlugin``@umijs/preset-umi和servicePlugin插件,是umi自身需要的插件或者插件集,用户无法手动配置。
new Plugin()
首先我们要知道new 一个对象时,对象中的 constructor方法会在 new 的时候执行,初始化一些信息。这里面有个 this.apply 方法。初始化的时候获取了插件对应的代码内容。__esModule 是一个市面上统一遵守的模块化打包标识,为了解决 Commonjs 的 module.exports 与 ESM 对应问题。默认使用 esModule。apply返回的 ret.default 或者 ret 其实就是插件带具体代码。因为umi的插件都是方法,所以执行 this.apply() 的返回值,我们可以理解为是一个函数,函数的内容就是插件本身。后面会用到这个 apply方法,这个后面再说。
this.apply = () => {
register.register({
implementor: esbuild,
exts: ['.ts', '.mjs'],
});
register.clearFiles();
let ret;
try {
ret = require(this.path);
} catch (e: any) {
throw new Error(
`Register ${this.type} ${this.path} failed, since ${e.message}`,
);
} finally {
register.restore();
}
// use the default member for es modules
return ret.__esModule ? ret.default : ret;
};