Appearance
Qiankun 微前端
什么是微前端?
微前端主要用于将一个大型的单体应用拆分为多个独立的子应用,或者通过多个子项目构建一个大型的单体项目。其中各个子应用可以独立开发独立部署,降低了整个系统运行的风险。常见的场景是大型中后台项目的拆分。
single-spa
single-spa 是最早提出并实现的微前端解决方案,它定义了微前端的雏形:
- 微前端主要是由基座应用(主应用)和子应用构成。
- 主应用中注册子应用的相关信息,并且子应用向外暴露一些生命周期钩子,来允许主应用在恰当的时机挂载或者卸载子应用。
- 根据路由的判断具体加载哪一个子应用。
single-spa 主要做的工作就是路由劫持。劫持浏览器原生的方法,并根据用户配置的规则决定去执行哪个入口。
但是 single-spa 有个核心问题没有解决,就是子应用之间的隔离问题。A 应用的副作用可能会影响到 B 应用,也就是子应用是在同一个运行环境下运行的,如果子应用之间可能同时在读取和修改同一个全局变量。
为了解决这个问题,qiankun 出现了。
qiankun
qiankun 是基于 single-spa 开发的微前端解决方案。相比于 single-spa,qiankun 主要做了下面几点优化:
- 使用 html entry,而 single-spa 只能使用 js entry
- 提供运行时沙箱,使得子应用之间相互隔离
- 实现了一套简单易用的应用间通信方法
html entry
在匹配到指定的路由,或者手动指定加载子应用时,qiankun 会通过 fetch 方法远程获取到子应用的 html 文件当作子应用的入口,并且使用 import-html-entry 这个包解析得到的资源,得到需要运行的脚本和需要下载的资源列表。
其中对于 CSS 文件,会通过 fetch 拉取后处理为一个单独的标签插入到结果中。
而对于 JS 文件则需要进一步处理才能保证每个子应用都运行在独立的沙箱内。
最后将解析好的内容插入到子应用对应的根节点上。
JS 沙箱
子应用如何在沙箱中运行?
在上一步我们拿到并解析 html 文件后,import-html-entry 会向外暴露方法 execScript,这个方法可以指定子应用运行的 global 环境。也就是在这时我们可以创建一个浏览器环境的沙箱,传递给子应用的脚本并运行它,此时就改变了子应用的运行环境。
其实 execScript 内部是将传入的脚本内容转化为字符串,在外面包裹指定的字符串,通过 with 关键字,使其改变为一个 iife 函数并通过 eval 方法调用执行。
而 JS 沙箱,qiankun 一共提供了三种:
ProxySandBox
:代理沙箱,在支持 Proxy 的浏览器上会默认开启,支持多实例。主要是通过 Proxy 代理 window 对象,同时内部会有个新的对象 fakeWindow,读取值时会优先读取 fakeWindow 上的属性,如果没有,再去读取 window 对象上的。设置值的时候直接设置到 fakeWindow 上。LegacySandBox
:旧版沙箱,目前已弃用,仅当用户配置了{ sandbox: "loose" }
时启用,仅支持单例模式。也是通过 Proxy 劫持对 window 对象的操作,内部维护一张记录表,对 window 的操作会被记录在这个表中。沙箱激活时应用记录表中的操作,失活时还原记录表中的操作。SnapShotSandBox
:快照沙箱,在不支持 Proxy 的浏览器上启用。这个沙箱会直接操作 window 对象,在沙箱失活时保存快照,在沙箱激活时应用快照。
CSS隔离
qiankun 提供了三种方式实现样式隔离。
默认方式:默认方式下子应用的样式文件会挂载在子应用的根结点下,并且通过内链标签的形式引入。这样做的目的是为了保证,当子应用被卸载后,子应用相关的 CSS 样式也会被一起卸载掉,不会影响到其他的子应用。但是这种方案可能会造成对主应用的样式污染。
Scoped CSS:qiankun 全新的实验性 API,开启之后 qiankun 会在子应用的根节点上添加
data-qiankun-[name]
,并且为所有的样式文件添加一个特殊的选择器来保证应用之间的样式不会污染。Shadow DOM:当开启严格的样式隔离模式时,qiankun 会为每个子应用创建一个 shadow dom 节点,以确保子应用的样式不会对全局造成影响。
当然你还可以自定义添加样式文件的前缀,例如 antd 之类的 ui 框架都可以轻松做到这一点。
应用间通信
对于应用间的通信,qiankun 提供了极为简单的 API,即可以通过 initGlobalState(state)
方法在主应用上创建一个全局的状态对象,那么在子应用中就可以感知到状态的变化,并且能够主动改变主应用上的状态对象。
在 initGlobalState 方法内部,还是使用发布订阅的设计方案,将子应用注册的钩子函数保存起来,在更改数据时依次调用注册的钩子函数。
qiankun 提供的默认方法处理一些简单的通信是可以的,但是这种方案有一个弊端,就是子应用也需要自己的状态管理器。
一个比较可行的方案是,主应用和子应用都存有一个状态管理器,主应用将自己的状态管理器通过 props传递给子应用,子应用在加载时判断自己时运行在微前端框架下,还是单独运行中,决定是使用主应用传递的状态管理器还是自己的。