工作中的很多我的项目都是基于 umi 开发的,所以最近学了一下 umi 的源码,对这个框架的好感又多了一些~。如果你也感兴趣的话,欢送跟我一起来学习 or 复习一下。
这篇文章会带你从我的项目运行开始切入,循序渐进地理解 umi 外围的局部。
咱们创立好 umi 我的项目之后,第一步个别是应用 yarn start
命令去运行它,执行的是 umi dev
,也就是 umi 命令,所以先来看看 umi 命令是怎么定义的。
上面提到的源码目录在 umi 的源码仓库 /packages 目录下。
umi 命令
umi 命令的定义在 /umi/bin
目录,默认执行 /umi/src/cli.ts
,逻辑是这样的:
1. 参数规范化
应用 yargs-parser
库解决命令行参数,解决 version
、help
命令的逻辑。
2.【dev】启动新的过程
这里说一下 dev
和 build
的区别,开发环境会额定启动新的过程来运行服务,并做一些事件监听、解决通信的工作。
这部分代码在 /umi/src/utils/fork.ts
,外围逻辑次要有三局部:
##### 2.1 解决端口号
默认端口是 8000,如果被占用会主动加 1。
##### 2.2 启动新的过程
应用 child_process
的 fork
创立新的子过程,执行的是 /umi/src/forkedDev.ts
,这个文件里的逻辑就是上面的第 3 步和第 4 步了。
##### 2.3 解决通信
创立好子过程之后,会监听这个子过程的事件来传递音讯,这里会解决两种事件:RESTART
重启 和 UPDATE_PORT
更新端口。
主过程(运行 umi 命令的以后过程)会监听退出等事件从而 kill 掉子过程以及触发插件的 onExit
事件。
3. 初始化 webpack
这部分代码在 /umi/src/initWebpack.ts
。
初始化之前会先去配置文件里找有没有 webpack5
或 mfsu
的配置,有的话初始化 webpack5,没有就初始化 webpack4。
这里提一个重要的点,umi 将相似 webpack 的依赖封装在了 deps
,之前的《Umi 4 设计思路》演讲里有提到中间商的概念,umi 会做一些预打包的工作来解决版本稳定性的问题。
4. 结构 Service 对象
终于讲到 umi 的外围局部了,代码很短但很重要。
await new Service({...params}).run({
name: 'dev', // or 'build'
args,
});
这里有个特地的解决,初始化应用的 Service
做了个小小的封装,默认内置了 preset-built-in
,这个 preset 就是 插件的扩大办法 的实现局部。
import {IServiceOpts, Service as CoreService} from '@umijs/core';
class Service extends CoreService {constructor(opts: IServiceOpts) {
...
super({
...opts,
presets: [require.resolve('@umijs/preset-built-in'),
...(opts.presets || []),
],
plugins: [require.resolve('./plugins/umiAlias'), ...(opts.plugins || [])],
});
}
}
plugin 是 umi 设计中十分重要的局部,插件化的思维使得 umi 能够轻松自如的管制流程和实现定制,这种思维有一个学术名称 微内核架构
。
微内核架构
这部分就不开展说啦,我也是查资料摘了一些重要的点,重点理解一下外围零碎的设计思维,Service
类就是 umi 的外围零碎。
外围类 Service
代码在 /core/src/Service/Service.ts
,这个类的构造非常简单,只有两局部:构造函数 和 run()
办法。
constructor 构造函数
初始化阶段的次要工作是收集配置,从这里能看进去作者是如此的心细,环境变量的优先级,配置文件的优先级,就连 page(s)
目录名这么细节的点都安顿的明明白白😂。
初始化的属性从图里能够清晰的看到,就不赘述了,这里只列出来了(我感觉)重要的局部。
初始化 presets 和 plugin 的同时,会把所有插件的信息记录下来,也就是 插件注册表
,以便于管理和运行插件。
run()
跑起来~
总结来说,这部分就是按生命周期后退:
- 初始化 presets 和 plugins
- 设置一些钩子
- 最初执行
runCommand()
运行 umi 命令相干的具体逻辑
Service 有 9 个生命周期,作用是后续插件内执行一些动作的判断根据。
export enum ServiceStage {
uninitialized,
constructor,
init,
initPresets,
initPlugins,
initHooks,
pluginReady,
getConfig,
getPaths,
run,
}
presets 和 plugins 的初始化逻辑是一样的,摘录几行重要的伪代码如下:
const api = this.getPluginAPI({id, key, service: this})
// 获取 PluginAPI 对象,用来传递给 plugin 自身,也就是插件的实现标准
this.registerPlugin(preset)
// 插件注册表,执行的代码是 this.plugins[preset.id] = preset
const {presets, plugins} = await this.applyAPI({api, apply: preset.apply})
// 执行插件外部的 apply 办法,即 return apply()(api)
PluginAPI
这个类里定义了 Plugin 的外围办法,umi 文档曾经介绍的很具体了。
applyPlugin()
插件执行的外围办法,参数中的 type
决定了执行插件的逻辑,add 和 modify 会增加或批改 initialValue,并在运行完后将后果返回,event 顾名思义作为事件存在。
tapable(webpack) 我也还在学习总结中,有优良的文章欢送举荐给我~。
runCommand()
这里次要看一下 dev
命令相干的逻辑,代码在 preset-built-in/src/plugins/commands/dev/dev.ts
。
preset-built-in
这是 umi 默认的插件集,实现的性能次要有五局部:
-
registorMethods
对立注册办法。
-
route
routes 配置的实现。
-
写临时文件
src/.umi
目录里的文件生成过程都在这里,包含我的项目运行的入口、路由、插件等等。 -
配置
umi 的文档 配置 里的实现。
-
commands
命令具体的实现逻辑,以及 webpack 配置批改相干。
就分享到这里吧,如有谬误欢送斧正。
参考
《蚂蚁前端研发最佳实际》文字稿
《Umi 4 设计思路 – 云谦》视频 & 文字版
《umi 源码》知乎专栏
umi 源码 - 幕布