PluginManager
代码位置/umi/src/client/plugin.ts,该方法交由浏览器运行
一个简单前端插件管理,使得使用方法和后台服务部分一致。例如我们在app.ts中export的方法都会注册到这里,用于干预运行时
// 一个简单前端插件管理,使得使用方法和后台服务部分一致
// 例如使用app.ts来干预运行时
export class PluginManager {
// 指定插件hooks的key必须在validKeys范围内
opts: { validKeys: string[] };
// 存储校验成功后的hook
hooks: {
[key: string]: any;
} = {};
constructor(opts: { validKeys: string[] }) {
this.opts = opts;
}
// 插入插件
// {plugin.apply的key, plugin的apply[key]的所有}
// apply[key]可以是数组,也可以直接返回属性
register(plugin: IPlugin) {
assert(plugin.apply, `plugin register failed, apply must supplied`);
Object.keys(plugin.apply).forEach((key) => {
// 必须在validKeys中注册过
assert(
this.opts.validKeys.indexOf(key) > -1,
`register failed, invalid key ${key} ${
plugin.path ? `from plugin ${plugin.path}` : ''
}.`,
);
this.hooks[key] = (this.hooks[key] || []).concat(plugin.apply[key]);
});
}
/**
* 获取符合keyWithDot的插件apply集合
* keyWithDot: a.b.c.d
* 某插件 apply: {a:{b:{c:{d: function fn1(){}}}}}
* 某插件 apply: {a:[{b:{c:{d: function fn2(){}}}}, {b:{c:{d: function fn3(){}}}}]}
* 此时返回的是[fn1, fn2, fn3]
*/
getHooks(keyWithDot: string) {
const [key, ...memberKeys] = keyWithDot.split('.');
let hooks = this.hooks[key] || [];
if (memberKeys.length) {
hooks = hooks
.map((hook: any) => {
try {
let ret = hook;
for (const memberKey of memberKeys) {
ret = ret[memberKey];
}
return ret;
} catch (e) {
return null;
}
})
.filter(Boolean);
}
return hooks;
}
// 依次执行hooks对应的方法
// 并将返回结果合并之后传递给下一个hook
// 可以异步执行
applyPlugins({
key,
type,
initialValue,
args,
async,
}: {
key: string;
type: ApplyPluginsType;
initialValue?: any;
args?: object;
async?: boolean;
}) {
const hooks = this.getHooks(key) || [];
// ...
}
// 创建PluginManager,并初始化插件
static create(opts: { validKeys: string[]; plugins: IPlugin[] }) {
const pluginManager = new PluginManager({
validKeys: opts.validKeys,
});
opts.plugins.forEach((plugin) => {
pluginManager.register(plugin);
});
return pluginManager;
}
}
模版代码分析
- plugin.ts: /preset-umi/templates/plugin.tpl
- umi.ts: /preset-umi/templates/umi.tpl
plugin
- 通过外部的path(app.ts的文件路径)获取到app.ts文件内的内容,然后成初始化插件
getValidKeys预设插件的运行时配置,我们熟悉的有render、onRouteChange等createPluginManager创建插件实例
umi
这块是前端入口文件,自执行render函数
- 创建插件对象
- 获取打平的路由列表,同时允许app.ts的patchRoutes中获取使用
- render的初始化传入一个函数,允许外部在合适的时机才渲染app
- 函数中初始化一些render配置,比如渲染根结点等
renderClient为packages/renderer-react的渲染函数,可以通过modifyRendererPath修改为renderer-vue
renderClient
渲染react应用,代码位置/renderer-react/src/browser.tsx
createClientRoutes: 创建带有层级的路由信息patchClientRoutes: 允许渲染前修改路由内容,增删改等- 依次调用如下方法,可以在渲染组件上层再嵌套组件,如Provider
for (const key of [
// Lowest to the highest priority
'innerProvider',
'i18nProvider',
'accessProvider',
'dataflowProvider',
'outerProvider',
'rootContainer',
]) {
rootContainer = opts.pluginManager.applyPlugins({
type: 'modify',
key: key,
initialValue: rootContainer,
args: {},
});
}
- Browser中监听路由变化去加载对应的文件
- 随后将Browser挂在到rootElement上