微前端知识梳理

37,267 阅读37分钟

我会按照以下几个方向对微前端进行知识梳理:

什么是微前端,微前端的方案有哪些,微前端方案的对比,微前端的适用场景,微前端的实现思路,微前端的底层源码分析,微前端技术的延伸--Vue如何使用React项目的组件和web componments,以及微前端的一些开源项目。

1. 什么是微前端

微前端是一种将前端应用程序拆分成更小,更独立的部分,以便于团队协作和维护的架构模式,可以理解为项目更加细粒度。

微前端允许不同的团队独立开发和部署各自的功能模块,同时保持整个应用程序的一致性和协调性,可以理解为一个项目分为了多个模块,多个模块可以自己开发和部署,符合我们高内聚,低耦合的期望。

微前端的核心思想是将前端应用程序视为一个由多个独立的微应用组成的整体,每个微应用都可以独立开发,测试,部署和运行。

微前端可以帮助团队更好的管理复杂的前端应用程序,提高开发效率和代码质

2. 场景引入

我列出一些场景出来,大家可以思考下如何实现以及对我们的增益。

  • Vue,React,jQuery这三个技术栈的项目,领导让你把这三个项目合为一个。
  • 你的Vue,React项目不用npm install,不用下载node_modules,如何运行。
  • 你的项目可以调用其他项目的组件,不用copy代码,就是直接调用。
  • 多个项目之间可以进行状态交流(这个延伸下没准可以增加个单点登录的实现方案)。
  • UI组件库的开发,每个组件的开发都不用考虑会影响到其他组件,而且可以实现可插拔以及增量构建。

其实还有很多好玩的场景,我就不一一列举了,上述这些都可以用微前端来实现。

3. 方案梳理

微前端的实现方案有很多,我做一个比较全面的梳理,目前各类前端方案层出不穷,找到最适合自己团队业务的方案极为重要。

Single-SPA

  • 用于构建微前端应用的JavaScript框架,它可以将多个独立的应用程序组合成一个整体应用程序。

  • 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。

  • 在同一页面上使用多个前端框架,而不用刷新页面。

  • 不限技术栈。

  • 支持独立部署每一个单页面应用。

  • 新功能使用新框架,旧的单页应用不用重写可以共存。

  • 有效改善初始加载时间,延迟加载代码。

  • 文档地址:zh-hans.single-spa.js.org/docs/gettin…

qiankun

  • 基于Single-SPA的微前端框架,它提供了更加完整的微前端解决方案,包括应用程序的注册,加载,通信等功能。

  • 基于 single-spa 封装,提供了更加开箱即用的 API,

  • 不限技术栈。

  • HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。

  • 样式隔离,确保微应用之间样式互相不干扰。

  • JS 沙箱,确保微应用之间全局变量/事件不冲突。

  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

  • umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。

  • 文档地址:qiankun.umijs.org/zh/cookbook

Piral

  • 用于构建微前端应用的框架,支持多种前端框架和库,同时提供了一些额外的功能,如插件系统和扩展性。

  • 渐进迁移。

  • 共享库。

  • 共享现有布局和程序框架。

  • 文档地址:github.com/smapiot/pir…

Bit

  • 用于构建和共享组件的平台,可以用于构建微前端应用,Bit提供了一些核心的AP和工具,例如组件注册,版本管理,依赖管理,测试和构建等,可以快速构建和部署微前端应用,这个建议和Cloud Alfa搭配使用。I

  • 具有传统单体式前端的安全性和健壮性。

  • 介接入方式简单、可伸缩性强。

  • 通过 简单的解耦代码库、自治团队、小型定义良好的 API、独立的发布管道 和 持续增量升级,增强工作流程。

  • 文档地址:bit.dev/docs/quick-…

Module Federation

  • 用于构建微前端应用的框架,基于webpack,提供了一些高级功能,例如动态加载和模块化,远程动态容器,可以实现项目间的组件共享,依赖共享,函数共享,状态共享,其核心思想是代码统一。

  • 是webpack给出的微前端方案。

  • 使 JavaScript 应用可以动态运行另一个 JavaScript 应用中的代码,同时可以共享依赖。

  • 依赖自动管理,可以共享 Host 中的依赖,版本不满足要求时自动 fallback 到 Remote 中依赖。

  • 共享模块粒度自由掌控,小到一个单独组件,大到一个完整应用。既实现了组件级别的复用,又实现了微服务的基本功能。

  • 共享模块非常灵活,模块中所有组件都可以通过异步加载调用。

  • 我做了一个Module Federation的知识梳理,比官方文档好理解一些:juejin.cn/post/719804…

  • 文档地址:webpack.js.org/concepts/mo…

PuzzleJS

  • 用于构建微前端应用的框架,可以将一个大型的前端应用拆分成多个小型的子应用,并实现子应用之间的通信和协作。

  • SEO 友好,在服务端进行准备和渲染。

  • 当片段所需的 api 出现故障时,PuzzleJs 可保证其他页面片段仍正常工作。

  • 文档地址:github.com/puzzle-js/p…

wujie

  • “无界”是微前端框架的一种实现,该框架提供了一种简单的方式来构建微前端应用,将所有的应用程序模块组合在一起,形成一个完整的 Web 应用程序。它基于 Web Components 技术,支持跨框架、跨站点、跨语言的工程化协作,并提供了一些工具和组件,使得微前端应用风格更加易于管理、协调和开发。

  • 开发人员可以更容易地扩展一个应用程序,无需担心上下游模块之间的兼容性问题。

  • 不需要理会多个模块之间的代码的复杂关系。

  • 文档地址:wujie-micro.github.io/doc/guide/

Micro-app

  • 基于微前端架构的前端应用程序,采用了Single-SPA框架作为基础,同时也进行了一些定制化的开发,目前Micro-app已经在京东的多个业务场景中得到了应用美丽如京东商城,京东金融,京东物流等。

  • 使用简单,接入微前端成本低。

  • 零依赖。

  • 兼容所有框架(不需要提供脚手架工具)。

  • 提供了JS沙箱、样式隔离、元素隔离、预加载、资源地址补全、插件系统、数据通信等一系列完善的功能。

  • 文档地址:zeroing.jd.com/docs.html#/

Luigi

  • 基于微前端架构的前端应用程序,采用了Single-SPA框架作为基础,同时也进行了一些定制化的开发,Luigi的特点是易于使用和扩展,它提供了一些高级功能,例如动态加载,按需加载,异步加载等,同时也提供了一些插件和扩展机制,可以让开发人员可以根据自己的需求进行定制化开发。Luigi在Zakabdo,SAP,ThoughtWorks中已经得到了应用。

  • 允许 Web 应用程序与应用程序包含的微前端进行通信。

  • 可以配置路由、导航、授权和 UX 元素等设置。

  • 文档地址:github.com/SAP/luigi

Mosaic

  • 和上面的Luigi,Micro-app其实大差不差。

  • 使用了片段(Fragments)的机制,这些片段由单独的服务程序提供服务,并根据模板定义在运行时组合在一起。

  • 由一堆软件包组成,这些软件包处理不同的问题,例如路由、布局、模板存储、甚至展示 UI。

  • 文档地址:www.mosaic9.org/

做个总结,上面我梳理出来了九种微前端架构的方案,我们可以根据自己的需求来选择一个合适的方案,我们可以不熟练地使用它,但是我们应该去知道它以及它的特性,有利于我们对需求的方案选择。

毕竟,很多时候,选择比努力更重要。

4. 方案对比

我这边准备列出两个框架来对比,分别是qiankun和Module Federation,qiankun算是我们最常用也是最周知的微前端框架了,而Module Federation则是webpack5的新特性,功能十分强大,我也很看好Module Federation的发展。

4.1 核心思想

qiankun的核心思想是: 路由统一。

可以理解为,qiankun可以做到同一项目,子应用是Vue,React等不同技术栈编写而成,子应用集成到了一个主应用中,主项目通过路由统一来管理这些子应用。

这意味着,主项目负责处理所有的路由请求,并将这些请求转发给了相应的子应用,这种方式使得多个子应用之间的理由管理更加统一和协调,从而提升了整个应用的可维护性和可扩展性。

Module Federation的核心思想是:代码统一。

Module Federation的核心思想是将多个独立的Webpack构建之间共享模块,从而实现代码统一。

这意味着,多个独立的应用可以共享一个代码库,从而避免了代码的重复和冗余,就像多个应用共享一个代码库中的组件,依赖,甚至函数,我们的这些应用甚至可以做到不下载依赖,使用UI组件,不需要公共函数,全都使用其他应用的,来进行开发。

4.2 技术实现

qiankun使用了浏览器端的路由和消息通信机制,通过web components实现应用之间的隔离和通信。

Module Federation是在webpack层面上实现的,通过共享模块和动态加载实现模块之间的通信和复用。

4.3 集成方案

qiankun以单页应用为核心,可以将多个子应用作为独立的SPA运行。

SPA:单页面应用程序,这是一种Web应用程序的架构模式,其特点是在一个页面中加载所有的HTML,Css,JavaScript资源,通过JavaScript动态的更新页面内容,实现无需刷新的页面效果。

可以理解为,在qiankun的架构中,主应用是一个单页面应用,负责管理路由和状态,而子应用也是单页面应用,负责具体的业务逻辑,每个子应用都可以独立运行,也可以通过qiankun框架集成到主应用中,实现了多个子应用在同一个页面中运行的效果。

这种方式实现了多个独立的子应用间的隔离和解耦,同时也可以共享主应用的路由和状态管理。

Module Federation以多个模块作为核心,可以将多个子应用作为多个模块运行。

可以理解为在Module Federation的架构中,每个子应用都可以抛出自身的状态,路由,组件,函数,依赖等供其他子应用获取,我们也可以做到多个中心模块甚至直接去中心化,例如A,B,C三个项目,A使用了B共享的组件,B使用C共享的组件,C使用A共享的组件,我们也可以以A和B为核心,C使用的A与B的共享。

这个和next.js的模块化方案很相似,是很不错的模块化方案。

4.4 应用场景

qiankun适用于多个独立的子应用需要集成到一个主应用的场景。

例如电商平台的商品详情页,购物车页,订单页等,在这种情况下,每个子应用都是独立的单页面应用,可以通过qiankun框架集成到主应用中,就是完整的电商网页了。

Module Federation适用于多个独立的模块需要共享和复用的场景。

例如企业级应用的公共组建,工具库,业务模块等。

4.5 资源加载

qiankun在加载子应用时,需要加载整个子应用的资源。

这些资源包括HTML,Css,JavaScript等文件,因此在切换子应用的时候可能会出现加载时间过长的问题。

Module Federation子加载子应用,只需要加载子应用的入口文件和需要共享的模块,而不需要加载整个子应用的资源。

Module Federation可以自动共享模块,因此加载子应用时不会出现加载时间过长的问题。

4.6 资源冗余

qiankun加载子应用时,如果多个子应用中都引入了同一个库,导致重复加载,会出现资源冗余的问题。

**Module Federation通过自动共享模块来避免了资源冗余,不会重复加载同一个库或者依赖。
**

4.7 资源更新

qiankun在切换子应用时,需要重新加载整个子应用的资源。

Module Federation可以通过自动共享模块来实现资源的实时更新。

4.8 内存占用

qiankun在切换子应用时,需要卸载当前子应用的所有组件和状态,然后加载新的子应用,会导致内存占用过高,影响性能。

**Module Federation可以通过自动共享模块来减少内存占用。
**

4.9 内存管理

qiankun需要手动管理子应用之间的内存,包括组件和状态等。

**Module Federation通过自动共享模块来减少内存管理的复杂度。
**

4.10 内存泄露

qiankun在切换子应用时,如果出现了未能正确卸载当前子应用的组件和状态等场景,则会出现内存泄露的问题。

Module Federation通过自动共享模块来避免内存泄露。

4.11 通信方式

qiankun需要手动配置子应用间的通信方式。

例如使用CustomEvent,postMessage等,这让qiankun的通信效率变低以及通信复杂度提高。

Module Federation通过自动共享模块来实现通信。

4.12 兼容性

qiankun支持IE11及以上版本的浏览器,但不支持微信小程序和支付宝小程序等非浏览器环境。

**Module Federation需要webpack5及以上版本,不支持webpack4及以下版本,也不支持IE11及以下版本的浏览器。
**

4.13 样式隔离机制

qiankun使用CSS Modules和Scoped CSS等技术来实现样式隔离,但需要手动配置。

这种方式可能出现样式冲突和样式性能的问题,都需要手动解决。

Module Federation通过自动共享模块来实现样式隔离。

4.14 JS隔离机制

qiankun使用沙箱技术来实现JS隔离,每个子应用都运行在独立的沙箱中,从而避免了JS代码之间的冲突。

沙箱技术可能会影响JS性能。

Module Federation通过自动共享模块来实现JS隔离。

可能会出现代码冲突,需要开发者手动解决。

4.15 生态

qiankun是一个相对独立的项目,生态相对较小,但是可以与其他前端框架和工具集成使用。

Module Federation的生态相对完善,可以与其他webpack插件和工具集成使用,例如webpack-dev-server,webpack-merge等。

5. 实现方案

对比了qiankun和Module Federation之后,我们来看一下这两个微前端框架的实现方案。

5.1 qiankun

  1. 应用隔离:qiankun采用了基于Web Components的应用拆分方案,将整个应用拆分成了多个独立的Web组件,可以独立开发,独立部署,独立运行。每个子应用都运行在独立的沙箱环境中,避免了应用之间的命名冲突和样式污染。

  2. 应用加载:qiankun采用了Single-SPA的应用加载机制,可以动态加载和卸载子应用,实现了应用的动态化管理。在加载子应用时,qiankun会先加载子应用的manifest.json文件,获取子应用的基本信息和依赖关系,然后再加载子应用的入口文件。

  3. 通信机制:qiankun采用了基于CustomEvent的通信机制,实现了子应用之间的通信和数据共享。在子应用中,可以通过window.dispatchEvent()方法触发CustomEvent事件,然后在父应用中通过window.addEventListener()方法监听CustomEvent事件,实现子应用和父应用之间的通信。

  4. 资源加载:qiankun采用了基于Fetch的资源加载机制,可以实现子应用的异步加载和缓存管理。在加载子应用时,qiankun会先检查本地缓存,如果缓存中已经存在该子应用,则直接从缓存中加载,否则再通过Fetch API从服务器上加载。

  5. 沙箱环境:qiankun采用了基于Proxy的沙箱环境,可以实现子应用的隔离和安全性保障。在加载子应用时,qiankun会为每个子应用创建一个独立的沙箱环境,通过Proxy对象对子应用的全局变量和方法进行代理,避免了子应用之间的相互影响。

  6. 打包优化:qiankun采用了基于Webpack的打包优化,可以实现子应用的按需加载和代码分割,提高了应用的性能和用户体验。在打包子应用时,qiankun会将子应用的代码分割成多个小块,然后在加载子应用时,只加载当前需要的代码块,避免了不必要的资源浪费。

  7. 模块化管理:qiankun采用了基于ES6 Module的模块化管理,可以实现子应用的模块化开发和管理,提高了代码的可维护性和可复用性。在子应用中,可以通过export关键字将模块暴露出去,然后在父应用中通过import关键字引入模块,实现子应用和父应用之间的模块化管理。

5.2 Module Federation

  1. 远程模块加载:Module Federation采用了远程模块加载的方式,可以将模块从一个应用加载到另一个应用中。在加载远程模块时,Module Federation会先从远程应用中获取模块的描述信息,然后再通过网络请求获取模块的代码。

  2. 模块描述信息:Module Federation中的模块描述信息包括了模块的名称、版本、入口文件等信息。在加载远程模块时,Module Federation会先从远程应用中获取模块的描述信息,然后再根据描述信息加载模块的代码。

  3. 共享模块:Module Federation可以实现不同应用之间的模块共享,可以将一个应用中的模块共享给其他应用使用。在共享模块时,Module Federation会将模块的代码打包成一个独立的库,然后在其他应用中通过远程模块加载的方式使用该库。

  4. 动态加载:Module Federation可以实现动态加载模块,可以根据用户的需求动态加载不同的模块。在动态加载模块时,Module Federation会先从远程应用中获取模块的描述信息,然后再根据描述信息动态加载模块的代码。

  5. Webpack插件:Module Federation是基于Webpack实现的,它提供了一系列的Webpack插件,可以实现模块的共享、动态加载和远程模块加载等功能。在使用Module Federation时,需要在Webpack配置文件中添加相应的插件。

  6. 模块映射表:Module Federation会生成一个模块映射表,用于记录不同应用之间的模块依赖关系。在加载远程模块时,Module Federation会根据模块映射表查找模块的依赖关系,然后再加载相应的模块。

  7. 模块缓存:Module Federation会缓存已经加载的模块,避免重复加载和网络请求。在加载远程模块时,Module Federation会先检查本地缓存,如果缓存中已经存在该模块,则直接从缓存中加载,否则再通过网络请求获取模块的代码。

5.3 方案总结

上面讲述了两个微前端库框架由哪些功能模块组成,以及功能模块的实现方案,我们需要知道该框架有这个功能,至于具体的底层代码实现,我后面会梳理。

6. Web Componments

上面提了这么多Web Componments,那这个东西到底是什么呢,我可以不熟练,但是我们需要知道它。

Web Components是一种新的Web开发技术,它可以将Web应用拆分成多个独立的组件,每个组件都可以独立开发、测试和部署。

它的出现原因是因为早期组件生态很乱,有各种各样的框架和库,都有各自的规范,导致一个团队切换框架很困难 。

为了解决这种分化的形式,让 Web 组件模型统一化,所以才有了Web Components规范的出现。目标是提供一种权威的、浏览器能理解的方式来创建组件。

但是有一定的性能问题:

vue是通过 模板->虚拟DOM->真实DOM 来实现自定义组件的。

web components是在浏览器中通过 自定义组件->Shadow Dom 实现自定义组件的。

浏览器中组件越多,vue的js计算时间比web components越少,性能越好。

Web Components由三个主要技术组成:Custom Elements、Shadow DOM和HTML Templates。

  1. Custom Elements:Custom Elements是一种自定义元素的技术,可以创建自定义的HTML元素。通过Custom Elements,开发者可以创建自己的HTML标签,并定义标签的行为和样式。Custom Elements可以实现组件的封装和复用,提高了代码的可维护性和可复用性。
  2. Shadow DOM:Shadow DOM是一种DOM的封装技术,可以将DOM树封装在一个独立的作用域中。通过Shadow DOM,开发者可以将组件的样式和行为封装在一个独立的作用域中,避免了组件之间的样式污染和命名冲突。
  3. HTML Templates:HTML Templates是一种HTML模板的技术,可以创建可复用的HTML模板。通过HTML Templates,开发者可以将组件的HTML结构封装在一个模板中,然后在需要使用组件时,动态地插入模板中的HTML结构。

Web Components的优点包括:

  1. 组件化开发:Web Components可以将Web应用拆分成多个独立的组件,每个组件都可以独立开发、测试和部署。组件化开发可以提高代码的可维护性和可复用性。
  2. 样式隔离:Web Components可以通过Shadow DOM实现样式的隔离,避免了组件之间的样式污染和命名冲突。
  3. HTML模板:Web Components可以通过HTML Templates创建可复用的HTML模板,提高了组件的可复用性和可维护性。
  4. 自定义元素:Web Components可以通过Custom Elements创建自定义的HTML元素,提高了代码的可读性和可维护性。
  5. 跨平台兼容:Web Components可以在不同的浏览器和平台上运行,具有很好的兼容性和可移植性。

Web Components的缺点包括:

  1. 学习成本:Web Components需要掌握多个技术,包括Custom Elements、Shadow DOM和HTML Templates,学习成本较高。

  2. 兼容性:Web Components在一些老旧的浏览器上可能存在兼容性问题,需要进行兼容性处理。

  3. 性能问题:Web Components可能存在性能问题,需要进行优化和测试。

7. 适用场景

上面我引入了一些场景,供大家思考,但是微前端的适用场景远不止我引入的那些,具体可以分为下面十类。

  • 大型单页应用(SPA)的拆分:将一个庞大的单页应用拆分成多个小型应用,每个应用都可以独立开发、测试和部署。

  • 多团队协作开发:不同团队可以独立开发和维护自己的微前端应用,通过统一的框架和协议进行集成和交互。

  • 前端模块化:将前端应用拆分成多个独立的模块,每个模块都可以独立开发、测试和部署,提高代码复用性和可维护性。

  • 多语言支持:不同语言的前端应用可以通过微前端框架进行集成和交互,实现多语言支持。

  • 前后端分离:将前端应用和后端应用分离,通过微前端框架进行集成和交互,实现前后端分离。

  • 多端适配:将前端应用拆分成多个独立的模块,每个模块都可以适配不同的终端,如PC端、移动端、小程序等。

  • 服务化架构:将前端应用拆分成多个独立的服务,每个服务都可以独立部署和扩展,实现服务化架构。

  • 动态加载:通过微前端框架实现动态加载,根据用户需求动态加载不同的前端应用和模块,提高用户体验和性能。

  • 统一管理:通过微前端框架实现统一管理,可以集中管理前端应用和模块的版本、依赖和配置等信息,提高管理效率和可控性。

  • 代码复用:通过微前端框架实现代码复用,不同的前端应用和模块可以共享同一份代码,提高代码复用性和可维护性。

8. 微前端和iframe的比较

首先,我们再仔细了解一下iframe。

iframe是HTML中的一个标签,用于在一个网页中嵌入另一个网页或文档。

它可以将一个网页作为一个独立的窗口嵌入到另一个网页中,使得用户可以在同一个页面中同时浏览多个网页或文档。

使用iframe可以实现以下功能:

  1. 在一个网页中嵌入另一个网页或文档,实现多个网页的同时浏览。

  2. 在一个网页中嵌入广告或其他内容,实现网页内容的扩展。

  3. 在一个网页中嵌入视频或音频,实现网页的多媒体功能。

  4. 在一个网页中嵌入地图或其他应用程序,实现网页的交互性和实用性。

使用iframe需要注意以下几点:

  1. iframe的src属性必须指向一个有效的网页或文档。

  2. iframe的宽度和高度必须指定,否则可能会导致页面布局混乱。

  3. iframe的内容可能会被浏览器的安全策略限制,例如跨域访问等。

  4. iframe的加载速度可能会影响整个页面的性能,因此需要谨慎使用。

总之,iframe是一种非常有用的HTML标签,可以实现网页内容的扩展和多媒体功能,但需要注意安全性和性能问题。

iframe确实存在很多缺点,但是在选择一个方案的时候还是要具体场景具体分析,它可能在当下很流行,但它不一定在任何时候都是最优解:iframe的这些缺点对我来说是否能够接受?它的缺点是否有其它方法可以弥补?使用它到底是利大于弊还是弊大于利?我们需要在优缺点之间找到一个平衡。

所以:

  • 如果页面本身比较简单,是一个没有弹窗、浮层、高度也是固定的纯信息展示页的话,用iframe一般没什么问题;
  • 如果页面是包含弹窗、信息提示、或者高度不是固定的话,需要看iframe是否占据了全部的内容区域,如果是像下图这种经典的导航+菜单+内容结构、并且整个内容区域都是iframe,那么可以放心大胆地尝试iframe,否则,需要慎重考虑方案选型。

对比总结:

如果是一些比较简单的业务场景,我们是可以直接使用ifame的,没有必要为了简单的业务场景而套上微前端的架构。

但是如果业务场景复杂或者iframe不满足,我们就可以考虑微前端的架构。

我们不能因为iframe在业界名声不好而直接去否定它,我们还是需要将它定为一种方案,列入我们的参考当中。

9. 项目架构

如果我们使用的微前端,那么我们整个项目架构我们需要有清晰的思路。

9.1 qiankun

使用qiankun搭建微服务,整个项目的架构梳理如下:

  1. 主应用:主应用是整个项目的入口,负责加载和管理所有子应用。主应用使用qiankun提供的API来注册和启动子应用,同时也负责处理子应用之间的通信和状态共享。

  2. 子应用:子应用是独立的应用程序,可以使用不同的技术栈和框架来构建。子应用需要使用qiankun提供的API来注册和暴露自己的生命周期钩子,以便主应用可以管理它们的生命周期。

  3. 路由管理:qiankun提供了一种基于history模式的路由管理方案,可以让主应用和子应用之间共享路由信息。主应用可以使用qiankun提供的API来注册和管理路由,同时也可以将路由信息传递给子应用。

  4. 状态管理:qiankun提供了一种基于props和emit的状态管理方案,可以让主应用和子应用之间共享状态信息。主应用可以使用qiankun提供的API来注册和管理状态,同时也可以将状态信息传递给子应用。

  5. 共享组件库:为了提高开发效率和代码复用性,可以将一些通用的组件封装成独立的库,并在主应用和子应用中共享使用。qiankun提供了一种基于Webpack的组件库共享方案,可以让主应用和子应用之间共享组件库。

  6. 部署方案:qiankun提供了一种基于微服务架构的部署方案,可以将主应用和子应用分别部署到不同的服务器上,从而实现高可用性和负载均衡。同时,qiankun还提供了一种基于Docker的容器化部署方案,可以更方便地管理和部署应用程序。

9.2 Module Federation

  1. 主应用:主应用是整个项目的入口,负责加载和管理所有子应用。主应用使用Webpack 5提供的module federation功能来注册和启动子应用,同时也负责处理子应用之间的通信和状态共享。

  2. 子应用:子应用是独立的应用程序,可以使用不同的技术栈和框架来构建。子应用需要使用Webpack 5提供的module federation功能来注册和暴露自己的模块,以便主应用可以加载和使用它们。

  3. 路由管理:使用Webpack 5提供的module federation功能可以让主应用和子应用之间共享路由信息。主应用可以使用Webpack 5提供的API来注册和管理路由,同时也可以将路由信息传递给子应用。

  4. 状态管理:使用Webpack 5提供的module federation功能可以让主应用和子应用之间共享状态信息。主应用可以使用Webpack 5提供的API来注册和管理状态,同时也可以将状态信息传递给子应用。

  5. 共享组件库:为了提高开发效率和代码复用性,可以将一些通用的组件封装成独立的库,并在主应用和子应用中共享使用。使用Webpack 5提供的module federation功能可以让主应用和子应用之间共享组件库。

  6. 部署方案:使用Webpack 5提供的module federation功能可以将主应用和子应用分别部署到不同的服务器上,从而实现高可用性和负载均衡。同时,使用Docker等容器化技术可以更方便地管理和部署应用程序。

10. 源码讲解

讲了这么多,我们可以进行到源码层面了,对于我们前端开发来说,我们要做的不仅仅是会用,还需要知道它是如何实现的。

因为函数式一环套一环的,我只能用chatGPT给大家讲解些一些功能模块的大方向实现,而不是一个功能的代码都列举出来,详细解释。

这块给人的收益不算多,可自行选择阅读。

10.1 qiankun

源码地址:github.com/umijs/qiank…

以下是qiankun中一些优秀功能的源码及讲解:

10.1.1 微前端应用的注册和卸载

在qiankun中,我们可以通过registerMicroApps方法来注册微前端应用,通过unregisterMicroApps方法来卸载微前端应用。这两个方法的源码如下:

注册微前端应用
function registerMicroApps(
  apps: Array<RegistrableApp<{}>>,
  lifeCycles?: LifeCycles<any>,
) {
  apps.forEach((app) => {
    const { name, entry, container, activeRule, props, ...rest } = app;
    if (!name || !entry || !container) {
      throw new Error('[qiankun] name、entry、container are required for each micro application to register.');
    }

    if (microApps.some((app) => app.name === name)) {
      return console.warn(`[qiankun] Micro app ${name} already registered.`);
    }

    microApps.push({
      name,
      entry,
      container,
      activeRule,
      props,
      ...rest,
    });

    lifeCycles && (lifecycles[name] = lifeCycles);
  });

  reroute();
}

// 卸载微前端应用
function unregisterMicroApps() {
  setUnmountByApp();
  microApps = [];
  currentApp = null;
  nextTick(() => {
    render({ appContent: '', loading: false });
  });
}

我们可以看到,registerMicroApps方法会将传入的微前端应用信息存储到microApps数组中,并将生命周期函数存储到lifecycles对象中。而unregisterMicroApps方法则会清空microApps数组和currentApp变量,并调用render方法将应用内容清空。

10.1.2 微前端应用的路由匹配

在qiankun中,我们可以通过activeRule属性来指定微前端应用的路由匹配规则。当前路由与activeRule匹配时,qiankun会将该微前端应用渲染到页面中。activeRule的源码如下:

function matchActiveRule(location: Location, activeRule: string | ((location: Location) => boolean)) {
  if (typeof activeRule === 'string') {
    return location.pathname.startsWith(activeRule);
  }

  return activeRule(location);
}

我们可以看到,matchActiveRule方法会根据传入的locationactiveRule参数来判断当前路由是否匹配该微前端应用。如果activeRule是字符串类型,则判断location.pathname是否以该字符串开头;如果activeRule是函数类型,则调用该函数并传入location参数。

10.1.3 微前端应用的沙箱隔离

在qiankun中,我们可以通过single-spa的single-spa-sandbox库来实现微前端应用的沙箱隔离。qiankun对single-spa-sandbox库进行了封装,提供了createSandbox方法来创建沙箱实例。createSandbox的源码如下:

function createSandbox(appName: string) {
  const { strictStyleIsolation, experimentalStyleIsolation, ...sandboxProps } = getMicroAppConfig(appName);

  if (strictStyleIsolation || experimentalStyleIsolation) {
    return new SnapshotSandbox(sandboxProps);
  }

  return new ProxySandbox(sandboxProps);
}

我们可以看到,createSandbox方法会根据微前端应用的配置信息来判断是否需要开启严格的样式隔离。如果需要,则创建SnapshotSandbox实例;否则创建ProxySandbox实例。

10.1.4 微前端应用的生命周期管理

在qiankun中,我们可以通过addGlobalUncaughtErrorHandler方法来添加全局错误处理函数,通过addErrorHandler方法来添加微前端应用的错误处理函数,通过addAppStatusChangeHandler方法来添加微前端应用状态变化的处理函数。这些方法的源码如下:

// 添加全局错误处理函数
function addGlobalUncaughtErrorHandler(handler: (event: Event) => void) {
  globalUncaughtErrorHandler = handler;
}

// 添加微前端应用的错误处理函数
function addErrorHandler(handler: (event: Event) => void) {
  errorHandler = handler;
}

// 添加微前端应用状态变化的处理函数
function addAppStatusChangeHandler(handler: (status: string, prev: string) => void) {
  appStatusChangeHandler = handler;
}

我们可以看到,这些方法都是将传入的处理函数存储到相应的变量中,以便在需要时调用。其中,globalUncaughtErrorHandlererrorHandler用于处理全局错误和微前端应用的错误,而appStatusChangeHandler用于处理微前端应用状态的变化。

10.2 Module Federation

源码地址:github.com/webpack/web…

以下是module federation中一些优秀功能的源码及讲解:

10.2.1 远程模块的加载

在module federation中,我们可以通过使用remoteEntry来加载远程模块。remoteEntry是一个Jsonp文件,包含了远程模块的信息和代码。我们可以使用import()函数来动态加载远程模块,如下所示:

import('http://localhost:3001/remoteEntry.js').then((remote) => {
  const module = remote.get('./module');
  // 使用远程模块
});

在上面的代码中,我们使用import()函数加载了http://localhost:3001/remoteEntry.js文件,并通过remote.get()方法获取了远程模块中的./module模块。

10.2.2 共享模块的使用

在module federation中,我们可以通过使用exposesremotes来共享模块。exposes用于将本地模块暴露给其他应用程序使用,而remotes用于引用其他应用程序暴露的模块。

下面是一个使用exposesremotes的示例:

// app1.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        './module': './src/module',
      },
    }),
  ],
};

// app2.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      filename: 'remoteEntry.js',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
    }),
  ],
};

在上面的代码中,我们在app1中使用exposes./src/module模块暴露出去,然后在app2中使用remotes引用了app1应用程序,并通过app1@http://localhost:3001/remoteEntry.js指定了app1应用程序的remoteEntry文件的URL。

10.2.3 共享模块的版本控制

在module federation中,我们可以通过使用shared选项来控制共享模块的版本。shared选项是一个对象,其中的键是模块名称,值是模块的版本号或版本范围。

下面是一个使用shared选项的示例:

// app1.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        './module': './src/module',
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
      },
    }),
  ],
};

// app2.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      filename: 'remoteEntry.js',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
      },
    }),
  ],
};

在上面的代码中,我们在app1app2中都使用了shared选项来控制共享模块的版本。在这个例子中,我们指定了reactreact-dom模块的版本范围为^17.0.0,表示只要是17.x.x版本都可以共享使用。同时,我们还将singleton选项设置为true,表示这些模块只会被加载一次。

11. Vue和React组件互用

这算是一个技术延伸,大家思考下,我们用不用技术栈的子项目来构建微前端时,组件如何通用呢,也就是标题所写的Vue和React组件如何互用。

这个听起来就是一个很有意思的技术点,让我们来好好研究一下。

我目前总结了三种方案,来实现组件互用。

分别是web componments,node中间件,使用第三方库(vue-reactivityreact-vue等)

注意:

虽然可以实现Vue和React组件的互用,但是在实际开发中,我们还是尽量避免这种情况,因为不同的框架有不同的设计理念和实现方式,组件互用可能导致代码复杂度增加,维护成本增加等问题。

11.1 web componments

将web componments封装成Vue和React都可以使用的组件,需要进行以下步骤:

11.1.1 创建web componments

使用原生的web conponments技术或者第三方库(例如 Polymer)创建组件,并且将组件封装成自定义元素。

例如,我们创建一个简单的web conponments,它可以显示一个Hello Feng的文本。

<!-- hello-feng.html -->
<template>
    <h1>Hello Feng</h1>
</template>

<script>
    class HelloFeng entends HTMLElement{
        super();
        const template = document.getElementById('hello-feng-template');
        const content = template.content.cloneNode(true);
        this.attachShadow({mode:'open'}).appendChild(content)
        customElement.defien('hello-feng',HelloFeng)
}

在类的构造函数中,我们使用attachShadow方法创建了一个Shadow DOM,然后我们将模板内容添加到了Shadow DOM中。

最后我们使用customElements.define方法将组件注册为自定义元素。

super:
1. super继承:在constructor中调用super,子类没有定义constructor方法,super方法会被默认添加。

子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错 。
(因为ES5 的继承的实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this)

2.super方法:作为函数时,super()只能用在子类的构造函数之中。

3.super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。

11.1.2 将web componments封装为Vue组件

使用Vue的自定义元素插件(例如vue-custom-element)将web componments封装为Vue组件。

例如,我们可以将上面的hello-feng封装为Vue组件。

<!-- hello-feng-vue.js -->
import Vue from 'vue';
import vueCustomElement from 'vue-custom-element'
import HelloFeng from './hello-feng.html'

Vue.use(vueCustomElement)

Vue.cusetomElement('hello-feng-vue',HelloFeng)

在上面的代码中,我们使用了vue-custom-element插件将web componments封装为了Vue组件。

使用Vue.customElement方法将组件注册为自定义元素。

自定义元素:

自定义元素是web componments中的一个重要的特性,自定义元素允许开发者自定义HTML元素,然后在页面中使用这些元素。

例如。

自定义元素和Vue中的组件十分类似,Vue组件也是一种可复用的组件,可以在页面使用自定义标签来调用。

11.1.3 将web componments封装为React组件

其实和Vue的流程差不多,使用的是React的自定义元素插件(例如 react-custom-elements)将web componments封装为React组件。

例如,我们可以将上面的web componments封装为React组件。

<!-- hello-feng-reatc.js -->
import React from 'react'
import ReactDOM from 'react-dom'
import {defineCustomElement} from 'react-custom-elemets'
import HelloFeng from './hello-feng.html'

class HelloFengReact extends React.Componment{
    render(){
        return ReactDOM.createProtal(
            <HelloFeng/>,
            this.shadowRoot
        )
    }
}
defineCustomElement('hello-feng-react',HelloFengReact)

在上面的代码中,我们使用了react-custom-elements插件将web componments封装为了React组件。

使用defineCustomElement方法将组件注册为自定义元素。

在组件的render方法中,我们使用ReactDOM.createProtal方法将组件渲染到Shadow DOM中。

11.2 node中间件

node中间件方法的思想是:把vue和react的组件都渲染成HTML字符串,然后在组件中使用。

具体中间件有:vue-server-rendererreact-dom/serverexpress-vuereact-express-views

11.3 第三方库

  • vue-reactivity: Vue官网提供的库,可以让Vue组件使用React组件。

  • react-vue: 第三方的库,可以在React组件中使用Vue组件。

  • vue-custom-element: 这也是Vue官方提供的库,可以将Vue组件转换成自定义元素,然后在React中使用。

  • react-custom-element: 第三方的库,可以讲React组件转换为自定义元素,然后在Vue中使用。

  • vue-in-react: 第三方的库,可以让Vue组件在React中使用。

  • react-in-vue: 第三方的库,可以让React组件在Vue中使用。

12. 经典案例

github.com/module-fede…

这个是Module Federation的案例,里面有很多demo,例如Vue项目的,React项目的,Angular项目的,还有webpack,vue-cli以及vite的,连Vue+React的demo也提供了。

github.com/umijs/qiank…

这个是qiankun的案例,里面三大主流框架包括Vue3的demo都提供了。

github.com/single-spa/…

这个是single-spa的案例,里面不仅有三大主流框架,svelte这种编译时框架也有demo,还有preact等非主流框架案例。

github.com/neuland/mic…

github.com/smapiot/pir…