最近在做性能优化相关的事情,也比较好奇umi2,umi3这套机制是如何运行的,怎么更好的做性能优化。

umi2和umi3都是一个编译时框架,即先会对本地代码做预编译,然后再交给webpack等打包工具,不然webpack无法直接处理。支付宝小程序也是这样。

干货分享:蚂蚁金服前端框架和工程化实践语言 & 开发陈成_InfoQ精选文章 路由实现

整体是如何构成的

  1. config/routes中写了路由的配置,支持树结构的嵌套
  2. 预编译成src/.umi/core/routes 文件
  3. webpack打包,会打包出多个chunks,
  4. 运行时 👍👍 重点

几个问题

  • 为什么上一个layout执行完,才会加载下一个命中层级的资源,背后的原理是什么?
    • 猜测children是对原组件做了wrapper,背后包含loadable,即要子components先执行load(即去加载资源),执行完才初始化

TODO

  • 弄清楚整个接口
  • 弄清楚整个运行时结构
  • 组件的dynamic代码,还是移动到运行时把?再详细和清晰化一些
  • 为何Router不直接使用,有几种不同的runtime? 服务端的?

1 文件编写

  1. path
  2. wrappers
  3. component
  4. // 即子路由
  5. routes

2 预编译

  1. import { ApplyPluginsType, dynamic } from '/Users/admin/devspace/umiapp3/node_modules/umi/node_modules/@umijs/runtime';
  2. export function getRoutes() {
  3. const routes = [
  4. {"path": "/sub-page",
  5. // 父路由,一般定义layout
  6. // 注释定义了chunk name
  7. // dynamic则在下面的4运行时中讲
  8. "component": dynamic({ loader: () => import(/* webpackChunkName: 'p__layouts__index' */'/Users/admin/devspace/umiapp3/src/pages/layouts/index'), loading: LoadingComponent}),
  9. "name": "",
  10. // 子路由
  11. "routes": [
  12. {
  13. "path": ""
  14. "name":
  15. "component"
  16. }
  17. ]
  18. }
  19. ];
  20. // allow user to extend routes
  21. plugin.applyPlugins({
  22. key: 'patchRoutes',
  23. type: ApplyPluginsType.event,
  24. args: { routes },
  25. });
  26. return routes;
  27. }

umi.ts的运行核心

  1. import { getRoutes } from './core/routes';
  2. const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({
  3. key: 'render',
  4. type: ApplyPluginsType.compose,
  5. initialValue: () => {
  6. const opts = plugin.applyPlugins({
  7. key: 'modifyClientRenderOpts',
  8. type: ApplyPluginsType.modify,
  9. initialValue: {
  10. routes: args.routes || getRoutes(),
  11. plugin,
  12. history: createHistory(args.hot),
  13. isServer: process.env.__IS_SERVER,
  14. dynamicImport: true,
  15. rootElement: 'root',
  16. },
  17. });
  18. return renderClient(opts);
  19. },
  20. args,
  21. });

dynamic({loader, loading});

dynamic -> Loadable -> createLoadableComponent(执行加载代码,并根据加载的状态,渲染loading或者加载完成的组件,或者null)

3 编译

  1. component: dynamic({
  2. loader: function loader(){
  3. return Promise.all([
  4. __webpack_require__.e('chunk1'),
  5. __webpack_require__.e('chunk2'),
  6. __webpack_require__.e('p_chunkName'),
  7. ]).then(__webpack_require__.bind(__webpack_require__, "./src/");
  8. },
  9. loading:
  10. })

4 运行时 如何递归树层级的路由 👍👍

  • Layout props.children即是子组件?是谁计算出来的?貌似是一个<Switch />组件

plugin-layout -> 递归方式,把路由配置的item.children赋值为item.routes,帮助在layout中执行props.children渲染子组件

  • renderClient 负责组合umi的optsreact-router-configreact-router中的Router提供__RouterContext以及@umi/runtime 并在此变更document.title

使用了reactRouterConfig.matchRoutes并未使用renderRoutes

  • React的树结构是如何的?通过Switch传入下个层次的route配置实现了递归。
  1. <RouterComponent> 路由的根组件,只有一个
  2. <Router> runtime的router,也即来自react-router-dom的Router,自umi顶部传入进来的opts.history
  3. <Switch> /**props [Routes] 根据传入的routes,执行match,render不同的Component.**/
  4. <Route>
  5. <runtime.__RouterContext.Consumer> // 消费上一级别的Consumer
  6. <runtime.__RouterContext.Provider> // 提供给下一级别的Provider,新的计算后的props,和Formily的每个层级有点像,基于上一层不断递进的层级。
  7. <xxxLayout> 第一层级,props.children为Switch组件
  8. <Switch>
  9. <Route>
  10. <xxxPage /> 第二层级,
  11. </Route>
  12. </Switch>
  13. </xxxLayout>
  14. </runtime.__RouterContext.Provider>
  15. </runtime.__RouterContext.Consumer>
  16. </Route>
  17. </Switch>
  18. </Router>
  19. </RouterComponent>

renderRoutes将路由的routes配置,转换为React的树结构

  • 组件的动态加载,dynamic

dynamic有两个参数,loader和loading

dynamic作为一个组件,负责异步加载loader的加载,并且在不同状态时输出不同内容,loading的展示,包加载完成后则展示真正的组件。

  1. component: dynamic({
  2. loader: function loader(){
  3. return Promise.all([
  4. __webpack_require__.e('chunk1'),
  5. __webpack_require__.e('chunk2'),
  6. __webpack_require__.e('p_chunkName'),
  7. ]).then(__webpack_require__.bind(__webpack_require__, "./src/");
  8. },
  9. loading:
  10. })

整个umi的运行时核心和插件机制

.umi/umi.ts 入口文件
@umijs/renderer-react 这个做什么?针对umi的opts配置来渲染,opts.routes等结合react-router-config.
@umi/runtime

  1. import { getRoutes } from './core/routes';
  2. const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({
  3. key: 'render',
  4. // 插件类型compose,还有modify,event
  5. type: ApplyPluginsType.compose,
  6. initialValue: () => {
  7. // 插件
  8. const opts = plugin.applyPlugins({
  9. key: 'modifyClientRenderOpts',
  10. type: ApplyPluginsType.modify,
  11. initialValue: {
  12. routes: args.routes || getRoutes(),
  13. plugin,
  14. history: createHistory(args.hot),
  15. isServer: process.env.__IS_SERVER,
  16. dynamicImport: true,
  17. rootElement: 'root',
  18. },
  19. });
  20. return renderClient(opts);
  21. },
  22. args,
  23. });
  24. const clientRender = getClientRender();
  25. 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