第七期 | 微前端专场笔记

1,990 阅读10分钟

微前端

2020.5.30 前端早早聊微前端专场


什么是微前端?解决什么样的问题

玉伯:微前端的前提,还是得有主题应用,然后才有微组件或微应用,解决的是可控体系下的前端协同开发问题。(含空间分离带来的协作和时间延续带来的升级维护。

这个概念来源于后端微服务,将微服务概念用于浏览器端,以使多个SPA应用接入到框架应用中。
要解决的问题是团队平台系统多且相互独立,系统体量大且页面多,开发效率低、接入成本高。

基于这样的问题,有没有其他的解决方案?

  • 巨石应用:随着页面增多,开发效率和稳定性会无法满足要求。
  • iframe: 有用户体验问题,如果体量不大还是很推荐
  • 框架组件:不满足一个系统的心智,同时长期维护成本高。
  • 微前端:在大型系统的业务场景下,具备体验和效率的平衡点。

大型系统为什么不考虑iframe?

  • 加载延迟
  • 用户子应用返回时直接回到Home页
  • 说白了就是用户体验和UI显示不太好

微前端业务价值

业务价值有下面几点:

  • 应用过多时,域名太多,不容易记
  • UI风格不一致,不易维护
  • 多应用操作断层,希望更连贯些。

工程价值为:

  • 统一管理应用
  • 发布提速
  • 模块拆分利于多人协作

微前端原理

以icestark为例,看看微前端做了什么:


路由劫持Proxy解决的问题:

  • 应用间跳转: pushState/popState/replaceState/hashChange
  • 应用内跳转: history

加载渲染:除了挂载之外,还要关心卸载逻辑。以避免应用污染

微前端内核

大致可以分为一个主应用框架和微应用容器 主应用框架负责的功能有:

  • 渲染
  • 加载
  • 路由
  • 通信

微应用容器包括 JS bundle context 和沙箱

展开来看各自具体需要解决的问题:

当前社区方案

  • Single-Spa ✅
  • Qiankun ✅
  • Luigi 基于ifame  ❎
  • Feature-Hub  ❎
  • Federation  (适合单体拆分,后面会有介绍)
  • icestark

Single Spa vs Qiankun
Qiankun实际上相当于single-spa + sandbox + import-html-entry

如何集成?

集成分为两种模式:简单模式和精细模式

简单模式

整个页面覆盖渲染 + 导航器的浮层

精细模式

提供 Content 渲染区域给子应用

  • 协定菜单数据结构,给子应用 menuService
  • 提供自定义 Menu、Header、Footer 的自定义接口
  • 注入统一请求库,利用 LRU 策略缓存接口

单体拆分


动态加载模块可能遇到的问题:
微前端和 Webpack 如何更好的结合?
Tobias Koppers 给出了答复:Federation, 但是目前文档还没跟上。
建议:可以去试着玩,但不要在生产上用。

Federation 比起其他解决方案有什么优势?
Federation 可以对依赖库做版本控制、自动加载缺失的 vender,可以支持远程加载,解决了 npm link 在开发时的不便利。

实施过程中可能遇到的问题

部分问题及解决的方案:

路由调度问题

思考:前端为什么要有路由以及路由产生的背景? 笔记:为了正确的维护页面的状态,可以通过url变化/页面交互来反映页面的状态变化。

需求是无论是点击跳转还是导航跳转/返回页面都能正常显示。
不同方案会带来哪些问题?
由简到难分别是:

  • 主应用完全不处理路由:

主应用不根据路由调度子应用,子应用内部状态变化按原样反映到主应用路由。
问题:路由冲突,无法初始化路由。

  • 子应用和主应用共享路由

主应用根据路由调度子应用,子应用内部状态变化根据特定规则反映到主应用的路由。
新的问题:需要将约定侵入到子应用的逻辑

// 使⽤子应⽤名称来对共享资源切⽚
const APP_NAME = 'cow';
 <Route path={`/${APP_NAME}/home`} component={Home} />
 <Route path={`/${APP_NAME}/detail`} component={Detail} />
  • 子应用维护隔离的路由

主应用根据路由调度子应用,子应用内部动态变化反映到各自沙箱,互不干扰,但不会反映到主应用的路由中
新问题:由于⽆法感知⼦应⽤路由的变化, 主应⽤不能体现路由逻辑。

  • 子应用路由同步回主应用

主应⽤根据路由调度子应⽤,⼦应用内部的状态变化反映到各自的沙箱中,互不干扰,并且最终会反映到主应⽤用的路由中。

ifram e路由变化时对主应用 history 有什么影响?
关键词:Joint Session History 标准

全局的样式冲突

Shadow DOM | Css Scope
业务代码样式隔离: CSS Modules,类名+哈希
基础组件样式隔离: namespace,在样式名前加前缀,统一修改

JS污染

  1. tc39/proposal-realms 类似于一个沙箱环境,但由于浏览器未被实现,还是得自己去写。
  2. new Proxy 拦截
// 创建沙箱
const sandbox = new Proxy(Object.create(null), {
 get(){},
  set(){},
  has(){},
  // ...
})
  
// 执行代码
const execScript = `with (sandbox) {${script}\n}`;
const execCode = new Function('sandbox', execScript);
execCode(sandbox);

某些库多版本

Externals => DLL

发布问题

因为应用之间有依赖,发布有次序性问题,除了 js 和 css 之外,对 html 也需要有 hash 版本( index.[hash].html ),同时需要开发一个配置中心,在模块发布成功后通知,并且主应用最后发布。

webpack升级问题

4.x->5(beta):把 loader 和 plugin 升到最新,有问题去 github issue 看

沙盒设计

这块听的不太懂,大概记些关键词。

沙盒在微前端中起到什么作用

  • CSS隔离
  • 全局变量的隔离

沙盒能做什么

老的iframe问题:

  • 一个站点拆成N个frame
  • 每个frame单独一个独立域名
  • 独立上下线,独立运行时
  • 数据共用困难:登录身份、站内信、跨模块通信

前端沙盒应该像浏览器 Docker,具有下面优势

  • 开发者必须体会不到环境区别,即运行时没有环境差异

怎么做?

1.参考单核、操作系统进程模拟进程切换策略,

  • 通过对路由切换的封装,模拟单进程
  • 通过对事件循环封装,模拟单核多进程

2.用 Context 切换模拟线程安全,新沙盒即将激活时,查找当前激活中的沙盒。

  • 保存现场,存储context
  • 恢复之前的context

3.Context 切换的笛卡尔积

  • 比较并切换
  • 沙盒数量 N 的笛卡尔平方
  • 退回「初始」context
  • 恢复之前 diff 的 context

qiankun分享

核心价值:「技术栈无关」

为什么需要技术栈无关
多个项目ABC,可能会有 React@16+webpack@5 和 jQuery+Gulp这种冲突,那么要解决的问题就不言而喻。

App Entry 选择和组合时机

  1. 运行时还是构建时?
  • 构建时:构建时容易做公共依赖提取等优化,缺点是主子应用构建方案、工具耦合且主子应用必须一起发布
  • 运行时:子应用独立构建,运行时动态加载,优点是主子应用完全解藕且与技术栈无关,缺点是有运行时损耗 ✅
  1. 入口选择,HTML 还是 JS ?
  • HTML , 它包含了应用所需的所有信息:网页结构和元信息、js 和 css 等资源,✅
  • JS: 便于构建时优化,但容易产生耦合,打包产物体积太大。

应用的隔离

主要是 JS 的隔离,需要两个 JS 沙箱。Global Env 外部全局环境和 Render Env 内部沙箱环境
两种方法:

  1. 快照
  • 应用挂载/卸载时记录快照
  • 应用切换时快照恢复环境
  • 缺点:不能支持多个实例
  1. Proxy
  • 代理沙箱内部操作,不影响 Global Env
  • 将副作用局限在 Render Env 内部


解决完 JS 隔离,再看样式冲突

  1. 动态样式表,当 A 切换到 B 时,样式也切换,删 A 激活 B, 但是只能保持一个应用激活,多个应用时没办法保证。
  2. 工程化手段 ✅
  • 通过前缀依赖约定,容易出纰漏
  • 通过编译生成不冲突的选择器名,但只能在构建时使用,依赖预处理器和打包工具
  • CSS-in-JS,CSS 和 JS 编码到一起,可以彻底隔离,但会有一些运行时的开销
  1. Shadow DOM,实际应用时还是有一些问题,需要去改造子应用。
  2. 样式隔离 RFC: runtime css transformer


解决完隔离问题,就要看通信问题了。

  1. 基于URL通信,比如:URL 为 /b/func-log?data=aaa, 即调用 b 应用的 log 方法打印 data 为 aaa,缺点是消息能力弱
  2. 发布/订阅模型。windows 自带事件总线 CustomEvent 来分发事件,主子应用订阅。缺点是全局命名冲突,缺乏管控
  3. 基于 props, 主应用向子应用传递 state 和事件,子应用 setGlobalState,缺点是主子应用耦合较强有新的 API 学习成本
  4. 再有就是全局变量、共用 Redux 来实现子应用之间的通信。

具体使用还是看实际应用场景。

上面问题小结下可以由下面图代替:

书籍推荐

《知行》:技术管理
《领域驱动设计》:领域如何划分,DDD
《守望者(Watchmen)》: 美漫
《金字塔原理》 排样自己说/想/做/表演的能力
《松本行弘的程序世界》 深入理解程序设计背后的故事

相关文章

总结

这一下午主要了解了以下几点:

  • 什么是微前端
  • 产生背景
  • 业务场景
  • 遇到的问题及解决方案
  • 社区方案

再说一些与发展相关的想法,我之前在面试的时候别人问到项目亮点总是答不上来,还是因为遇到不会的问题总是直接上手找库、找框架,把重点放在了实现功能,而不去考虑这些框架的设计背景,他们的理念、原理,核心。(顶多是被动的去学习 Vue 源码)所以感觉这次的分享学到了很多。尤其是最后一场分享可以说是总结全文了。


前端早早聊大会目标成为用得上、听得懂、抄得走的技术大会,计划 2020 年办 >= 15 期,由前端早早聊与掘金联合举办,前端早早聊大会行程动态、录播视频/PPT/讲稿资料下载请关注 「前端早早聊」 公众号跟进。

6 月 13 日举办第九届 - 前端如何搞文档,报名请戳:www.huodongxing.com/go/tl9 ,海报及讲师行程如下:

6 月 20 日举办第十届 - 前端如何搞跨端跨栈,报名请戳:www.huodongxing.com/go/tl10 ,海报及讲师行程如下:

6 月 27 日举办第十一届 - 女生前端职业发展专场,报名请戳:www.huodongxing.com/go/tl11 ,海报及讲师行程如下:

看完若有启发,举手给作者点个赞吧