# 概述

# 特性

  • 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
  • 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 🛡​ 样式隔离,确保微应用之间样式互相不干扰。
  • 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。

# 设计理念

  • 🥄 简单

由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。

  • 🍡 解耦/技术栈无关

微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

# 核心痛点

  • 问题一是应用的加载与切换。包括路由的处理、应用加载的处理和应用入口的选择。

  • 问题二是应用的隔离与通信。这是应用已经加载之后面临的问题,它们包括 JS 的隔离(也就是副作用的隔离)、样式的隔离、也包括父子应用和子子应用之间的通信问题。

# HTML Entry

qiankun 通过 HTML Entry 的方式来解决 JS Entry 带来的问题,让你接入微应用像使用 iframe 一样简单。

# 应用隔离

# JS沙箱

实际上刚才那些抉择完了之后,尤其是当我们借用了 Single-SPA 能力之后,我们已经基本解决了应用的加载与切换。我们接下来要解决的另一块事情是应用的隔离和通信。 首当其冲就是应用的隔离问题,隔离问题最开始我们就是要做 JS 的隔离。在这个问题上,有的时候你不隔离其实也可以运行的,只不过不隔离,直接裸用 Single-SPA 的话,一旦应用之间的发生了冲突,你的应用很可能就跑不下去了,所以 JS 沙箱大部分情况是必要的。 再 qiankun 沙箱的实现里,我们会有两个环境:一个代表的是外部的部分,就是我们的全局环境 Global Env,指的是你框架应用所运行的全局环境。而子应用加载的时候,实际上是应该跑在一个内部的沙箱环境里面,就是这张图上所表示的 Render Env

  • 沙箱实现思路有两条:其中最经典的实践思路其实是快照沙箱

快照沙箱就是在应用沙箱挂载和卸载的时候记录快照,在应用切换的时候依据快照恢复环境, 当我子应用加载、启动了之后,此时的环境其实就是 Render Environment,是内部沙箱环境里,这个时候我记录一下当时的快照状态。而在我最后子应用 unmount 的时候,我把当前的环境和记录的快照进行一个对比,把它恢复回原来的全局状态。这就是说当我应用挂载了又卸载了,这个过程走了一遍之后,我当前整个 Windows 运行环境恢复成原来的样子,应用内部所做的修改,在它卸载的时候就会被恢复,这是快照沙箱思路。

举个例子,假如我们在 A 应用运行时,追加了一个全局变量,我们说 window.a = 123 ,这个时候 window.a 就变成了 123 ,但是在应用 A 卸载之后,快照还原, window.a 会被重新删除,你在全局环境中并不会继续看到 a 变量 快照怎么打?其实也有两种思路: 一种是直接用 windows diff。把当前的环境和原来的环境做一个比较,我们跑两个循环,把两个环境作一次比较,然后去全量地恢复回原来的环境。 另一种思路其实是借助 ES6 的 proxy 就是代理。通过劫持 window ,我们可以劫持到子应用对全局环境的一些修改。当子应用往 window 上挂东西、修改东西和删除东西的时候,我们可以把这个操作记录下来。当我们恢复回外面的全局环境的时候,我们只需要反向执行之前的操作即可。比如我们在沙箱内部设了一个新的变量 window.a = 123 。那在离开的时候,我们只需要把 a 变量删了即可。 快照沙箱这个思路也正是 qiankun1.0 所使用的思路,它相对完善,但是缺点在于无法支持多个实例,也就是说我两个沙箱不可能同时激活,我不能同时挂载两个应用,不然的话这两个沙箱之间会打架,导致错误。 所以说我们把目光又投向了另一条思路,我们让子应用里面的环境和外面的环境完全隔离。就如图所示,我们的 A 应用就活在 A 应用的沙箱里面,B 应用就活在 B 应用的沙箱里面,两者之间要不发生干扰,这个沙箱的实现思路其实也是通过 ES6 的 proxy,也通过代理特性实现的。

# 样式隔离

# Dynamic Stylesheet

qiankun 所做的第一件事情其实是动态样式表。当你从子应用 A 切换到子应用 B 的时候,这个时候需要把子应用的样式表 A 的样式给删除,把子应用 B 的样式表给挂载。这样就避免了子应用 A 的样式和子应用 B 的样式同时存在于这个项目中,就做到了最基本的隔离。当你从应用 A 切到应用 B 的时候,你的样式也会自然的从应用 A 的样式切换到应用 B 的样式,我们会自动的帮你做应用样式的删除和加载。当然这样做只仅仅能够保证你在单应用模式下(就是同时只能有一个应用活跃的情况下)保证子应用和子应用之间的样式不会冲突。 当然动态样式表实现之后,我们还是没有解决主应用和主应用之间的样式冲突。实际上这个问题最好的手段还是通过一些工程化的手段来解,比如说 BEM,就是说大家约定一下,你应用 A 样式就统一加一个 a- 的前缀,应用 B 样式就统一加一个 b- 的前缀,你的框架应用也统一加个前缀。大家通过一些约定(比如约定大家都加上一个应用名的前缀)来避免冲突,这是非常有效的一个方案。尤其是主子应用之间的冲突,大部分情况下你只要保证主应用的样式做好改造,保证主应用的样式是很具象的规则,不会跟子应用冲突,那主子应用之间的冲突其实也解决了。不过这种方案终归是依赖约定,往往容易出纰漏。

# 工程化手段

当前 css module 其实非常成熟一种做法,就是通过编译生成不冲突的选择器名。你只要把你想要避免冲突的应用,通过 css module (在构建工具里加一些 css 预处理器即可实现)就能很简单的做到。 css module 构建打包之后,应用之间的选择器名就不同了,也就不会相互冲突了。 css-in-js 也是一种流行的方案,通过这种方式编码的样式也不会冲突,这几个方案实现起来都不复杂,而且都非常行之有效。所以绝大部分情况下,大家手动用工程化手段处理一下主子应用之间的样式冲突,就可以解决掉样式隔离的问题。

# Shadow DOM

# RFC:runtime css transformer

# 应用通信

# 基于URL

# 发布/订阅模型

# 基于 props

# 实践中问题集锦

  1. 生产环境点击路由跳转无法打开子应用

# 参考

微前端框架 - qiankun 大法好 (opens new window)

微前端框架 之 qiankun 从入门到源码分析 (opens new window)

如何设计实现微前端框架-qiankun (opens new window)