乐趣区

关于前端:umi3-源码学习

工作中的很多我的项目都是基于 umi 开发的,所以最近学了一下 umi 的源码,对这个框架的好感又多了一些~。如果你也感兴趣的话,欢送跟我一起来学习 or 复习一下。

这篇文章会带你从我的项目运行开始切入,循序渐进地理解 umi 外围的局部。


咱们创立好 umi 我的项目之后,第一步个别是应用 yarn start 命令去运行它,执行的是 umi dev,也就是 umi 命令,所以先来看看 umi 命令是怎么定义的。

 上面提到的源码目录在 umi 的源码仓库 /packages 目录下。

umi 命令

umi 命令的定义在 /umi/bin 目录,默认执行 /umi/src/cli.ts,逻辑是这样的:

1. 参数规范化

应用 yargs-parser 库解决命令行参数,解决 versionhelp 命令的逻辑。

2.【dev】启动新的过程

这里说一下 devbuild 的区别,开发环境会额定启动新的过程来运行服务,并做一些事件监听、解决通信的工作。

这部分代码在 /umi/src/utils/fork.ts,外围逻辑次要有三局部:

##### 2.1 解决端口号

默认端口是 8000,如果被占用会主动加 1。

##### 2.2 启动新的过程

应用 child_processfork 创立新的子过程,执行的是 /umi/src/forkedDev.ts,这个文件里的逻辑就是上面的第 3 步和第 4 步了。

##### 2.3 解决通信

创立好子过程之后,会监听这个子过程的事件来传递音讯,这里会解决两种事件:RESTART 重启 和 UPDATE_PORT 更新端口。

主过程(运行 umi 命令的以后过程)会监听退出等事件从而 kill 掉子过程以及触发插件的 onExit 事件。

3. 初始化 webpack

这部分代码在 /umi/src/initWebpack.ts

初始化之前会先去配置文件里找有没有 webpack5mfsu 的配置,有的话初始化 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()

跑起来~

总结来说,这部分就是按生命周期后退:

  1. 初始化 presets 和 plugins
  2. 设置一些钩子
  3. 最初执行 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 默认的插件集,实现的性能次要有五局部:

  1. registorMethods

    对立注册办法。

  2. route

    routes 配置的实现。

  3. 写临时文件

    src/.umi 目录里的文件生成过程都在这里,包含我的项目运行的入口、路由、插件等等。

  4. 配置

    umi 的文档 配置 里的实现。

  5. commands

    命令具体的实现逻辑,以及 webpack 配置批改相干。


就分享到这里吧,如有谬误欢送斧正。

参考

《蚂蚁前端研发最佳实际》文字稿

《Umi 4 设计思路 – 云谦》视频 & 文字版

《umi 源码》知乎专栏

umi 源码 - 幕布

退出移动版