1 概述
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用,各个前端应用还可以独立运行、独立开发、独立部署。
实现微前端不管是采用什么方案,本质都是一样的,微前端各个部分之间相互独立、独立部署的能力本质是在允许构建孤立或松散耦合的服务 。
微前端的一般结构如下:
2 使用场景
1)治理巨石应用
单体应用不断膨大导致理解和修改的成本上升,代码的提交到实际部署的周期越来越长,并且很容易出现问题。
这时,我们可以把原有功能抽离为单独的子应用,而新的功能模块作为新的子应用嵌入,在逐渐重构的过程中,既要保证中间版本能够平滑过渡,还能持续交付新的功能。
2)应用功能自由组合拆分及定制化开发
在业务方面,经常可能会面临不同特定的定制化需求或者功能组合,那实际上我们可以对每个单独的功能作为一个单独的子系统开发、子系统间的耦合,只需要规定好相应的通讯方式和内容,不需要关注对方的实现,在需要的时候自由组合即可。
3 使用微前端需要关注什么
在实践一个微前端的架构,我们需要关注哪些切实的问题呢?
通讯和状态管理
微前端的实现本质也是一种组合的思想,子应用间的组合跟功能中组件的组合有类似之处,组件之间的组合最重要的就是通讯和状态管理。
不同的子应用之间组合而成,无法避免状态管理的问题,无论是全局下的状态管理、几个子应用间的局部状态管理,还是单个子应用间的状态管理,以及状态的上传和下发,都将是我们需要去考虑的问题。
在采用微前端架构的时候,很多时候遇到的问题可以转化成:如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。
这本质上是两个问题:
- 子会响应父的状态变化,父会在子初始化之后初始化,子会在父变更后变更,导致子状态必须在子组件内部
- vue/react 以及状态管理,只有子向父 dispatch 事件的能力,父无法向子 dispatch 事件
实现一个发布订阅模型就能解决这个问题,这也是为什么 qiankun 之类的微前端方案的应用间通讯方式是使用发布订阅的方式进行的,这样 parent 和 child 就可以彼此通过事件传递消息,且独立变化。
工作空间的独立
1)js沙箱
因为每一个子应用都是一个单独的项目和应用,在同一个页面中出现多个子应用是很常见的场景。
出于安全考虑,例如全局变量污染、多版本库等,还有各种复杂场景下的执行问题,我们需要对每个子应用间的 js 的工作空间进行隔离,使每个子应用内的执行自洽,只关注输出和输入,js沙箱的实现成为微前端方案中需要关注的一点
js沙箱是一种安全机制,为 js 代码创建了一个隔离的运行环境,使得在沙箱中运行的代码不会对全局环境产生影响,沙箱环境可以限制代码对某些资源的访问权限,比如全局变量、浏览器 API 等
js沙箱机制相关资料:https://zhuanlan.zhihu.com/p/527437146
2)css沙箱
虽然是微前端的结构,但本质上同一个页面中每个子应用的挂载依旧在同一个 dom 树上,那子应用间的样式自然不应该出现互相干扰的情况,并且在子应用切换时可以自行装载和卸载。
PS:单纯的 iframe 实现微前端的方案虽然有很多问题,但是在 js 和 css 的隔离上无疑是成本最低且较好的实现
路由管理
1)路由劫持
方案原理大致是监听 hashchange 事件,劫持浏览器 history 下的 pushState 和 replaceState
2)主应用控制路由
实现思路是主应用使用现有的路由库(vue-router或react-router),子应用使用 webpack 提供的 import() 函数动态加载子应用的模块
3)子应用的资源加载方式
资源加载方式影响页面加载速度,主要分为 html entry 和 js entry,前者加载是按原来方式打包出来的一个 html 文件,后者是加载子应用打出来的整个 js 文件。
如果将整个微应用打包成一个 js 文件,那常见的打包优化都用不了,例如:按需加载、首屏资源加载优化、css独立打包等
预加载
在微前端的方案中,每个子应用都是独立的,如果不做处理的话,在一个页面存在多个子应用的情况下,每个子应用的加载都是独立的,在这种情况下可能会出现白屏情况,导致用户体验不好。
并且在实际使用中,浏览器大部分是处在空闲的,我们要怎么利用这样的空闲时间,去加载其他子应用的 js 呢?
公共依赖的处理问题
子项目多了之后,公共依赖如果处理不好,会造成各种重复工作,bug风险也会增长,不利于后期维护。
是否支持应用保活
在切换子应用后,之前的状态是否需要保存,事关 keep-alive 之类在微前端的架构中怎么做
子应用之间的互相嵌套
是否支持子应用之间的互相嵌套,子应用组件互相嵌套后的通讯和状态管理又需要怎么做
4 常见技术方案
iframe
通过 iframe 我们可以很方便地将一个应用嵌入到另一个应用中,而且两个应用之间的 css 和 js 是相互隔离的,互不干扰
优点:
- css 和 js 天然隔离,互不干扰
- 多个子应用可以并存,不需要对现有应用进行改造
- 与技术栈无关
缺点:
- 每次切换应用时,浏览器都需要重新加载页面
- UI 不同步,DOM 结构不共享
- 全局上下文完全隔离,内存变量不共享,子应用之间通信、数据同步过程比较复杂
single-spa
在 single-spa 方案中,应用被分为两类:基座应用和子应用,子应用是需要聚合的应用,基座应用是另外一个单独的应用,用于聚合子应用
和单页应用的实现原理类似,single-spa 会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应页面
优点:
- 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验
- 多个子应用可并存
- 与技术栈无关
缺点:
- 需对原有应用进行改造,应用要兼容接入 single-spa 和独立使用
- 使用复杂,子应用加载、隔离、通信等问题,需要自己实现
- 子应用间相同资源重复加载
- 启动应用时,要先启动基座应用
qiankun
qiankun 是在 single-spa 的基础上做了二次开发,在框架层面上解决了使用 single-spa 时需要开发人员自己编写子应用加载、通信、隔离等逻辑的问题,是一种比 single-spa 更优秀的微前端方案
优点:
- 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验
- 相比 single-spa,解决了子应用加载、隔离、通信等问题,使用起来相对简单
- 多个子应用可并存
- 与技术栈无关
缺点:
- 需对原有应用进行改造,应用要兼容接入 qiankun 和独立使用
- 相同资源重复加载
- 启动应用时,要先启动基座应用
webpack5:module federation
webpack5 提供了一个新特性:module federation,基于这个特性,我们可以在一个 JavaScript 应用中动态加载并运行另一个 JavaScript 应用的代码,并实现应用之间的依赖共享
通过 module federation,我们可以在一个应用里面动态渲染另一个应用的页面,这样就实现了多个子应用的聚合
优点:
- 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验
- 相同资源不需要重复加载
- 应用启动后,无需加载与自己无关的资源
- 免登友好
缺点:
- 构建工具只能使用 webpack5
- 对老项目不友好,需要对 webpack 进行改造