# 什么是微前端?
微前端是一套架构体系。将Web应用由**单一应用**转变为**多个小型应用**聚合为一的应用。各个前端应用可以独立运行,独立开发,独立部署
# 微前端解决了什么问题?
1. **解决了大型项目开发维护的难题**
- 降低代码耦合度:随着应用功能的丰富,单页应用变得越来越庞大,难以维护。微前端的意义就是将这些庞大的应用进行拆分解耦,每个部分单独进行维护和部署,提升效率,减低风险。
- 提高代码的可维护性:单体应用中修改一处代码都可能产生意想不到的结果,微前端架构下,子应用的独立性,修改后不需要担心其他模块。
2. **技术栈灵活使用和升级**
- 微前端架构下,允许每个应用根据自身业务需求选择最合适的技术栈。
- 实现技术栈升级:单体应用技术栈统一,升级困难。 使用微前端,各模块可按需选择技术栈,方便技术迭代。
3. **提升团队协作与效率**
- 独立开发与部属:微前端架构下,不同的应用可以不同的团队和小组独立进行开发测试部署。每个团队可以根据自己的节奏进行工作,不需要其他小组完成。
- 明确职责分工:微前端架构下,使得每个小组专注自己负责的子应用的业务逻辑和功能的实现。
4. **优化用户体验和应用性能**
- 按需加载资源:微前端架构下,按需加载,只有当用户访问到某个子应用对应的功能,才会加载子应用的代码和资源。 这可以减少应用的初始加载时间,提高用户体验。
- 并行加载子应用:有些情况下,多个子应用可以并行加载,充分利用浏览器的并发请求能力,提升应用的整体加载速度。
# 实现微前端的方案
## 通过Nginx路由分发资源
通过Nginx配置反向代理,实现不同路由映射不同的应用。这种方式,主要是在运维侧配置,不属于前端改造层面。
优点:
- 实现简单
- 不用前端改造
- 技术栈无关
缺点:
- 体验差,每次切换应用重新加载页面
- 子应用无法共存
- 子应用之间通信困难
## iframe
优点
- 实现简单
- 天然实现沙盒机制,实现css js隔离。
- 多个子应用可以共存
- 技术栈无关
缺点
- 全局上下文完全隔离,内存变量不共享,子应用之间的通信,**数据同步过程比较复杂**(postmessage)。
- UI不友好: 只能局限于iframe宽高中。例如想要将iframe中的弹窗,居中显示
- 额外的资源消耗:iframe的加载速度和构建环境导致**白屏时间过长**。
- 路由状态消失:页面刷新之后,iframe的url状态丢失。
- iframe加载失败时,主应用无法感知
## Web Component
原生提供的api,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。
主要有三项技术组成:
- Custom element(自定义元素):通过调用Api创建一个自定义的标签。比如创建一个<my-element></my-element>.有自己的生命周期。
- Shadow Dom(影子dom):为元素创建一个独立的Dom树,与主文档的Dom树分离,从而实现元素功能的私有,不用担心和文档的其他部分发生冲突。
- HTML template(HTML)模板:可以编写不呈现在页面上的标记模板, 然后可以被作为自定义结构,多从使用。
优点:
- 技术栈无关:原生组件,在任何框架中都可以使用
- 应用隔离:Shdow DOM的特性, 使得各个子应用可以达到隔离的效果。
- 多个子应用可以共存
缺点:
- 改造困难,组件通信困难
- 浏览器新特性需要考虑兼容性问题。
## 路由分发+资源处理
每个子应用独立构建和部署,运行时由基座应用进行路由管理子应用的生命周期。再建立通信机制。是当下各个业务普遍采用的方案。
优点:
- 纯前端改造,体验好
- 无感知切换
- 子应用相互隔离
缺点:
- 需要设计和开发,
- 需要解决css冲突,js 对象污染,通信等问题
# 前端框架
结合上面的这些方案, 可以得出结论:要想实现一个微前端框架,需要解决三个问题:
- 实现路由分发:主应用中对其他微应用的路由转发
- 实现css,js隔离
- 解决通信问题
### single-spa框架 2017年
single-spa方案中应用分为两类:基座应用和主应用。 会在基座应用中维护一个路由表,每个路由对应一个子应用。切换路由的时候,动态加载js脚本。通过路由匹配的的机制实现对子应用生命周期的管理。
优点:
- 单页面设计,切换应用不需要重新加载页面
- 多个子应用可以共存
缺点:
- 不支持JS沙箱隔离机制,CSS样式隔离。
- 无法预加载
single-spa不支持JS隔离和CSS隔离。如果再设计JS沙箱隔离机制,CSS样式隔离,那就可以组合成完善的体系。这也就是很多框架对single-spa进行了二次封装。
基于**single-spa** 实现的方案qiankun、 Garfish等。
### qiankun(阿里巴巴:可能是你见过最完善的微前端解决方案)2019年
基于single-spa框架二次封装,目前国内影响力最大的微前端方案。 single-spa官方推荐。
qiankun主要的技术点:
- 继续延用single-spa处理路由和生命周期
- import-html-entry 加载子应用,并尝试未脚本提供一个隔离的执行环境,从而实现子应用的独立加载和运行,提供了一定的资源管理和隔离能力。
- 实现了隔离/通信机制
#### js隔离:
qiankun使用沙箱实现。 沙箱:给运行中的程序提供的隔离环境。 通过沙盒,为每个子应用提供隔离的运行环境。保证**全局变量**独属于当前**子应用**。
也就是说,每个子应用有自己的window。实现沙箱的原理如何理解呢?
1. 给每一个子应用创建一个独有的window对象
1. 解析每个子应用的js脚本字符串,包装成立即执行函数,将独有的window对象注入到各自的js代码中
1. 通过eval手动触发。
现在业界流行的沙箱分为两种:快照沙箱和Proxy沙箱。
qiankun在支持Proxy的环境中使用Proxy沙箱,否则使用快照沙箱。
**快照沙箱:**
原理:在子应用**激活**时,记录**当前**全局变量的状态,在子应用**卸载**时,将全局变量**恢复**到之前的状态,从而避免子应用对全局变量的修改影响到全局。
1. 在激活时,遍历window上的变量,存一个**快照**。
1. 卸载时,再次遍历window和之前的快照对比, 将变更的记录下来,再将window恢复到快照状态。
1. 当应用再次切换的时候,就可以把上次变更的变量恢复到window上,实现沙箱的切换。
**Proxy沙箱:**
原理:利用ES6的Proxy对象创建一个代理对象,拦截对全局变量的读和写,通过维护一个独立的变量存储空间,实现子应用对全局变量的操作隔离。这种方式比快照沙箱更加高效,因为它不需要每次激活和卸载时进行全局变量的快照和恢复的操作。天然允许多实例的存在(一个页面挂在多个子应用)
1. 先创建一个空对象,fakeWindow。通过proxy代理,作为全局对象。
1. 子应用需要添加全局变量的时候, 直接再fakeWindow上添加,
1. 子应用需要读取的时候,先从fakeWindow读取,读不到就直接从window上获取。
#### css隔离
- 严格隔离:基于Web Component的shadow Dom实现。qiankun中并不是所有的子应用都可以使用,因为shadow dom可能会限制某些样式和布局的灵活性,并且不是所有的HTML元素都能最为Shadow Dom的宿主。
- scoped样式隔离:在不使用Shadow Dom的时候,通过过遍历html中所有的style节点,为每一个css添加一个唯一前缀。通过属性的配置进行隔离。 如果一些动态添加样式的情况,qiankun会对操作进行劫持。
#### 通信
子应用通信方面。qiankun使用了基于**发布订阅模式**实现。 基座应用中会定义事件中心Event,每个微应用分别来注册事件,触发的时候再统一下发。
缺点: 不支持子应用保活。不支持vite等esmodule脚本运行。资源重复加载。
### micro app(京东) 2020年
基于webCompont实现
js隔离:和qiankun一样
css隔离:shadow dom 或者 添加前缀
路由:使用虚拟路由,对子应用的路由操作进行劫持。
### 无界(腾讯)2021年
”继承iframe的优点,不足iframe的 缺点“
基于 Web Components + iframe
#### js隔离
利用iframe的隔离性,把js代码放到iframe里执行。
#### css隔离
使用shadow Dom隔离css。
在A应用中构造一个shadow和iframe。 将B应用的HTML写入shadow中, js运行在iframe中,iframe的url和A应用保持同域并且保留B应用的路由信息,这样保证在iframe中运行路由正确。
如何解决iframe的缺点呢?
- dom割裂严重问题:主应用中提供一个容器,将子应用的shdowRoot插入到这个容器中。shadowRoot内部,是可以设置css属性相对于主应用视图。
- 路由状态丢失问题:使用URL存储路由状态,当iframe重新加载时,根据url中的信息恢复之前的状态。 在iframe中操作路由时,也会同步到主应用。
- 通信困难问题:在将子应用放在iframe中执行的时候,保持了同域,这样就解决了通信问题。还实现了去中心化的通信机制。每个子应用都可以作为事件的发布订阅,不需要依赖统一的事件中心。子应用间可以直接通信, 并且还可以提供了筛选机制,让事件只能在某几个子应用之间使用。使用时需要注意每个子应用都应该有明确的事件注册和注销机制,防止内存泄漏。
- 加载慢白屏问题: wujie实例可以提前实例化,加快首屏展示。 切换白屏问题,无界可以将实例缓存下来,子应用切换成本降低。
一些细节
- 为子应用创建iframe后,会设置src和主应用同域,这时候就会加载主应用的资源,这时候必须在iframe实例化完成后资源未加载完时,及时中断,防止污染。
- 子应用在iframe内部访问widow,document,location都会被劫持到对应的proxy。
- iframe中拦截document对象,统一将dom指向shadowDom,完成约束(确保修改自己的dom)
- 拦截window对象,共享一个window
- 拦截location对象,处理子应用的跳转操作,避免刷新页面; 通过拦截,可以将子应用的路由状态保存,保存路由状态。
缺点:shadowRoot和proxy的兼容性问题。 内存花销大。
### Garfish(字节跳动)
字节跳动自研框架。设计层面采取的仍然是 基座+子应用。
在js隔离代码执行器方面, qiankun使用的eval执行,Grafish使用的 new Function();
传入一个字符串之后,后面执行的都是同一个函数,但是eval需要每次都处理,性能上更高一些。
Garfish在部署层 通过分析子应用的依赖信息提取公共基础库解决**依赖重复加载**的问题。
# 总结
需要了解什么是微前端? 微前端解决了什么问题? 如何实现沙箱隔离? 各大框架的核心是什么?
# 参考文章
- [微前端学习系列(一):微前端介绍](https://juejin.cn/post/6955341801381167112#heading-15)
- [微前端-最容易看懂的微前端知识](https://juejin.cn/post/6844904162509979662?searchId=20250112112714247869C1C2AA06AC19D4#heading-4)
- [除了 Qiankun, 这些微前端框架或许更适合你「建议收藏」](https://juejin.cn/post/7121883538311348238?searchId=202501132256327C8868AAB4A77A21B5B6#heading-8)
- [微前端五大门派大 Battle](https://juejin.cn/post/7338230967390224435?searchId=20250112112714247869C1C2AA06AC19D4#heading-11)
- [京东零售前端团队 | 微前端框架MicroApp 1.0正式发布](https://juejin.cn/post/7327892137880649782?searchId=202501152208169BDFDB074C01DEC6C182#heading-6)
- [浅析micro-app](https://juejin.cn/post/7233697025711013943#heading-10)
- [wujie官网](https://wujie-micro.github.io/doc/)
- [Garfish微前端架构设计](https://www.garfishjs.org/blog/architecture.html)