本文介绍 umi 框架的一些深入概念,帮助大家理解背后的运行机制。

下图是 umi 的架构图,我们试着从此图来一步步理解 umi 。

深入理解 umi - 图1

基于路由

举个 SPA 的例子,比如我们访问 /users,会由 ./src/pages/users.js 决定具体渲染什么,按我们的理解,这其中 /users 是路由,./src/pages/users.js 是路由组件,他们俩组成了一个路由配置,然后多个路由配置又形成了一个完整的应用。不难发现,在这个应用里,路由即入口。

umi 是基于路由的,所以具备了管理入口的能力。你甚至可以简单地理解为 umi = 路由 + webpack,当然我们在此基础上做了很多额外的工作。然后,管理了入口之后,能做的事情就很多了。

比如:

  • 开发时按需编译
  • 运行时按需加载,做 code-splitting
  • 智能提取公共代码,加速用户访问,通常是被 路由数/2 引用的模块才被提取到公共代码中
  • 服务端渲染
  • 基于路由的埋点
  • 基于约定,如果 ./src/pages/404.js 存在则添加为 fallback 路由

这里有很大的想象空间。

从源码到上线的生命周期管理

市面上的框架基本都是从源码到构建产物,很少会考虑到各种发布流程,而 umi 则多走了这一步。

下图是 umi 从源码到上线的一个流程。

深入理解 umi - 图2

umi 首先会加载用户的配置和插件,然后基于配置或者目录,生成一份路由配置,再基于此路由配置,把 JS/CSS 源码和 HTML 完整地串联起来。用户配置的参数和插件会影响流程里的每个环节。

举个例子,比如以下目录:

  1. + src
  2. + layouts/index.js
  3. + pages
  4. - a.js
  5. - b.js
  6. - 404.js

会生成路由配置如下:

  1. {
  2. component: 'layouts/index.js',
  3. routes: [
  4. { path: '/a', exact: true, component: 'pages/a.js' },
  5. { path: '/b', exact: true, component: 'pages/b.js' },
  6. { component: 'pages/404.js' },
  7. ],
  8. }

以及可运行的代码:

  1. const routes = {
  2. component: require('layouts/index.js'),
  3. routes: [
  4. { path: '/a', exact: true, component: require('pages/a.js') },
  5. { path: '/b', exact: true, component: require('pages/b.js') },
  6. { component: require('pages/404.js') },
  7. ],
  8. };
  9. export default () =>
  10. <Router history={window.g_history}>
  11. { renderRoutes(routes) }
  12. </Router>

另外,HTML 也是一个很重要的环节,因为他才是真正的入口,js 和 css 都是在 HTML 里发起的。HTML 在 umi 里是一等公民,这意味着我们可以通过插件来调整他的输出,以便和各个后台系统对接,以及打通各种发布流程。

举个例子,我们要配置 webpack externals 掉 react 来优化构建产物,通常我们要做两步:

  1. 配置 externals: { react: 'window.React' }
  2. 在 html 里引入 https://unpkg.com/react@16.4.1/cjs/react.production.min.js

这里的问题是一个功能需要在两个地方维护并且一一对应。然后在 umi 里,由于 HTML 具备插件的能力,所以可以做到你只需要完成第一步,然后 umi 自动帮你做第二步。

最后,通过 umi 可以把各种部署方式封装成插件,实现同一份源码,装载不同的插件,就可以部署到不同平台。比如 umi + umi-plugin-deploy-offline 可以部署为离线包;umi + umi-plugin-deploy-chair 可以部署到 chair 系统。

插件机制

关于 umi 的插件机制你可以阅读 umi 的文档《插件开发》来了解更多。