前言
前面几篇文章,我们分析了umijs
「核心」Service
「类的初始化流程」、「插件化的核心流程」以及「构建阶段」dev
「命令的执行流程」。那我们今天继续分析项目在「运行时阶段」又会做哪些事呢?
在开始今天的文章之前,大家不妨想几个问题:
「自动生成的入口文件」和我们平时自己写的入口文件有什么不一样?
项目中使用的插件(例如:
plugin-model
、plugin-request
等插件)又是「如何注册到项目」中的?在运行时阶段,如何「动态修改路由」或者如何「重写」
render
「方法」?umijs
生成的「临时文件通过其他构建工具」(不限于webpack
、rollup
、esbuild
)可以跑起来吗?
入口文件
在前面我们提到了在解析preset-built-in
预设阶段会批量导入generate files
相关的plugin
,同时在这些plugin
中注册onGenerateFiles
钩子,然后在webpack
编译前触发,生成临时文件。
接下来,我们从入口文件出发,看下通过执行umi dev
命令生成的入口文件内容:
// path: src/.umi/umi.ts
// @ts-nocheck
import './core/polyfill';
import '@@/core/devScripts';
import { plugin } from './core/plugin';
import './core/pluginRegister';
import { createHistory } from './core/history';
import { ApplyPluginsType } from '~/umi-test/node_modules/@umijs/runtime';
import { renderClient } from '~/umi-test/node_modules/@umijs/renderer-react';
import { getRoutes } from './core/routes';
const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({
key: 'render',
type: ApplyPluginsType.compose,
initialValue: () => {
const opts = plugin.applyPlugins({
key: 'modifyClientRenderOpts',
type: ApplyPluginsType.modify,
initialValue: {
routes: args.routes || getRoutes(),
plugin,
history: createHistory(args.hot),
isServer: process.env.__IS_SERVER,
rootElement: 'root',
defaultTitle: ``,
},
});
return renderClient(opts);
},
args,
});
const clientRender = getClientRender();
export default clientRender();
window.g_umi = {
version: '3.5.14',
};
// hot module replacement
...
首先可以看到文件的顶部导入了polyfill
文件,也就是我们平时自己开发项目导入的babel
相关的polyfill
文件。
❝当然在
❞umi-next
版本已经在尝试用swc
代替babel
,感兴趣的小伙伴可以自行查阅umi-next
的相关issues
。
// path: src/.umi/umi.ts
// @ts-nocheck
import 'core-js';
import 'regenerator-runtime/runtime';
export {};
接下来我们继续往下分析,通过执行getClientRender
函数,返回clientRender
。在getClientRender
函数内部我们看到了熟悉的面孔--plugin
,运行时阶段同样通过插件化返回渲染需要的render
方法。
值得注意的是这里的Plugin
是有别于前面提到的PluginAPI
,PluginAPI
是在作用于编译阶段,而Plugin
是作用于运行时的插件。
接下来,我们看下运行时插件是怎么实现的。
运行时插件
❝阅读源码最好的出入点就是从它的测试用例出发。测试用例是题干,源码就是答案。
❞------加夫列尔·加西亚·马尔波斯
「接下来我们从不同方向出发,更好的了解运行时插件机制的原理及实现。」
从Plugin的测试用例出发
接下来,我们看下几个plugin
的「测试用例」:
实例化时设置可允许注册的
key
,同时在register
时会校验key
是否允许
test('invalid key', () => {
const p = new Plugin({
validKeys: [],
});
expect(() => {
p.register({
apply: { foo: 1 },
path: '/foo.js',
});
}).toThrow(/invalid key foo from plugin \/foo.js/);
});
通过
getHooks
方法可获取指定