微前端运行时
谈到微前端绕不开的话题就是为什么不适用 iframe 作为承载微前端子应用的容器,其实从浏览器原生的方案来说,iframe 不从体验角度上来看几乎是最可靠的微前端方案了,主应用通过iframe 来加载子应用,iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制,但也是由于它的隔离性导致其并不适合作为加载子应用的加载器,iframe 的特性不仅会导致用户体验的下降,也会在研发在日常工作中造成较多困扰,以下总结了 iframe 作为子应用的一些劣势:
- 使用Iframe 会大幅增加内存和计算资源,因为 iframe 内所承载的页面需要一个全新并且完整的文档环境
- Iframe 与上层应用并非同一个文档上下文导致
- 事件冒泡不穿透到主文档树上,焦点在子应用时,事件无法传递上一个文档流
- 主应用劫持快捷键操作
- 事件无法冒泡顶层,针对整个应用统一处理时效
- 跳转路径无法与上层文档同步,刷新丢失路由状态
- Iframe 内元素会被限制在文档树中,视窗宽高限制问题
- Iframe 登录态无法共享,子应用需要重新登录
- Iframe 在禁用三方 cookie 时,iframe 平台服务不可用
- Iframe 应用加载失败,内容发生错误主应用无法感知
- 难以计算出 iframe 作为页面一部分时的性能情况
- 无法预加载缓存 iframe 内容
- 无法共享基础库进一步减少包体积
- 事件通信繁琐且限制多
基于 SPA 的微前端架构
尽管难以将 Iframe 作为微前端应用的加载器,但是却可以参考其设计思想,一个传统的 Iframe 加载文档的能力可以分为四层:文档的加载能力、HTML 的渲染、执行 JavaScript、隔离样式和 JavaScript 运行环境。那么微前端库的基础能力也可以参考其设计思想。
从设计层面采取的是基座+子应用分治的概念,部署平台负责进行服务发现和服务注册,将注册的应用列表信息下发至基座,通过基座来动态控制子系统的渲染和销毁,并提供集中式的模式来完成应用间的通信和应用的公共依赖管理,因此 Garfish 在 Runtime 层面主要提供了以下四个核心能力:
加载器(Loader)
- 负责注册平台侧提供的应用列表
- 负责加载和解析子应用入口资源
- HTML 入口类型,拆解 HTML Dom、Script、Style
- JS 入口类型,提供基础 Dom 容器
- 预加载能力
- 解析子应用导出内容
沙箱隔离(Sandbox)
- 提供代码执行能力,收集执行代码时存在的副作用
- 提供销毁收集副作用的能力
- 支持沙箱多实例,收集不同实例的副作用
路由托管(Router)
- 解决不同应用间的路由不同步问题
- 提供路由劫持能力,在主应用上管控子应用路由
- 提供路由驱动能力来拼装完整的平台的能力
子应用通信(Store)
- 建立通信桥梁
- 提供共享机制
应用生命周期
整个微前端子应用的生命周期基本可以总结为:
渲染阶段
- 主应用通过路由驱动或手动挂载的方式触发子应用渲染
- 开始加载应用的资源内容,并初始化子应用的沙箱运行时环境
- 判断入口类型
- 若入口类型为 HTML 类型,将开始解析和拆解子应用资源
- 若入口类型为 JS,创建子应用的挂点 DOM
- 将子应用存在”副作用“(对当前页面可能产生影响的内容)交由沙箱处理
- 开始渲染子应用的 DOM 树
- 触发子应用的渲染 Hook
销毁阶段
- 若路由变化离开子应用的激活范围或主动触发销毁函数,触发应用的销毁
- 清除应用在渲染时和运行时产生的副作用
- 移除子应用的 DOM 元素