前言
在华润的万象生活部门,最近两年的一个工作重点是将很多独立的运营系统合并融合为一站式系统。
随着合并的项目越来越多,系统性能瓶颈逐渐显现。项目架构是webpack5+react18,为解决当前的问题,将webpack升级为vite,冷启动、热更新、build有了不少的提升,但是为长久计,还是建议做微前端拆分。
调研和对比了当前主流的解决方案,并通过demo进行实验性实践。
下面是一些总结记录。
超大型系统开发痛点
- 模块之间耦合度太高
- 模块上线影响范围大
- 代码合并难
- 不能并行迭代
- 沟通成本较高
传统解决方案
iframe也可以实现微前端,但是需要解决其自身的诸多弊端。
优点
• iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制(相互隔离)。
• 嵌入子应用比较简单,学习较为简单。
缺点
• iframe功能之间的跳转是无效的,刷新页面无法保存状态。
• URL的记录完全无效,刷新会返回首页。
• 主应用劫持快捷键操作,事件冒泡不穿透到主文档树上。
• 模态弹窗的背景是无法覆盖到整个应用。
• iframe应用加载失败,内容发生错误主应用无法感知,通信麻烦。
微前端基础概念
微前端由来
微前端的概念是由 ThoughtWorks 在2016年提出的,它借鉴了微服务的架构理念。
核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署。
再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。
微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
解决问题
• 随着项目迭代应用越来越庞大,难以维护。
• 跨团队或跨部门协作开发项目导致效率低下的问题。
微前端架构图
img
如图所示,整个项目大概分为:
- 一个基座项目
- N 个子项目
- 一个公共组件库
基座项目主要做的事情有:
- 初始化一个基座应用
- 注册所有的子项目
- 注册公共依赖库
- 注册全局路由
- 注册全局的 Store(由于我是重构项目,大部分业务都需要依赖一些全局的东西)
- 初始化一些全局可用的环境(虽然貌似有点违背微前端的初衷,不过实践后还是很多场景需要)
微前端流程图
设计理念和核心思想
独立开发、部署
微前端框架强调每个子应用的独立性,使其可以独立开发、测试和部署。这种架构模式不仅提升了开发效率,还显著降低了整体系统的复杂度和维护成本。
独立运行时
每个应用之间状态隔离,运行时状态不共享。
技术栈无关
不同前端应用可以使用不同的前端框架或技术栈,如React、Vue、Angular等,彼此之间保持独立且互不影响。
增量式更新
通过微前端的架构,可以实现增量式的系统更新。新功能和改进可以逐个发布,无需每次升级都涉及整个系统,减少发布的频率和风险,提升用户体验。
微前端共性问题
实际上所有的微前端框架都面临这两大共性问题。当你解决了这两大问题之后,你的微前端框架的运行时,就已经基本可用了。
- 问题一是应用的加载与切换。包括路由的处理、应用加载的处理和应用入口的选择。
- 问题二是应用的隔离与通信。这是应用已经加载之后面临的问题,它们包括 JS 的隔离(也就是副作用的隔离)、样式的隔离、也包括父子应用和子子应用之间的通信问题。
所有微前端框架都为了围绕这两个问题,用不同的技术方案实现,如路由劫持、css沙箱、js沙箱等。微前端的前提还是得有主体应用,然后才有微组件或微应用,解决的是可控体系下前端协同开发问题。这里开发问题有两部分:一部分是空间分离带来的协作,另一部分是时间延续带来的升级维护。空间分离带来的协作,需要微前端方案能够提供独立开发、独立部署的特性,来处理协作问题。而时间带来的延续其实是需要我们做到技术栈无关,做到随着时间的推移,当我们技术栈陈旧的时候,还是能够正常的接入我们的框架应用。
微前端的优缺点
优势
- 独立开发:每个微前端应用运行在独立的进程中,开发团队可以选择自己熟悉的技术栈进行开发,提高了开发效率和灵活性。这种技术栈无关性使得团队能够专注于各自擅长的领域,加速了开发过程。
- 独立部署:每个微前端应用都可以独立部署,这意味着可以更快速地实现小规模的功能更新和修复。这种增量升级的方式避免了大规模部署带来的风险,同时提高了系统的可维护性和升级效率。
- 独立测试:每个微前端应用都可以单独测试,有助于更快地发现问题并进行修复。这种测试方式降低了测试的复杂性,提高了测试效率。
- 性能优越:微前端架构通过将大型应用拆分成多个小型应用,实现了按需加载和动态加载,减少了页面的初始加载时间,提高了应用的性能和用户体验。
- 可扩展性和可维护性:模块化设计使得微前端架构具有良好的可扩展性和可维护性。随着应用的不断扩展,可以轻松地添加新的模块或组件,而无需对整个应用进行重构。同时,独立的模块也便于进行代码维护和升级
- 技术栈多样性:微前端支持多种技术栈的集成,允许不同的团队使用最适合其需求的技术进行开发。这种多样性促进了技术创新和团队协作。
- 高效协作:在大型项目中,不同的团队或开发者可以负责不同的微前端应用,从而实现高效的并行开发和协作。这有助于缩短开发周期,提高开发效率。
劣势
- 复杂性增加:微前端架构需要处理多个独立的应用,每个应用都有自己的技术栈、构建流程和依赖关系。这可能导致整体架构的复杂性增加,需要额外的管理和协调工作。
- 性能问题:虽然微前端架构在整体性能上有所提升,但在某些情况下,如应用之间的数据共享和状态同步时,可能会引入额外的性能开销。此外,多个应用的加载和初始化也可能导致页面加载时间增加。
- 数据共享和状态管理复杂:不同的微前端应用可能来自不同的团队,它们之间的数据共享和状态管理可能变得复杂。需要找到合适的方法来确保数据的一致性和同步性。
- 样式冲突:不同微前端应用的样式可能会产生冲突,需要额外的工作来确保样式不会相互影响,保持一致的外观。
- 跨域问题:如果不同的微前端应用部署在不同的域名下,可能会涉及到跨域问题。这需要适当的配置跨域策略或使用反向代理等方法来解决。
- 重复依赖:不同应用之间的依赖包有可能会存在很多重复的。由于各应用独立开发、编译、部署,难免会出现重复下载依赖的情况,增加了流量和服务端压力。
- 技术成本增加:微前端架构需要开发者具备更多的技术知识和能力,如需要同时了解主应用和子应用的技术栈以及微前端架构本身。这增加了技术学习和培训的成本。
主流微前端框架
- Single-Spa
- qiankunjs
- EMP
- wujie
- micro-app: 是基于 webcomponent + qiankun sandbox 的微前端方案。
Single-Spa
概念
Single-spa是一个用于构建前端微服务架构的JavaScript框架。它允许开发者独立地构建、部署和运行多个前端应用,并将它们集成到一个单一的界面中。这意味着每个前端应用可以独立地开发、测试和部署,而不会干扰其他应用的工作。
Single-spa的核心思想是将前端应用拆分为一系列小的功能模块,每个模块可以由不同的团队独立开发和维护。这样一来,跨团队的合作变得更加灵活,可以更好地应对需求的变化和应用的扩展。
优点
- 轻量级
Single-spa是一个非常轻量级的微前端框架,它主要提供了一个加载和管理微应用的机制,使得微应用的集成变得简单。这种轻量级特性使得它成为构建微前端架构的一个理想选择,尤其是对于需要快速启动和迭代的项目。
- 灵活性
由于其轻量级特性,Single-spa允许开发者根据项目需求进行定制,灵活集成不同的技术栈。这意味着开发者可以自由选择最适合项目需求的前端框架和库,而无需担心与Single-spa的兼容性问题。
- 前后端分离
在使用Single-spa构建的微前端架构中,前端主要负责交互逻辑和页面渲染,而后端则负责数据处理和API提供。这种前后端职责的明确分离有助于提升开发效率,并使得系统架构更加清晰。
- 用户体验好
Single-spa通过路由机制实现HTML内容的变换,避免了页面的重新加载。这意味着用户可以在不刷新页面的情况下获得流畅的应用体验,减少了等待时间和不必要的跳转。
- 服务器压力小
由于Single-spa减少了页面的重新加载和HTTP请求的数量,因此可以减轻服务器的压力。这对于需要处理大量并发请求的应用来说尤为重要。
缺点
single-spa 就做了两件事,加载微应用(加载方法还是用户自己提供的)、维护微应用状态(初始化、挂载、卸载)。 single-spa 采用 JS Entry 的方式接入微应用。将整个微应用打包成一个JS文件,发布静态资源服务器,然后在主应用中配置该 JS 文件的地址告诉 single-spa 去这个地址加载微应用。
single-spa存在一些问题:
- 对微应用的侵入性太强 将整个微应用打包成一个 JS 文件,常见的打包优化基本上都没了,比如:按需加载、首屏资源加载优化、css 独立打包等优化措施
- 样式隔离问题 single-spa中没有做这部分的工作。如果保证一个大型项目中,主应用和微应用之间,微应用和微应用之间的样式隔离。比如应用样式以自己的应用名称开头
- JS隔离问题 single-spa中没有做这部分的工作。JS全局对象污染,没有保证微应用A和微应用B的window互相没有影响
- 资源预加载 single-spa中没有做这部分的工作。
- 应用间通信 single-spa中没有做这部分的工作。它只在注册微应用时给微应用注入一些状态信息,后续就不管了,没有任何通信的手段,只能用户自己去实现。
思维导图
img
wujie
概念
wujie 是腾讯出品的一款微前端框架。作为改良派的代表,它认为:iframe 虽然问题很多,但仅把它作为一个 js 沙箱去用,表现还是很稳定的,毕竟是浏览器原生实现的,比自己实现 js 沙箱靠谱多了。至于 iframe 的弊端,可以针对性的去优化:
- DOM 渲染无法突破 iframe 边界? (弹框不居中问题)
那 DOM 就不放 iframe 里渲染了,而是单独提取到一个 webComponent 里渲染,顺便用 shadowDOM 解决样式隔离的问题。
无界微前端是一款基于 Web Components + iframe 微前端框架,具备成本低、速度快、原生隔离、功能强等一系列优点。其能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等。
简单说,无界的方案就是:JS 放 iframe 里运行,DOM 放 webComponent 渲染。
那么问题来了:用 JS 操作 DOM 时,两者如何联系起来呢?毕竟 JS 默认操作的总是全局的 DOM。无界在此处用了一种比较 hack 的方式:代理子应用中所有的 DOM 操作,比如将 document 下的 getElementById、querySelector、querySelectorAll、head、body 等查询类 api 全部代理到 webComponent。
使用
实现原理
至于多实例模式,就更容易理解了。给每个子应用都分配一套 iframe + webComponent 的组合,就可以实现相互之间的隔离了!
特性
- 急速: 极致预加载、预执行,页面秒开无白屏、丝滑般切换
- 强大: 支持子应用保活、内嵌、去中心化通信、多应用激活
- 简单: 框架封装, 保持普通组件使用体验一致
- 原生隔离: 基于 WebComponent 和 iframe,原生物理隔离
- 原生性能: 避免 with 语句运行代码,整体的运行性能接近原生
- 开箱即用: 主、子应用无需做任何适配,开箱即用
优点
使用简单
<template>
<!--保活模式,name相同则复用一个子应用实例,改变url无效,必须采用通信的方式告知路由变化 -->
<WujieVue width="100%" height="100%" name="vue3" :url="vue3Url" :sync="true"></WujieVue>
</template>
<script>
import hostMap from "../hostMap";
export default {
data() {
return {
vue3Url: hostMap("//localhost:7300/"),
};
},
};
</script>
<style lang="scss" scoped></style>
应用加载机制和 js 沙箱机制
将子应用的js注入主应用同域的iframe中运行,iframe是一个原生的window沙箱,内部有完整的history和location接口,子应用实例instance运行在iframe中,路由也彻底和主应用解耦,可以直接在业务组件里面启动应用。
- 组件方式来使用微前端
不用注册,不用改造路由,直接使用无界组件,化繁为简
- 一个页面可以同时激活多个子应用
子应用采用 iframe 的路由,不用关心路由占用的问题
- 天然 js 沙箱,不会污染主应用环境
不用修改主应用window任何属性,只在iframe内部进行修改
- 应用切换没有清理成本
由于不污染主应用,子应用销毁也无需做任何清理工作
路由同步机制
在iframe内部进行history.pushState,浏览器会自动的在joint session history中添加iframe的session-history,浏览器的前进、后退在不做任何处理的情况就可以直接作用于子应用
劫持iframe的history.pushState和history.replaceState,就可以将子应用的url同步到主应用的query参数上,当刷新浏览器初始化iframe时,读回子应用的url并使用iframe的history.replaceState进行同步。
- 浏览器刷新、前进、后退都可以作用到子应用
- 实现成本低,无需复杂的监听来处理同步问题
- 多应用同时激活时也能保持路由同步
iframe 连接机制和 css 沙箱机制
无界采用webcomponent来实现页面的样式隔离,无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部
子应用的实例instance在iframe内运行,dom在主应用容器下的webcomponent内,通过代理 iframe的document到webcomponent,可以实现两者的互联。
将document的查询类接口:getElementsByTagName、getElementsByClassName、getElementsByName、getElementById、querySelector、querySelectorAll、head、body全部代理到webcomponent,这样instance和webcomponent就精准的链接起来。
当子应用发生切换,iframe保留下来,子应用的容器可能销毁,但webcomponent依然可以选择保留,这样等应用切换回来将webcomponent再挂载回容器上,子应用可以获得类似vue的keep-alive的能力。
- 天然 css 沙箱
直接物理隔离,样式隔离子应用不用做任何修改
- 天然适配弹窗问题
document.body的appendChild或者insertBefore会代理直接插入到webcomponent,子应用不用做任何改造
- 子应用保活
子应用保留iframe和webcomponent,应用内部的state不会丢失
- 完整的 DOM 结构
webcomponent保留了子应用完整的html结构,样式和结构完全对应,子应用不用做任何修改
通信机制
承载子应用的iframe和主应用是同域的,所以主、子应用天然就可以很好的进行通信,在无界我们提供三种通信方式
- props 注入机制
子应用通过$wujie.props可以轻松拿到主应用注入的数据
- window.parent 通信机制
子应用iframe沙箱和主应用同源,子应用可以直接通过window.parent和主应用通信
- 去中心化的通信机制
无界提供了EventBus实例,注入到主应用和子应用,所有的应用可以去中心化的进行通信
优势
- 多应用同时激活在线
框架具备同时激活多应用,并保持这些应用路由同步的能力
- 组件式的使用方式
无需注册,更无需路由适配,在组件内使用,跟随组件装载、卸载
- 应用级别的 keep-alive
子应用开启保活模式后,应用发生切换时整个子应用的状态可以保存下来不丢失,结合预执行模式可以获得类似ssr的打开体验
-
纯净无污染
-
无界利用
iframe和webcomponent来搭建天然的js隔离沙箱和css隔离沙箱 -
利用
iframe的history和主应用的history在同一个top-level browsing context来搭建天然的路由同步机制 -
副作用局限在沙箱内部,子应用切换无需任何清理工作,没有额外的切换成本
-
性能和体积兼具
-
子应用执行性能和原生一致,子应用实例
instance运行在iframe的window上下文中,避免with(proxyWindow){code}这样指定代码执行上下文导致的性能下降,但是多了实例化iframe的一次性的开销,可以通过 preload 提前实例化 -
体积比较轻量,借助
iframe和webcomponent来实现沙箱,有效的减小了代码量 -
开箱即用
不管是样式的兼容、路由的处理、弹窗的处理、热更新的加载,子应用完成接入即可开箱即用无需额外处理,应用接入成本也极低
缺点
-
坑比较多
-
跨域问题: 用于 JS 沙箱的 iframe 的 src 必须指向一个同域地址导致的问题。
-
兼容性: 复杂的 iframe 到 webComponent 的代理机制,导致市面上大部分富文本编辑器都无法在无界中完好运行。所以有富文本的项目,尽量别用无界,除非你对富文本库的源码了如指掌。
-
长期维护性一般
-
内存开销较大
用于 js 沙箱的 iframe 是隐藏在主应用的 body 下面的,相当于是常驻内存,这可能会带来额外的内存开销。
micro-app
一款简约、高效、功能强大的微前端框架。
特性
- 只需一行代码,实现微前端,如此简单
img
- 无关技术栈,任何框架皆可使用
img
能力
概念图
image
概念
-
micro-app是由京东前端团队推出的一款微前端框架,它借鉴了WebComponent的思想,通过js沙箱、样式隔离、元素隔离、路由隔离模拟实现了ShadowDom的隔离特性,并结合CustomElement将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染,旨在降低上手难度、提升工作效率。 -
micro-app和技术栈无关,也不和业务绑定,可以用于任何前端框架。 -
样式隔离方案与 qiankun 的实验方案类似,也是在运行时给子应用中所有的样式规则增加一个特殊标识来限定 css 作用范围。
-
子应用路由同步方案与 wujie 类似,也是通过劫持路由跳转方法,同步记录到 url 的 query 中,刷新时读取并恢复。
-
组件化的使用方式与 wujie 方案类似,这也是 micro-app 主打的宣传点。
整体感觉 micro-app 是一种偏“现实主义”的框架,它的特点就是取各家所长,最终成为了功能最丰富的微前端框架。
优点
使用简单: 我们将所有功能都封装到一个类WebComponent组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。
功能强大: micro-app提供了js沙箱、样式隔离、元素隔离、路由隔离、预加载、数据通信等一系列完善的功能。
兼容所有框架: 为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何前端框架中都可以正常运行。
缺点
-
功能丰富导致配置项与 api 太多。
-
浏览器版本兼容问题。
-
主应用的样式影响到子应用
虽然我们将子应用的样式进行隔离,但主应用的样式依然会影响到子应用,如果发生冲突,推荐通过约定前缀或CSS Modules方式解决。
- 内存泄漏问题。
qiankun
概念
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。
目前 qiankun 已在蚂蚁内部服务了超过 2000+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
革新派
以 qiankun为主的革新派认为:iframe 问题很多,应避免使用它。 完全可以利用现有的前端技术自建一套应用隔离渲染方案。
完整的微前端解决方案
可能是你见过最完善的微前端解决方案
img
取名 qiankun,意为统一。我们希望通过 qiankun 这种技术手段,让你能很方便的将一个巨石应用改造成一个基于微前端架构的系统,并且不再需要去关注各种过程中的技术细节,做到真正的开箱即用和生产可用,。
特性
1、简单
任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
2、完备
几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
3、生产可用
已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。
核心设计理念
- 简单
由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
- 解耦/技术栈无关
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
优点
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
缺点
-
适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
-
css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
-
无法同时激活多个子应用,也不支持子应用保活;
-
无法支持 vite 等 esmodule 脚本运行;
EMP
-
EMP 方案是基于 webpack 5 module federation 的微前端方案。
-
基于Rust生态打造的高性能前端构建系统。
特性
聚焦高性能 与 微前端
基于Rspack、New ModuleFederation进行重构 结合 ESM 与 MF新特征进行全面定制 起到高效构建与热更新的作用
全面拥抱 Rust 生态
探索Rust的构建生态 引进 Rspack、Swc、LightningCss、Biome 等 进行提效
工程能化项目构建
多页面、微前端、ESM & ImportMap、远程调试、浏览器兼容 等 模式支持
通用的插件
得益于Rspack对Webpack的插件兼容,在开发和构建之间共享 Webpack、Rspack 插件接口。
多功能模块支持
对 TypeScript、JSX、CSS、Less、Sass、Wasm 等 支持开箱即用。
全链路的生命周期注入
构建阶段支持结合 Compile Hook 实现全周期的构建注入支持
优点
- webpack 联邦编译可以保证所有子应用依赖解耦;
- 应用间去中心化的调用、共享模块;
- 模块远程 ts 支持;
缺点
- 对 webpack 强依赖,老旧项目不友好;
- 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
- 子应用保活、多应用激活无法实现;
- 主、子应用的路由可能发生冲突;
特性对比
社区活跃度
编辑于 2024-11-12 09:26・IP 属地湖北
赞同 121 条评论
分享
喜欢收藏申请转载
赞同 12
分享
写下你的评论...
1 条评论
默认
最新
使用过qiankun,集成的时候也没有想象的那么顺利。
2024-12-12 · 重庆
回复喜欢