iframe
iframe
是 html
提供的标签,能加载其他web应用的内容,并且它能兼容所有的浏览器,因此,你可以用它来加载任何你想要加载的web应用。
iframe最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。iframe与微前端概念中提到的独立开发、独立维护、相互隔离非常的吻合。
qiankun技术圆桌中一篇《关于微前端Why Not Iframe的思考》,总结的很到位,现复述其中的一段描述
iframe虽然基本能做到微前端所要做的所有事情,但它的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来开发体验、产品体验的问题。
文中对 iframe 的总结:
- 不是单页应用,不会影响外部的路由地址,无法记住当前访问的页面地址,会导致浏览器刷新页面 iframe url 状态丢失、后退前进按钮无法使用。
- 弹框类的功能无法应用到整个大应用中,只能在对应的窗口内展示。
- 由于可能应用间不是在相同的域内,主应用的 cookie 要透传到根域名都不同的子应用中才能实现免登录效果。
- 每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程,占用大量资源的同时也在极大地消耗资源。
- iframe的特性导致搜索引擎无法获取到其中的内容,进而无法实现应用的 seo
Web Components
或许很多小伙伴对Web Components不是很了解,它是由google推出的浏览器的原生组件,MDN对Web Components的定义是这样的:
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义UI控件,并且如果您不小心的话,多次使用它们会使您的页面变得一团糟。
Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
它的三项主要技术是指:
- Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates(HTML模板): 和 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
通过以上描述,再结合微前端的概念,我们来看看Web Components是如何做到微前端:
- 技术栈无关:Web Components是浏览器原生组件,那即是在任何框架中都可以使用。
- 独立开发:使用Web Components开发的应用无需与其他应用间产生任何关联。
- 应用间隔离:Shadow DOM的特性,各个引入的微应用间可以达到相互隔离的效果。
综上所述,Web Components是有能力以组件加载的方式将微应用整合在一起作为微前端的一种手段,但不幸的是,Web Components是浏览器的新特性,所以它的兼容性不是很好,如果有兼容性要求的项目还是无法使用,具体请查看can i use。
ESM
ESM是ES Module的缩写,是Ecma script 2015中提出的一种前端模块化手段,那么,它又是如何做到微前端的呢?其实,微前端无外乎三大特性,无技术栈限制、应用单独开发,多应用整合,只要抓住了这三个特性,那就不难理解ESM如何做的了:
- 无技术栈限制:ESM加载的只是js内容,无论哪个框架,最终都要编译成js,因此,无论哪种框架,ESM都能加载。
- 应用单独开发:ESM只是js的一种规范,不会影响应用的开发模式。
- 多应用整合:只要将微应用以ESM的方式暴露出来,就能正常加载。
- 远程加载模块: ESM能够直接请求cdn资源,这是它与生俱来的能力。
ESM是能做到微前端的核心思想,但是它也存在着兼容性这一大弊端,尽管ESM已经很优秀了,但是大部分老版的浏览器仍然无法直接使用,这也是babel等编译工具出现的原因,幸运的是,他可以通过webpack、rollup、esbuild、snowpack等编译工具成为兼容性的代码。
qiankun
qiankun
特点如下:
- 基于single-spa封装,提供了更加开箱即用的 API
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架
- 子应用独立部署
- 子应用与主应用无缝衔接,主应用对子应用可控,子应用可获取主应用资源
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单
- 样式隔离,确保微应用之间样式互相不干扰
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统除了最后一点拓展以外,微前端想要达到的效果都已经达到。
微前端原理
微前端实现原理是 主工程在运行时获取应用配置,然后注册应用和路由,先加载主应用(菜单等),当url 地址变化时,通过路由管理器和应用管理器动态加载对应的子应用
路由管理器是指把所有子应用的路由统一放在一个总路由里管理,应用管理器是把所有子应用打包后的实例放在一个应用管理器里,当URL和路由管理器中的子应用路由匹配时,通过应用管理器加载对应的子应用
actions 通信原理
Actions 通信方案是通过全局状态池和观察者函数进行应用间通信,比较适合业务划分清晰,应用间通信较少的微前端应用场景。
qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:
- setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。
- onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
- offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。
Actions 通信方案也存在一些优缺点,优点如下:
- 使用简单;
-官方支持性高; - 适合通信较少的业务场景;
缺点如下:
- 子应用独立运行时,需要额外配置无 Actions 时的逻辑;
- 子应用需要先了解状态池的细节,再进行通信;
- 由于状态池无法跟踪,通信场景较多时,容易出现状态混乱、维护困难等问题;
Shared 通信原理
适合需要跟踪通信状态,子应用具备独立运行能力,较为复杂的微前端应用。
Shared 通信方案的原理就是,主应用基于 redux 维护一个状态池,通过 shared 实例暴露一些方法给子应用使用。同时,子应用需要单独维护一份 shared 实例,在独立运行时使用自身的 shared 实例,在嵌入主应用时使用主应用的 shared 实例,这样就可以保证在使用和表现上的一致性。
Shared 通信方案需要自行维护状态池,这样会增加项目的复杂度。好处是可以使用市面上比较成熟的状态管理工具,如 redux、mobx,可以有更好的状态管理追踪和一些工具集。
Shared 通信方案要求父子应用都各自维护一份属于自己的 shared 实例,同样会增加项目的复杂度。好处是子应用可以完全独立于父应用运行(不依赖状态池),子应用也能以最小的改动被嵌入到其他 第三方应用 中。
Shared 通信方案也可以帮助主应用更好的管控子应用。子应用只可以通过 shared 实例来操作状态池,可以避免子应用对状态池随意操作引发的一系列问题。主应用的 Shared 相对于子应用来说是一个黑箱,子应用只需要了解 Shared 所暴露的 API 而无需关心实现细节。
优点:
- 可以*选择状态管理库,更好的开发体验。 - 比如 redux 有专门配套的开发工具可以跟踪状态的变化。
- 子应用无需了解主应用的状态池实现细节,只需要了解 shared 的函数抽象,实现一套自身的 shared 甚至空 shared 即可,可以更好的规范子应用开发。
- 子应用无法随意污染主应用的状态池,只能通过主应用暴露的 shared 实例的特定方法操作状态池,从而避免状态池污染产生的问题。
- 子应用将具备独立运行的能力,Shared 通信使得父子应用有了更好的解耦性。
缺点:
- 主应用需要单独维护一套状态池,会增加维护成本和项目复杂度;
- 子应用需要单独维护一份 shared 实例,会增加维护成本;
EMP
EMP是由欢聚时代业务中台自主研发的最年轻的单页微前端解决方案
有如下特性:
-
基于
Webpack5
的新特性Module Federation
实现,达到第三方依赖共享,减少不必要的代码引入的目的,什么是Module Federation这里就不再赘述。 - 每个微应用独立部署运行,并通过cdn的方式引入主程序中,因此只需要部署一次,便可以提供给任何基于Module Federation的应用使用。并且此部分代码是远程引入,无需参与应用的打包。
- 动态更新微应用:EMP是通过cdn加载微应用,因此每个微应用中的代码有变动时,无需重新打包发布新的整合应用便能加载到最新的微应用。
- 去中心化,每个微应用间都可以引入其他的微应用,无中心应用的概念。
- 跨技术栈组件式调用,提供了在主应用框架中可以调用其他框架组件的能力(目前已支持互相调用的框架及使用方式请参阅官方文档)。
- 按需加载,开发者可以选择只加载微应用中需要的部分,而不是强制只能将整个应用全部加载。
- 应用间通信,每一个应用都可以进行状态共享,就像在使用npm模块进行开发一样便捷。
- 生成对应技术栈模板,它能像cerate-react-app一样,也能像create-vue-app一样,通过指令一键搭建好开发环境,减少开发者的负担。
- 远程拉取ts声明文件,emp-cli中内置了拉取远程应用中代码声明文件的能力,让使用ts开发的开发者不再为代码报错而烦恼。
总结
各解决方案的利弊:
-
iframe
可以直接加载其他应用,但无法做到单页导致许多功能无法正常在主应用中展示。 -
web Components
及ESM
是浏览器提供给开发者的能力,能在单页中实现微前端,不过后者需要做好代码隔离,并且他们都是浏览器的新特性,都存在兼容性问题,微前端方面的探索也不成熟,只能作为面向未来的微前端手段。 -
qiankun
基本上可以称为单页版的 iframe,具有沙箱隔离及资源预加载的特点,几乎无可挑剔。 -
EMP
作为最年轻微前端解决方案,也是吸收了许多web优秀特性才诞生的,它在实现微前端的基础上,扩充了跨应用状态共享、跨框架组件调用、远程拉取ts声明文件、动态更新微应用等能力。同时,EMP
能做到第三方依赖的共享,使代码尽可能地重复利用,减少加载的内容。