本文介绍 umi 框架的一些深入概念,帮助大家理解背后的运行机制。
下图是 umi 的架构图,我们试着从此图来一步步理解 umi 。
基于路由
举个 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 首先会加载用户的配置和插件,然后基于配置或者目录,生成一份路由配置,再基于此路由配置,把 JS/CSS 源码和 HTML 完整地串联起来。用户配置的参数和插件会影响流程里的每个环节。
举个例子,比如以下目录:
+ src
+ layouts/index.js
+ pages
- a.js
- b.js
- 404.js
会生成路由配置如下:
{
component: 'layouts/index.js',
routes: [
{ path: '/a', exact: true, component: 'pages/a.js' },
{ path: '/b', exact: true, component: 'pages/b.js' },
{ component: 'pages/404.js' },
],
}
以及可运行的代码:
const routes = {
component: require('layouts/index.js'),
routes: [
{ path: '/a', exact: true, component: require('pages/a.js') },
{ path: '/b', exact: true, component: require('pages/b.js') },
{ component: require('pages/404.js') },
],
};
export default () =>
<Router history={window.g_history}>
{ renderRoutes(routes) }
</Router>
另外,HTML 也是一个很重要的环节,因为他才是真正的入口,js 和 css 都是在 HTML 里发起的。HTML 在 umi 里是一等公民,这意味着我们可以通过插件来调整他的输出,以便和各个后台系统对接,以及打通各种发布流程。
举个例子,我们要配置 webpack externals 掉 react 来优化构建产物,通常我们要做两步:
- 配置
externals: { react: 'window.React' }
- 在 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 的文档《插件开发》来了解更多。