理论
微前端:运行时复用方案及其适用场景
微前端这个概念的定义极为混乱,导致社区中谈论微前端时,颇有自说自话的感觉。本文旨在厘清目前社区中存在的对“微前端”一词的不同解释。
总的来说,对微前端的各种解释的一个最大公约数,是指一种在运行时加载欲复用的代码的方法,并且你项目的编译工具并不知道被复用代码的存在,这意味着——待复用的代码虽然未必可以独立使用,但是是可独立部署的(有独立的部署流程,最终有一个可访问的 URL) 。因此传统的“根据路由动态加载组件”之类的方案,甚至更早的 module loader(如 require.js 之类的),虽然也是运行时加载复用代码,但不是微前端方案。
对于以下场景,运行时复用比传统的构建期复用更好:
- 欲复用的代码源码不在你项目的 repo 里,因此无法在构建期复用。这可能是因为:
- 代码是其它团队的非公开代码
- 属于无法合并为 monorepo 的另一个项目
- 你嫌项目 repo 太大,正把这段代码移往别的 repo
- 欲复用的代码在你项目的 repo 里,但由于采用了不同的技术栈等原因,构建期复用比较麻烦
- 欲复用的代码(例如,组件库)实际上被大量不同的项目同时复用,一旦改动,依赖于它的项目都需要重新编译、发布
Dan 认为,微前端方案更像是由于团队原因而做的技术上的迂回。
qiankun creator 的这篇文章也指出了微前端本质是个团队架构问题。
微前端的两种主流解释
现阶段(2021年底),在上述公约数的基础之上,社区里对“微前端”一词有两种主流解释。这两种解释的区别在于代码的复用粒度。
- 解释一:微前端 = 运行时动态加载执行 JS 模块。在英文社区中,由后端术语“micro-service 借鉴而来的术语 “micro-frontend” 基本上是指这种解释
- 代表框架: single-SPA
- 框架定位:运行时模块(组件、utils)加载器
- 解释二:微前端 = 运行时动态加载执行 App。在中文社区中,受 qiankun 的影响,术语“微前端”基本是指这种解释
- 代表实现:qiankun
- 框架定位:升级版的 iframe
搜索社区文章时应当注意,有些文章(通常是英文社区的文章)使用解释一;有些文章使用解释二(通常是中文社区的文章)。有些文章混用了两种解释。
联系
这两种解释并未泾渭分明,而是有所联系的——App 级的复用粒度,也可以视为模块级复用(将 main.js 视为模块)。正因如此,qiankun 是基于 single-SPA 的封装。
区别
直接动机
- 模块级复用(single-SPA):由于工程原因,你需要跨 repo 或跨技术栈的代码模块复用
- App 级复用(qiankun):因为存量的业务原因,你需要集成多个现有的独立 App;因为存量的业务或工程原因,你想拆分一个大 App 为多个小的独立 App,再集成
主要关注点
- 模块级复用(single-SPA):模块生命周期、模块加载(JS Entry)、依赖共享(shared dependencies)、模块间通信、模块 API 版本管理
- App 级复用(qiankun):App 生命周期、App 资源(预)加载(HTML Entry)、CSS / JS 沙箱、主/子应用路由等
微前端的其它解释
- 宏大叙事型:常见于英语社区中。大体上描述了某种前后端协同渲染、分发页面组件的方案,本文不进行详细介绍,可以参阅:
- 待续…
牢记:微前端不可作为业务架构选型的一种选项!
正如前文反复强调的那样,微前端方案(无论何种解释),本质上是受工程现状或团队权责问题困扰的无奈之举,是一种解决存量问题的方案,通常不可将其作为全新项目中“进行业务划分的架构选型”的选项(除非你的项目在立项之初就碰到了项目团队的权责问题)。对于新项目,你的真实问题其实是——如何处理领域模块。可参考这几篇:
一句话:在新项目中,考虑引入“微前端”的场景应当是业务无关的性能优化/部署发布流程优化。
此处列出一些在我看来,实质上是“试图解决一些本不存在的问题”的方案。它们共同的一个显著特点是,都提供了 CLI 工具供用户在创建新项目时使用,并让用户把这些项目组合起来形成“微前端”:
最后,即使是为处理存量问题,也应该意识到,微前端只是一个选项,而不是标准答案。此外,绝对不能使用任何微前端方案加载不可 100% 信任的第三方代码。
工程
无框架实现
美团的这两篇文章介绍了不使用框架,手动进行 App 级微前端项目改造的过程。
框架实现
框架介绍:single-SPA
更详细的介绍可见 【微前端】single-spa 到底是个什么鬼 一文
single-SPA 的作用
sinle-SPA 的只起一个简单的唯一作用:在运行时用用户给定的加载方法,加载 JS 模块,并执行上面暴露出的生命周期钩子。
可见,single-SPA 甚至没提供加载器(loader)的实现(虽然以官方推荐的形式建议大家用 SystemJS)。qiankun 使用了自己实现(而不是 single-SPA 官方推荐的 SystemmJS)的一套基于 umd 格式的加载方案(见源码文件 loader.ts)。
single-SPA 的三类模块
single-SPA 划分了三类可复用的模块:Application、Parcel、Utility
Application | Parcel | Utility | |
---|---|---|---|
模块规模 | 大。通常是某个 App 的 main.js | 可大可小 | 小 |
专门的 API | 有 | 有 | 无。用户需自行调用加载逻辑,并正确配置 Webpack 进行加载 |
根据路由触发加载/卸载 | 是 | 否 | 否 |
生命周期 | single-SPA 自动控制(也支持手动 unload) | 手动控制 | 无 |
额外能力 | 被注入了加载 Parcel 的能力(且自动同步生命周期) | 无 | 无 |
综上,Application 配置简单功能更多,Parcel 控制灵活。基于 single-SPA 的 qiankun ,有时将子应用视为 single-SPA 的 Application(当根据声明式路由进行加载时),有时则是 Parcel(当使用命令式进行加载时)。
框架介绍:qiankun
作为 App 级微前端框架的代表性作品,qiankun 的功能是此类框架的标准功能。可见 【微前端】qiankun 到底是个什么鬼 一文。
类似的框架还有(不出意外,都是国产):
它们的目的基本一致—— 即 App 级复用。个别功能/实现/目标框架略有不同。例如 MicroApp, mfy 主打无侵入性,Afla 主打依赖共享等。
依赖共享及其方案
依赖共享指的是,当用微前端方案在运行时加载模块 / App 时,防止重复加载相同的内容(如模块、依赖库等等)。甚至有人认为,微前端基本上等价于依赖共享(这种观点着重强调低运行时开销的复用能力)。
此处介绍依赖共享的两种实现方案(这两种方案可任意与 single-spa / qiankun或其它微前端框架进行集成):
- SystemJS 的 import maps
- Webpack 5 的模块联邦。它甚至声称自己本身就是一个微前端方案(它确实满足本文开头概括的“微前端”公约数)