最近在做性能优化相关的事情,也比较好奇umi2,umi3这套机制是如何运行的,怎么更好的做性能优化。
umi2和umi3都是一个编译时框架,即先会对本地代码做预编译,然后再交给webpack等打包工具,不然webpack无法直接处理。支付宝小程序也是这样。
干货分享:蚂蚁金服前端框架和工程化实践语言 & 开发陈成_InfoQ精选文章 路由实现
整体是如何构成的
config/routes
中写了路由的配置,支持树结构的嵌套- 预编译成
src/.umi/core/routes
文件 - webpack打包,会打包出多个chunks,
- 运行时 👍👍 重点
几个问题
- 为什么上一个layout执行完,才会加载下一个命中层级的资源,背后的原理是什么?
- 猜测children是对原组件做了wrapper,背后包含loadable,即要子components先执行load(即去加载资源),执行完才初始化
TODO
- 弄清楚整个接口
- 弄清楚整个运行时结构
- 组件的dynamic代码,还是移动到运行时把?再详细和清晰化一些
- 为何Router不直接使用,有几种不同的runtime? 服务端的?
1 文件编写
path
wrappers
component
// 即子路由
routes
2 预编译
import { ApplyPluginsType, dynamic } from '/Users/admin/devspace/umiapp3/node_modules/umi/node_modules/@umijs/runtime';
export function getRoutes() {
const routes = [
{"path": "/sub-page",
// 父路由,一般定义layout
// 注释定义了chunk name
// dynamic则在下面的4运行时中讲
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__layouts__index' */'/Users/admin/devspace/umiapp3/src/pages/layouts/index'), loading: LoadingComponent}),
"name": "",
// 子路由
"routes": [
{
"path": ""
"name":
"component"
}
]
}
];
// allow user to extend routes
plugin.applyPlugins({
key: 'patchRoutes',
type: ApplyPluginsType.event,
args: { routes },
});
return routes;
}
umi.ts的运行核心
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,
dynamicImport: true,
rootElement: 'root',
},
});
return renderClient(opts);
},
args,
});
dynamic({loader, loading});
dynamic -> Loadable -> createLoadableComponent(执行加载代码,并根据加载的状态,渲染loading或者加载完成的组件,或者null)
3 编译
component: dynamic({
loader: function loader(){
return Promise.all([
__webpack_require__.e('chunk1'),
__webpack_require__.e('chunk2'),
__webpack_require__.e('p_chunkName'),
]).then(__webpack_require__.bind(__webpack_require__, "./src/");
},
loading:
})
4 运行时 如何递归树层级的路由 👍👍
- Layout
props.children
即是子组件?是谁计算出来的?貌似是一个<Switch />
组件
plugin-layout -> 递归方式,把路由配置的item.children赋值为item.routes,帮助在layout中执行props.children渲染子组件
- renderClient 负责组合umi的
opts
和react-router-config
,react-router
中的Router提供__RouterContext
以及@umi/runtime
并在此变更document.title
使用了reactRouterConfig.matchRoutes
并未使用renderRoutes
- React的树结构是如何的?通过Switch传入下个层次的route配置实现了递归。
<RouterComponent> 路由的根组件,只有一个
<Router> runtime的router,也即来自react-router-dom的Router,自umi顶部传入进来的opts.history
<Switch> /**props [Routes] 根据传入的routes,执行match,render不同的Component.**/
<Route>
<runtime.__RouterContext.Consumer> // 消费上一级别的Consumer
<runtime.__RouterContext.Provider> // 提供给下一级别的Provider,新的计算后的props,和Formily的每个层级有点像,基于上一层不断递进的层级。
<xxxLayout> 第一层级,props.children为Switch组件
<Switch>
<Route>
<xxxPage /> 第二层级,
</Route>
</Switch>
</xxxLayout>
</runtime.__RouterContext.Provider>
</runtime.__RouterContext.Consumer>
</Route>
</Switch>
</Router>
</RouterComponent>
renderRoutes将路由的routes配置,转换为React的树结构
- 组件的动态加载,dynamic
dynamic有两个参数,loader和loading
dynamic作为一个组件,负责异步加载loader的加载,并且在不同状态时输出不同内容,loading的展示,包加载完成后则展示真正的组件。
component: dynamic({
loader: function loader(){
return Promise.all([
__webpack_require__.e('chunk1'),
__webpack_require__.e('chunk2'),
__webpack_require__.e('p_chunkName'),
]).then(__webpack_require__.bind(__webpack_require__, "./src/");
},
loading:
})
整个umi的运行时核心和插件机制
.umi/umi.ts
入口文件@umijs/renderer-react
这个做什么?针对umi的opts配置来渲染,opts.routes等结合react-router-config.
@umi/runtime
import { getRoutes } from './core/routes';
const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({
key: 'render',
// 插件类型compose,还有modify,event
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,
dynamicImport: true,
rootElement: 'root',
},
});
return renderClient(opts);
},
args,
});
const clientRender = getClientRender();
export default clientRender();
有哪些插件事件?
- render
- modifyClientRenderOpts
一些插件的运行
- plugin-antd-icon-config -> patchRoutes 针对icon的配置,patch为antdIcon的element
- plugin-layout -> 递归方式,把路由配置的item.children赋值为item.routes,帮助在layout中执行props.children渲染子组件
插件哪几种组合类型?compose, modify, event