# 微前端演进(一)
2020年7月,落地实践
- 投入生产中的微前端会遇到哪些问题?
- 用了qiankun框架能解决所有问题吗?
- 开发 -> 构建 -> 部署 整个流程如何实践?
- 子应用之间的通信如何解决?
微前端需要从运行时、构建时、部署时,多个方面考虑,qiankun 并不能解决所有的问题,这个框架的定位只是一个微前端加载器。所以引入框架之后,还要搭建一个支撑微前端的体系,从开发、构建、 部署各个方面。
# 代码仓库
首先在代码仓库上,我们的理念是尽量一人一仓库,代码责任到人(参考微服务的治理的理念)。原则上不允许直接修改其他人的仓库代码。这样做的目的也是为了划分职责和代码的边界清晰,以及模块之间的解耦。
那么首先要对于代码仓库的组织方式有一些了解,主要有以下三种形态:
- Monorepo;
- Git submodule 子模块;
- 独立仓库;
先说Monorepo, 实际就是基于lerna的多包管理模式,本质的思想是将多个包的代码放到一个仓库里面来管理。但是这种方式更适合一人负责多包的情况,更适合作npm包的管理和发布。对于业务模块来说,我们需要的是一人一仓库,隔离和解耦。
然后再说Git submodule,这样可以实现一人一仓库,对于子应用的开发者来说,只需要关注一个仓库即可。然后对于产品的主管理员或架构师来说,往往需要同时关注多个仓库,此时Git子模块就起到作用了,用一个主仓库来管理多个子仓库。
总结一下
- 对于子应用开发者来说,一人一仓库;
- 对于主应用开发者、架构师来说,可以使用Git submodule 做仓库的整合;
- Monorepo 并不适合我们;
# 构建部署
# 构建时
构建时 与技术中台的CI(持续集成)的流程整合,当代码仓库push时会触发构建;
微前端的构建部署与传统的SPA的构建部署的区别;
# 部署时
部署时 与技术中台体系整合,使用容器化部署。
- 主应用和子应用构建出SPA所需要的dist静态资源;
- 使用一个Nginx镜像作为基础镜像,Docker启动,每一个子应用都是一个独立的Docker
# 运行时
运行时的主角就是qiankun 框架了, qiankun
的定位是一个微前端的加载器,在上一篇微前端预研, 我们分析了其中的利弊,和我们即将采用的功能。
我们对qiankun 做了一层封装,发布一个自己内部的微前端运行库@h3yun/microfrontend。(封装的目的是防止与qiankun的API产生强关联)
yarn add @h3yun/microfrontend
# 主应用规范
微应用按照加载方式分为两种:路由子应用和手动加载子应用。
# 路由子应用
路由子应用直接查看qiankun官方文档 路由子应用。需要注意的是:
- 子应用需要指定
activeRule
属性,来匹配路由规则 - 需要一个提前挂载好的DOM元素来作为挂载容器。
一个坑
一个qiankun 框架的bug, 切换路由时可能会导致主项目与路由相关的css样式丢失。可查看下面的issue 并最好持续跟进,我们也用了issue中提到的临时解决方案。
# 手动加载子应用
qiankun 官方文档手动加载子应用
将qiankun
的api与Vue
, 实现手动加载子应用的组件。可在项目中查看,micro-app-loader.vue
;
下面给出一个最简版的。
<script>
// micro-app-loader.vue
import { loadMicroApp } from '@h3yun/microfrontend';
import { H3Loading } from '@h3/awesome-ui';
export default {
name: 'MicroAppLoader',
props: {
microConfig: {
type: Object,
required: true,
},
configuration: {
type: Object,
default () {
return {};
},
},
},
data () {
return {
microAppRef: null,
};
},
mounted () {
const appConfig = {
...this.microConfig,
container: this.$refs['micro-app'],
};
this.microAppRef = loadMicroApp(appConfig, {
singular: false,
...this.configuration,
});
},
render () {
return (
<div class="micro-app-container">
{this.mounted ? null : <H3Loading class="micro-app-loading" />}
<div ref="micro-app" class="micro-app"></div>
</div>
);
},
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 子应用规范
参考qiankun 的生命周期钩子, 在子应用的项目入口中。可查看qiankun 官方文档 微应用指南
// 注意所有的钩子函数都是异步的
export async function bootstrap (props) {
// 启动。脚本加载并运行完毕,并且子应用激活时触发。比如路由规则激活或者手动激活
// 注意启动钩子,每个子应用在声明周期中,只会执行一次。
}
export async function mount (props) {
// 激活挂载。子应用激活之后,在HTML Entry中,代表HTML内容挂载到容器元素上之后。
// 注意挂载钩子,在子应用激活时会多次挂载
}
export async function unmount () {
// 卸载。子应用的路由切出或者手动加载子应用手动卸载时调用
}
export async function update (props) {
// 更新钩子,这个钩子只在手动加载微应用时有效。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
实现qiankun的微应用加载还需要webpack的配合,打包导出umd模块。webpack 配置
# 沙箱
当初选择qiankun时看好的就是这个沙箱的功能,关于沙箱的原理可以参考下面文章:
# 跨模块通信
跨模块通信的几种方法:
# 通过props由主应用向子应用传值
利用qiankun框架,在子应用挂载的钩子中可以拿到props
对象;
// 主应用中
appRoutes([
{
name: 'todos',
activeRule: '/todos',
entry: 'index.html'
container: rootContainer,
// 向子应用传递props
props: { mountAt: 'root', baseRoute: '/todos', Modou: context.modou },
},
// ...
])
// 子应用中
export async function mount (props) {
// 拿到props
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 通过eventbus
eventbus封装在@h3yun/global-provider中,具体请查看@h3yun/global-provider;
# 通过globalState
通过globalState封装在@h3yun/global-provider中,具体请查看@h3yun/global-provider;
qiankun 内部也有一个内置的简单globalState, 但是没有响应式所以未启用。
# 本地开发
上文也说到我们是一人一仓库,所以子应用开发人员本地开发时,运行的只有自己的子应用。这样就要考虑开发时,如何与主框架以及其他子应用集成。
我们可以灵活利用微前端运行时加载的能力,将开发集群中正在运行的子应用服务替换为本地开发运行的服务。这样浏览器中只有自己开发的模块是本地代码,其他代码都是其他人构建好的代码。
这样本地开发的好处是:
- 代码时时刻刻都在进行集成,不同人的代码在时刻碰撞,免去了Git仓库分支合并的过程。
- 对于子应用开发者来说,更加聚焦;关注点分离
Wanted问题悬赏
根据开发反馈,子应用接入开发时,webpack热更新可能会失效。
原因是可能会接入多个本地开发子应用,此时容易出现热更新错乱的问题(因为热更新的脚本可没有沙箱隔离);
还可能有别的原因,如果你看到这个问题,并且有兴趣,可以研究一下webpack的热更新机制,加一些自定义的代码应该可以解决这个问题。
期待你的解决!!