常见微前端方案
转载请注明出处,点击此处 查看更多精彩内容。
iframe
iframe 是 HTML 的内联框架元素,表示嵌套的 Browsing Context,它能够将另一个 HTML 页面嵌入到当前页面中,每个嵌入的 Browsing Context
都有自己的会话历史记录和 DOM 树。
每个浏览上下文都拥有完整的文档环境,因此页面上每个 iframe
都需要增加内存和其它计算资源,虽然理论上来说能够在代码中写出来无限多的 iframe
,但是这么做可能会导致某些性能问题。
优点
- Web 应用隔离的非常完美,无论是 JavaScript、CSS、DOM 都完全隔离开来。
- 非常简单,使用没有任何心智负担。
缺点
- DOM 结构不共享,弹窗只能在
iframe
内部展示,无法覆盖全局。 - 路由状态丢失,刷新页面会导致
iframe
的 url 状态丢失、后退前进按钮无法使用。 - 性能差,每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
- 应用之间通信困难,全局上下文完全隔离,内存变量不共享,无法访问非同源的
window
对象的几乎所有属性,跨域通信仅可通过window.postMessage()
来实现。
路由转发
路由分发式微前端,即通过路由将不同的业务分发到不同的独立前端应用上。最常用的方案是通过 HTTP 服务的反向代理(如 Nginx)将不同页面的请求分发到不同的服务上。
优点
- 实现简单。
- 维护、开发成本低。
- 不需要对现有应用进行改造。
缺点
- 用户体验不好,每次切换应用时,浏览器都需要重新加载页面。
- 子应用之间的通信比较困难。
- 多个子应用无法并存。
- 子应用切换时需要重新登录。
动态加载模块
创建一个基座应用,允许子应用单独部署。为了实现这一点,创建一个 manifest
文件,记录上线的子应用及版本信息,当子应用部署更新时修改 manifest
文件,基座应用通过 manifest
检查更新子应用资源。
改变每个子应用加载的 JavaScript 文件有很多的方法:
- Web 服务器:在你的 Web 服务器为每个子应用的正确版本创建一个动态脚本。
- 使用模块加载 例如 SystemJS 可以在浏览器通过动态 urls 下载并执行 JavaScript 代码。
npm
每个子应用在一个单独的代码仓库中,每次更新时发布一个新版本到 npm,创建一个基座应用,通过 npm 安装每个子应用。
优点
- npm 安装对于开发中更熟悉,易于搭建。
缺点
- 子应用发生变更时,基座应用需要重新安装、重新构建和重新部署。
- 无法动态安装、卸载子应用。
Web Component
Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 Web 应用中使用它们。
Web Components
由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
- Custom Element(自定义元素)
一组 JavaScript API,允许您定义 Custom Elements 及其行为,然后可以在您的用户界面中按照需要使用它们。 - Shadow DOM(影子 DOM)
一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。 - HTML Template(HTML 模板)
<template>
和<slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
基于 Web Component
的 Shadow Dom
能力,我们也可以实现微前端,将多个子应用聚合起来。
const shadow = document.querySelector('#hostElement').attachShadow({mode: 'open'});
// url 为应用的地址,基于 fetch,我们可以获取到应用的 html 模板,添加到指定节点下
fetch(url).then(res => {
shadow.innerHTML = res
});
优点
- 实现简单。
- 完全技术栈无关。
- 不需要对现有应用进行改造。
- CSS 和 JavaScript 天然隔离,互不干扰。
缺点
- 没有统一的
Web Component
规范。 - 浏览器实现不一致。
- Web 组件存在向后不兼容的版本问题。
- 开发成本较高。
EMP
EMP 基于 Webpack5 的 Module Federation 实现,用一个词概括,就是“去中心化”,在 EMP 的方案中不需要中心化的基座,每一个微前端应用都可以通过远程调用的方式引入共享模块。
优点
- Webpack5 Module Federation 可以保证所有子应用依赖解耦。
- 模块远程 TypeScript 支持。
- 应用间去中心化的调用、共享模块。
缺点
- 对 Webpack 强依赖,老旧项目不友好。
- 没有有效的 CSS 沙箱和 JavaScript 沙箱,需要靠用户自觉。
- 主、子应用的路由可能发生冲突。
- 子应用保活、多应用激活无法实现。
single-spa / qiankun
single-spa 是一个目前主流的微前端技术方案。
single-spa
从现代框架组件生命周期中获得灵感,将生命周期应用于整个应用程序,其主要实现思路:
- 预先注册子应用(激活路由、子应用资源、生命周期函数)。
- 监听路由的变化,匹配到了激活的路由则加载子应用资源,顺序调用生命周期函数并最终渲染到容器。
single-spa
未解决子应用加载、应用隔离、子应用通信等问题。
qiankun 微前端架构则进一步对 single-spa
方案进行完善,主要的完善点:
- 子应用资源由 JavaScript 列表修改进为一个 url,大大减轻注册子应用的复杂度。
- 增加资源预加载能力,预先缓存子应用的 HTML、JavaScript、CSS 资源,加快子应用的打开速度。
- 实现应用隔离,完成 JavaScript 隔离方案 (window 工厂) 和 CSS 隔离方案 (类 Vue 的 scoped)。
- 提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
优点
- 监听路由自动的加载、卸载当前路由对应的子应用。
- 路由保持,浏览器刷新、前进、后退,都可以作用到子应用。
- 应用间通信简单,全局注入。
- 完备的沙箱方案,JavaScript 沙箱做了
SnapshotSandbox
、LegacySandbox
、ProxySandbox
三套渐进增强方案,CSS 沙箱做了两套strictStyleIsolation
、experimentalStyleIsolation
两套适用不同场景的方案。
缺点
- 改造成本较大,从 Webpack、代码、路由等等都要做一系列的适配。
- 基于路由匹配,无法同时激活多个子应用,也不支持子应用保活。
- CSS 沙箱无法绝对的隔离,JavaScript 沙箱在某些场景下执行性能下降严重。
- 无法支持
Vite
等 ESM 脚本运行。
无界(wujie)
无界利用 iframe
的沙箱机制,将子应用的 JavaScript 注入到基座应用同域的 iframe
中运行,并采用 Web Component
实现页面的样式隔离。
- 应用加载机制和 JavaScript 沙箱机制
将子应用的 JavaScript 注入主应用同域的iframe
中运行,iframe
是一个原生的window
沙箱,内部有完整的history
和location
接口,子应用实例运行在iframe
中,路由主应用解耦,可以直接在业务组件里面启动应用。 - 路由同步机制
劫持iframe
的history.pushState
和history.replaceState
,就可以将子应用的 url 同步到主应用的 query 参数上,当刷新浏览器初始化iframe
时,读回子应用的 url 并使用iframe
的history.replaceState
进行同步。 -
iframe
连接机制和 CSS 沙箱机制
无界采用Web Component
来实现页面的样式隔离,无界会创建一个wujie
自定义元素,然后将子应用的完整结构渲染在内部。子应用的实例在iframe
内运行,DOM 在主应用容器下的Web Component
内,通过代理iframe
的document
到Web Component
,可以实现两者的互联。 - 通信机制
承载子应用的iframe
和主应用是同域的,所以主、子应用天然就可以很好的进行通信,无界提供三种通信方式。-
props
注入机制,子应用通过$wujie.props
可以轻松拿到主应用注入的数据。 - 子应用
iframe
沙箱和主应用同源,子应用可以直接通过window.parent
和主应用通信。 - 无界提供了
EventBus
实例,注入到主应用和子应用,所有的应用可以去中心化的进行通信。
-
优点
- 多应用同时激活在线
框架具备同时激活多应用,并保持这些应用路由同步的能力。 - 组件式的使用方式
无需注册,更无需路由适配,在组件内使用,跟随组件装载、卸载。 - 应用级别的
keep-alive
子应用开启保活模式后,应用发生切换时整个子应用的状态可以保存下来不丢失,结合预执行模式可以获得类似 SSR 的打开体验。 - 纯净无污染
- 无界利用
iframe
和Web Component
来搭建天然的 JavaScript 隔离沙箱和 CSS 隔离沙箱。 - 利用
iframe
的history
和主应用的history
在同一个 top-levelBrowsing Context
来搭建天然的路由同步机制。 - 副作用局限在沙箱内部,子应用切换无需任何清理工作,没有额外的切换成本。
- 无界利用
- 性能和体积兼具
- 子应用执行性能和原生一致,子应用实例运行在
iframe
的window
上下文中,避免with(proxyWindow){code}
这样指定代码执行上下文导致的性能下降,但是多了实例化iframe
的一次性的开销,可以通过 preload 提前实例化。 - 体积比较轻量,借助
iframe
和Web Component
来实现沙箱,有效的减小了代码量。
- 子应用执行性能和原生一致,子应用实例运行在
- 开箱即用
不管是样式的兼容、路由的处理、弹窗的处理、热更新的加载,子应用完成接入即可开箱即用无需额外处理,应用接入成本也极低。 - 子应用运行在
iframe
中,原生支持 ESM 的脚本。
缺点
-
iframe
的一些坑尚未解决,如路由回退问题。
micro-app
micro-app 借鉴了 Web Component
的思想,通过 Custom Element
结合自定义的 Shadow Dom
,将微前端封装成一个类 Web Component
组件,从而实现微前端的组件化渲染。并且由于自定义 Shadow Dom
的隔离特性,micro-app
不需要像 single-spa
和 qiankun
一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改 Webpack 配置,在基座应用中嵌入一行代码即可渲染一个微前端应用,是目前市面上接入微前端成本最低的方案。
优点
- 零依赖
micro-app
没有任何依赖,这赋予它小巧的体积和更高的扩展性。 - 使用简单
我们将所有功能都封装到一个类Web Component
组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。同时micro-app
还提供了 JavaScript 沙箱、样式隔离、元素隔离、预加载、数据通信、静态资源补全等一系列完善的功能。 - 兼容所有框架
为了保证各个业务之间独立开发、独立部署的能力,micro-app
做了诸多兼容,在任何技术框架中都可以正常运行。
缺点
- 多应用激活后无法保持子应用的路由状态,刷新后全部丢失。
- 对于不支持
Web Component
的浏览器没有做降级处理。 - 支持
Vite
运行,但必须使用plugin
改造子应用,且 JavaScript 代码没办法做沙箱隔离。 - 较长时间未更新,稳定版最近一次发版(v0.8.10)时间是 2022.08.19,测试版最近一次发版(v1.0.0-alpha.10)时间是 2022.10.11。