关于微前端,你想知道的都在这!

更多请关注微前端专题 https://codeteenager.github.io/Micro-Frontends/

介绍

微前端官网:https://micro-frontends.org/

问题:如何实现多个应用之间的资源共享?

之前比较多的处理方式是npm包形式抽离和引用,比如多个应用项目之间,可能有某业务逻辑模块或其他是可复用的,便抽离出来以npm包的形式进行管理和使用。但这样却带来了以下几个问题:

  • 发布效率低下:如果需要迭代npm包内的逻辑业务,需要先发布npm包之后,再每个使用了该npm包的应用都更新一次npm包版本,再各自构建发布一次,过程繁琐。如果涉及到的应用更多的话,花费的人力和精力就更多了。
  • 多团队协作容易不规范:包含通用模块的npm包作为共享资产,每个人拥有它,但在实践中,这通常意味着没有人拥有它,它很快就会充满杂乱的风格不一致的代码,没有明确的约定或技术愿景。

这些问题让我们意识到,扩展前端开发规模以便多个团队可以同时开发一个大型且复杂的产品是一个重要但又棘手的难题。因此,早在2016年,微前端概念诞生了。

微前端概念

“微前端”一词最早于 2016 年底在 ThoughtWorks Technology Radar 中提出,它将后端的微服务概念扩展到了前端世界。微服务是服务端提出的一个有界上下文、松耦合的架构模式,具体是将应用的服务端拆分成更小的微服务,这些微服务都能独立运行,采用轻量级的通信方式(比如 HTTP )。

微前端概念的提出可以借助下面的 Web 应用架构模式演变图来理解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpWJhCJG-1677042958816)(/application/foundation/1.png)]

最原始的架构模式是单体 Web 应用,整个应用由一个团队来负责开发。

随着技术的发展,开发职责开始细分,一个项目的负责团队会分化成前端团队和后端团队,即出现了前后端分离的架构方式。

随着项目变得越来越复杂,先感受到压力的是后端,于是微服务的架构模式开始出现。

随着前端运行环境进一步提升,Web 应用的发展趋势越来越倾向于富应用,即在浏览器端集成更多的功能,前端层的代码量以及业务逻辑也开始快速增长,从而变得越来越难以维护。于是引入了微服务的架构思想,将网站或 Web 应用按照业务拆分成粒度更小的微应用,由独立的团队负责开发。

从图上可以看出,微前端、微服务这些架构模式的演变趋势就是不断地将逻辑进行拆分,从而降低项目复杂度,提升可维护性和可复用性。

所以说微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。有一个基座应用(主应用),来管理各个子应用的加载和卸载。

微前端的应用场景

从上面的演变过程可以看出,微前端架构比较适合大型的 Web 应用,常见的有以下 3 种形式。

  • 公司内部的平台系统。这些系统之间存在一定的相关性,用户在使用过程中会涉及跨系统的操作,频繁地页面跳转或系统切换将导致操作效率低下。而且,在多个独立系统内部可能会开发一些重复度很高的功能,比如用户管理,这些重复的功能会导致开发成本和用户使用成本上升。
  • 大型单页应用。这类应用的特点是系统体量较大,导致在日常调试开发的时候需要耗费较多时间,严重影响到开发体验和效率。而且随着业务上的功能升级,项目体积还会不断增大,如果项目要进行架构升级的话改造成本会很高。
  • 对已有系统的兼容和扩展。比如一些项目使用的是老旧的技术,使用微前端之后,对于新功能的开发可以使用新的技术框架,这样避免了推翻重构,也避免了继续基于过时的技术进行开发。

微前端的架构模式

微前端架构按集成微应用的位置不同,主要可以分为 2 类:

  • 在服务端集成微应用,比如通过 Nginx 代理转发;
  • 在浏览器集成微应用,比如使用 Web Components 的自定义元素功能。

服务端集成

服务端集成常用的方式是通过反向代理,在服务端进行路由转发,即通过路径匹配将不同请求转发到对应的微应用。这种架构方式实现起来比较容易,改造的工作量也比较小,因为只是将不同的 Web 应用拼凑在一起,严格地说并不能算是一个完整的 Web 应用。当用户从一个微应用跳转到另一个微应用时,往往需要刷新页面重新加载资源。

这种代理转发的方式和直接跳转到对应的 Web 应用相比具有一个优势,那就是不同应用之间的通信问题变得简单了,因为在同一个域下,所以可以共享 localstorage、cookie 这些数据。譬如每个微应用都需要身份认证信息 token,那么只需要登录后将 token 信息写入 localstorage,后续所有的微应用就都可以使用了,不必再重新登录或者使用其他方式传递登录信息。

浏览器集成

浏览器集成也称运行时集成,常见的方式有以下 3 种。

  • iframe。通过 iframe 的方式将不同的微应用集成到主应用中,实现成本低,但样式、兼容性方面存在一定问题,比如沙箱属性 sandbox 的某些值在 IE 下不支持。
  • 前端路由。每个微应用暴露出渲染函数,主应用在启动时加载各个微应用的主模块,之后根据路由规则渲染相应的微应用。虽然实现方式比较灵活,但有一定的改造成本。
  • Web Components。基于原生的自定义元素来加载不同微应用,借助 Shadow DOM 实现隔离,改造成本比较大。

这也是一种非常热门的集成方式,代表性的框架有 single-spa 以及基于它修改的乾坤。

微前端的优势:

同步更新

对比npm包方式抽离,让我们意识到更新流程和效率的重要性,微前端由于是多个子应用的聚合,如果多个业务应用依赖同一个服务应用的功能模块,只需要更新服务应用,其他业务应用就可以立马更新,从而缩短了更新流程和节约了更新成本。

增量升级

迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用AngularJS开发维护已经有三年时间,但是随着时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS已经不能满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接迁移是不可能的,在新的框架中完全重写也不太现实。

使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求,然后再使用微前端架构将旧的项目和新的项目进行整合,这样既可以使产品得到更好的用户体验,也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。

独立部署与发布

在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。

在使用了微前端架构后,可以将不能的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。

独立团队决策

因为微前端架构与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。

如何实现微前端

  1. 多个微应用如何进行组合?

    在微前端架构中,除了存在多个微应用以外,还存在一个容器应用,每个微应用都需要被注册到容
    器应用中。

    微前端中的每个应用在浏览器中都是一个独立的 JavaScript 模块,通过模块化的方式被容器应用启
    动和运行。

    使用模块化的方式运行应用可以防止不同的微应用在同时运行时发生冲突。

  2. 在微应用中如何实现路由?

    在微前端架构中,当路由发生变化时,容器应用首先会拦截路由的变化,根据路由匹配微前端应
    用,当匹配到微应用以后,再启动微应用路由,匹配具体的页面组件。

  3. 微应用与微应用之间如何实现状态共享?

    在微应用中可以通过发布订阅模式实现状态共享,比如使用 RxJS。

  4. 微应用与微应用之间如何实现框架和库的共享?

    通过 import-map 和 webpack 中的 externals 属性。

微前端落地方案

  1. 自组织模式:通过约定进行互调,但会遇到处理第三方依赖等问题
  2. 基座模式:通过搭建基座、配置中心来管理子应用。如基于Single Spa的乾坤方案,也有基于本身团队业务量身定制的方案。
  3. 去中心模式:脱离基座模式,每个应用之间都可以彼此分享资源,如基于Webpack 5 Module Federation实现的EMP微前端方案,可以实现多个应用彼此共享资源。

其中,目前值得关注的是去中心模式中的EMP微前端方案,既可以实现跨技术栈调用,又可以在相同技术栈的应用间深度定制共享资源。

微前端的建设

微前端不是框架、不是工具/库,而是一套架构体系,它包括若干库、工具、中心化治理平台以及相关配套设施。

微前端包括 3 部分:

  • 微前端的基础设施。这是目前讨论得最多的,一个微应用如何通过一个组件基座加载进来、脚手架工具、怎么单独构建和部署、怎么联调。
  • 微前端配置中心:标准化的配置文件格式,支持灰度、回滚、红蓝、A/B 等发布策略。
  • 微前端的可观察性工具:对于任何分布式特点的架构,线上/线下治理都很重要。

微前端具体要解决好的 10 个问题:

  1. 微应用的注册、异步加载和生命周期管理
  2. 微应用之间、主从之间的消息机制
  3. 微应用之间的安全隔离措施
  4. 微应用的框架无关、版本无关
  5. 微应用之间、主从之间的公共依赖的库、业务逻辑(utils)以及版本怎么管理
  6. 微应用独立调试、和主应用联调的方式,快速定位报错(发射问题)
  7. 微应用的发布流程
  8. 微应用打包优化问题
  9. 微应用专有云场景的出包方案
  10. 渐进式升级:用微应用方案平滑重构老项目

微前端框架

  • Single-SPA:是一个用于前端微服务化的JavaScript前端解决方案,实现了路由劫持和应用加载,但是没有处理样式隔离,js执行隔离。https://github.com/single-spa/single-spa
  • qiankun:基于Single-SPA提供了更多开箱即用的API(single-spa + sandbox + import-html-entry),做到了与技术栈无关,并且接入简单,靠的是协议接入(子应用必须导出bootstrap、mount、unmount方法)。 https://github.com/umijs/qiankun
  • 腾讯的无界:https://github.com/Tencent/wujie
  • 腾讯的hel-micro:https://github.com/tnfe/hel
  • 美团的Bifrost:Bifrost微前端框架
  • 字节的Garfish:https://github.com/modern-js-dev/garfish
  • 阿里的icestark:https://github.com/ice-lab/icestark
  • 京东的MicroApp:https://zeroing.jd.com/micro-app/
  • EMP:https://github.com/efoxTeam/emp
  • 阿里云:https://github.com/aliyun/alibabacloud-alfa

实现微前端的几种方式:

  • 基于single-spa的qiankun:single-spa的实现原理是首先在基座应用中注册所有App的路由,single-spa保存各子应用的路由映射关系,充当微前端控制器Controler,URL响应时,匹配子应用路由并加载渲染子应用。相比于single-spa,qiankun他解决了JS沙盒环境,不需要我们自己去进行处理。在single-spa的开发过程中,我们需要自己手动的去写调用子应用JS的方法(如上面的 createScript方法),而qiankun不需要,乾坤只需要你传入响应的apps的配置即可,会帮助我们去加载。
  • 基于WebComponent的micro-app:micro-app并没有沿袭single-spa的思路,而是借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app不需要像single-spa和qiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。
  • 基于webpack5提供的Module Federation的hel-micro框架:Module Federation是Webpack5提出的概念,module federation用来解决多个应用之间代码共享的问题,让我们更加优雅的实现跨应用的代码共享。
  • 基于iframe的wujie

iframe方案

qiankun技术圆桌中有一篇关于微前端Why Not Iframe的思考,主要有以下几点:

  • iframe 提供了浏览器原生的硬隔离方案,不论是样式隔离、 js 隔离这类问题统统都能被完美解决。
  • url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  • UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中…
  • 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  • 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

因为这些原因,最终大家都舍弃了 iframe 方案。

Web Component

MDN Web Components由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。

  • Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates(HTML模板):