# 微前端演进(一)

2020年7月,落地实践

  • 投入生产中的微前端会遇到哪些问题?
  • 用了qiankun框架能解决所有问题吗?
  • 开发 -> 构建 -> 部署 整个流程如何实践?
  • 子应用之间的通信如何解决?

微前端需要从运行时、构建时、部署时,多个方面考虑,qiankun 并不能解决所有的问题,这个框架的定位只是一个微前端加载器。所以引入框架之后,还要搭建一个支撑微前端的体系,从开发、构建、 部署各个方面。

# 代码仓库

首先在代码仓库上,我们的理念是尽量一人一仓库,代码责任到人(参考微服务的治理的理念)。原则上不允许直接修改其他人的仓库代码。这样做的目的也是为了划分职责和代码的边界清晰,以及模块之间的解耦。

那么首先要对于代码仓库的组织方式有一些了解,主要有以下三种形态:

先说Monorepo, 实际就是基于lerna的多包管理模式,本质的思想是将多个包的代码放到一个仓库里面来管理。但是这种方式更适合一人负责多包的情况,更适合作npm包的管理和发布。对于业务模块来说,我们需要的是一人一仓库,隔离和解耦。

然后再说Git submodule,这样可以实现一人一仓库,对于子应用的开发者来说,只需要关注一个仓库即可。然后对于产品的主管理员或架构师来说,往往需要同时关注多个仓库,此时Git子模块就起到作用了,用一个主仓库来管理多个子仓库。

总结一下

  • 对于子应用开发者来说,一人一仓库;
  • 对于主应用开发者、架构师来说,可以使用Git submodule 做仓库的整合;
  • Monorepo 并不适合我们;

# 构建部署

# 构建时

构建时 与技术中台的CI(持续集成)的流程整合,当代码仓库push时会触发构建;

微前端的构建部署与传统的SPA的构建部署的区别;

微前端与传统SPA构建

# 部署时

部署时 与技术中台体系整合,使用容器化部署

  • 主应用和子应用构建出SPA所需要的dist静态资源;
  • 使用一个Nginx镜像作为基础镜像,Docker启动,每一个子应用都是一个独立的Docker

构建部署流程

# 运行时

运行时的主角就是qiankun 框架了, qiankun的定位是一个微前端的加载器,在上一篇微前端预研, 我们分析了其中的利弊,和我们即将采用的功能。

我们对qiankun 做了一层封装,发布一个自己内部的微前端运行库@h3yun/microfrontend。(封装的目的是防止与qiankun的API产生强关联)

yarn add @h3yun/microfrontend
1

# 主应用规范

微应用按照加载方式分为两种:路由子应用和手动加载子应用

# 路由子应用

路由子应用直接查看qiankun官方文档 路由子应用。需要注意的是:

  • 子应用需要指定activeRule属性,来匹配路由规则
  • 需要一个提前挂载好的DOM元素来作为挂载容器。

一个坑

一个qiankun 框架的bug, 切换路由时可能会导致主项目与路由相关的css样式丢失。可查看下面的issue 并最好持续跟进,我们也用了issue中提到的临时解决方案。

https://github.com/umijs/qiankun/issues/578

# 手动加载子应用

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>
1
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) {
  // 更新钩子,这个钩子只在手动加载微应用时有效。
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

实现qiankun的微应用加载还需要webpack的配合,打包导出umd模块。webpack 配置

# 沙箱

当初选择qiankun时看好的就是这个沙箱的功能,关于沙箱的原理可以参考下面文章:

解密qiankun沙箱 - CSDN

微前端JS沙箱.png

# 跨模块通信

跨模块通信的几种方法:

# 通过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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 通过eventbus

eventbus封装在@h3yun/global-provider中,具体请查看@h3yun/global-provider;

数据通信eventbus.png

# 通过globalState

通过globalState封装在@h3yun/global-provider中,具体请查看@h3yun/global-provider;

qiankun 内部也有一个内置的简单globalState, 但是没有响应式所以未启用。

数据通信globalState.png

# 本地开发

上文也说到我们是一人一仓库,所以子应用开发人员本地开发时,运行的只有自己的子应用。这样就要考虑开发时,如何与主框架以及其他子应用集成。

我们可以灵活利用微前端运行时加载的能力,将开发集群中正在运行的子应用服务替换为本地开发运行的服务。这样浏览器中只有自己开发的模块是本地代码,其他代码都是其他人构建好的代码。

这样本地开发的好处是:

  • 代码时时刻刻都在进行集成,不同人的代码在时刻碰撞,免去了Git仓库分支合并的过程。
  • 对于子应用开发者来说,更加聚焦;关注点分离

微前端本地开发.png

Wanted问题悬赏

根据开发反馈,子应用接入开发时,webpack热更新可能会失效。

原因是可能会接入多个本地开发子应用,此时容易出现热更新错乱的问题(因为热更新的脚本可没有沙箱隔离);

还可能有别的原因,如果你看到这个问题,并且有兴趣,可以研究一下webpack的热更新机制,加一些自定义的代码应该可以解决这个问题。

期待你的解决!!