欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

聊聊微前端的两种不同级别划分方法

最编程 2024-02-05 18:37:42
...

前言

我们的业务主要是做一些中后台的前端项目,有两个特点:生命周期长、代码量庞大,这带来了技术栈落后编译部署慢两个问题。虽然我们之前在编译缓存上做了些努力,但是治标不治本,在项目极大的情况下编译时间还是不够理想。因此,微前端应该是唯一的解决方案了。

于是我们做了一些微前端的技术调研,主要是乾坤single-spa这两个框架,一开始我们觉得基于single-spa的乾坤应该会比single-spa本身更符合我们的需求,但是经过深入探索后,发现情况远没有我们想象的那么简单。

两种微前端

在做了一些调研后,有个问题一直困扰着我:微前端到底是什么?

乾坤

在乾坤的角度,微前端就是“微应用加载器”,它主要解决的是:如何安全快速的把多个分散的项目集中起来的问题,这从乾坤自身提点便可看出:

所有这些特性都是服务于“微应用加载器”这个定位。

single-spa

在single-spa的角度,微前端就是“微模块加载器”,它主要解决的是:如何实现前端的“微服务化”,从而让应用、组件、逻辑都成为可共享的微服务,这从single-spa关于微前端的概述中可以看出:

在single-spa看来微前端有三种类型:微应用、微组件、微模块,实际上single-spa要求它们都以SystemJS的形式打包,换句话说它们本质上都是微模块

SystemJS是一个运行时加载模块的工具,是现阶段下(浏览器尚未正式支持importMap)原生ES Module的完全替代品,在此不做过多介绍,更多内容请看:zh-hans.single-spa.js.org/docs/recomm…

谁才是微前端?

要讨论这个问题,我们要先想清楚以下两点的区别:

  • 微应用加载器:“微”的粒度是应用,也就是HTML(或main.js),它只能做到应用级别的分享
  • 微模块加载器:“微”的粒度是模块,也就是JS模块,它能做到模块级别的分享

所以,它们的区别就是微服务的粒度,乾坤的所能服务的粒度是应用级别,而single-spa则是模块级别。

那么谁才是微前端呢?答案是:都是。因为它们都能将前端进行拆分,只是拆分的粒度不同罢了。但要说谁的微前端更极致,那肯定是single-spa。

但是要注意,不要觉得single-spa更极致就无脑选择它。最终选择什么,需要根据项目的未来规划以及我们真实的需求来决定,下面我们会继续分析。

总之先记住这句话:合适的才是最好的

服务粒度带来的影响

解决了“为其那段到底是什么”的疑问后,我们对乾坤和single-spa有了基本上的认识,那么我们到底该选择什么呢?

微前端能带来哪些影响

微前端之所以吸引人,是因为它带来了很多变革性的东西,为前端提供了非常大的想象空间:

  1. 微前端能做应用集成,可以把多个技术无关的应用集成在一起,从而统一UI、统一用户体验
  2. 微前端能做模块联邦,可以分发微模块,并在运行时动态加载微模块
  3. 微前端能做依赖共享,基于模块联邦,模块可以在运行时被充分分析,并根据需求合理的进行分享

第1点是我们普遍理解的微前端,而第2、3点还鲜为人知,社区的实践也比较少,所以我也只能举几个例子来发散下思维:

模块联邦

使用npm包的形式管理库有时候会比较头疼,因为每次库更新,使用者都要重新走一遍:更新库 -> 项目重新编译 -> 项目重新发布这样的流程,但是如果我们的库以微模块的形式发布,而使用者通过运行时加载模块的方式使用库,那么每次库更新,我们只需要刷新一下浏览器即可

依赖共享

之前的绝大部分微应用集成方式,不管是iframe,还是icestark,又或是乾坤,都没法很好的解决依赖共享的问题,它们唯一能做的就是用打包工具的externals功能进行vendor级别的共享,但是对于微应用内部的组件、模块是无法共享的。如果你的多个应用使用了共同的组件、模块,要么每个微应用都copy一份,要么以npm包的形式维护,都非常麻烦。

除此之外,更致命的是externals功能的羸弱。externals本质上就是让打包工具跳过vendor的编译,而是在运行时使用通过<script src="xxx/some-vendor.js" />这样的形式加载vendor,这里的问题就是一个vendor只能有一个版本。换句话说就是,如果你要共享依赖,那么只能用同一个版本;如果你不用同一个版本,就不能共享。

举个比较现实的例子:你有2个antd3的项目,2个antd4的项目,还有一个宿主项目,如果你使用传统的微前端框架,你将只有一下两种选择:

  1. 宿主项目写<script src="xxx/antd3.js"/>,2个antd3项目externals掉antd,2个antd4项目不做externals(即把antd4打包到输出的js中)
  2. 宿主项目写<script src="xxx/antd4.js"/>,2个antd3项目不做externals,2个antd4项目externals掉antd

这样的微前端不够极致,对吧?继续往下看吧。

服务粒度是微应用

服务粒度是微应用的微前端框架,通常只能做到应用集成,对于模块联邦和依赖共享是做不好的。但是你可能会问:乾坤也支持Webpack5模块联邦啊,这样不就可以做到了?

这样理解有问题的,乾坤并不支持Webpack5,乾坤其实根本看不到Webpack5,所以这里问题的本质是:乾坤和Webpack5模块联邦的关系是什么?

实际上,乾坤完全不会关心微应用使用了什么技术,不关心你到底是Webpack4还是Webpack5,因为它根本看不到微应用内部的细节。之所以有了Webpack5就可以做到微模块,纯粹是微应用自己的功劳,跟乾坤没有任何关系。

所以话应该这么说:服务粒度是微应用的微前端框架,在借助Webpack5模块联邦后,可以实现微模块的分享。而框架本身的服务粒度依旧是停留在微应用

其实,这样有利有弊,后面再讨论。

服务粒度是微模块

服务粒度是微模块的微前端框架,以上三点都能做到,这样我们的微前端项目的想象空间就很大了。

再谈微前端

通过上述关于服务粒度的阐述,微前端其实可以用这样一个式子来表示:微前端 = 微应用生命周期管理 + 模块联邦(可选)

其中模块联邦是可选的,如果实现了,服务粒度就是微模块;而如果没有实现,服务粒度就是微应用。

微应用生命周期管理

这个比较好理解,其实就是提供了微应用的解析、挂载、卸载、生命周期钩子等能力。针对不同微前端框架的特点,还会有CSS隔离、沙箱、prefetch等功能。

模块联邦

这里的模块联邦并非特指Webpack5的模块联邦,而是只要实现了模块的分发、共享,就可以称为“模块联邦”。实现模块联邦目前有两种手段:

  1. SystemJS
  2. Webpack5模块联邦

表面上看这两者都是在运行时动态加载模块,但内在区别很大:

  1. SystemJS动态加载的模块必须是SystemJS模块或者UMD模块;Webpack5模块联邦则没有这些限制。
  2. SystemJS的模块依赖关系是在运行时确定的,即通过importMap;而Webpack5模块联邦的依赖关系是在编译时确定的,即读取编译时生成的remoteEntry.js来分析依赖。

Webpack5模块联邦要比SystemJS易用的多,唯一存在的问题就是只支持Webpack5项目,而SystemJS对Webpack4/5是都支持的。

如何实现微前端

根据上面的式子,要实现一个微前端就很清晰了,可以选择的方案有:

序号 生命周期管理 模块联邦 描述
1 乾坤 应用粒度
2 乾坤 Webpack5模块联邦 模块粒度,仅支持在Webpack5项目中做模块联邦
3 single-spa 应用粒度
4 single-spa SystemJS 模块粒度,一般用在Webpack4项目
5 single-spa SystemJS + Webpack5模块联邦 模块粒度,使用SystemJS加载Webpack5模块。即Webpack4项目用SystemJS加载SystemJS模块,Webpack5项目使用SystemJS加载Webpack5模块。这样新旧项目都能做到模块联邦了

再谈乾坤和single-spa的区别

乾坤基于single-spa,加强了微应用集成能力,却抛弃了微模块的能力,为什么要这么做呢?我们要想清楚这两个框架出现的背景:

**乾坤:**阿里内部有大量年久失修的项目,业务侧急需工具去把他们快速、安全的集成到一起。在这个角度,乾坤根本没有做模块联邦的需求,它的需求仅仅是如何快速、安全的把项目集成起来。所以乾坤是想做一个微前端工具。

**single-spa:**学习后端的微服务,实现前端的微服务化,让应用、组件以及逻辑都成为可共享的微服务,实现真正意义上的微前端。所以single-spa是想做一个game-changer。

这里我还整理了一个图方便理解:

合适的才是最好的

微前端不是银弹,在做微前端改造之前先想清楚这些问题:

后记

回到最开始,我们最初做微前端改造的初衷是为了解决:技术栈落后编译部署慢这两个问题,通过上述链路,其实客观上我们应该选择乾坤。但是主观上,作为一个有追求的人,乾坤目前真的没法做到极致的微前端,所以我们尝试了single-spa,但又发现single-spa对项目的改造太多,对项目的可维护性带来了严峻挑战。

从微前端改造效果和项目的可维护性这两个角度考虑,当前这些方案好像都不够理想,它们要么改造效果不理想,要么可维护性差。作为一个追求极致的人,我选择了暂时放弃。

我觉得唯一完美的方案是:乾坤 + 老项目迁移到Webpack5 + 新项目使用Webpack5,因为只有这样才既能达成改造理想,又能保证可维护性。

注意

在这里,老项目迁移到Webpack5非常重要,我相信现在大家的项目都是Webpack4,在未来我们的微前端中大概率大部分都是Webpack4的老项目。而Webpack5的模块联邦只支持Webpack5项目,所以假如你有9个老项目和2个新项目,如果你不做老项目迁移,那么你的模块联邦也只能用在这2个新项目中,意义不大。

但是如果老项目就是没法迁移到Webpack5又该怎么办呢?此时唯一的方案就是用SystemJS,但是single-spa+SystemJS的使用又太麻烦,维护成本太高,害,太难了。这种情况,也许只能基于single-spa+SystemJS开发一个新的框架才行了。

推荐阅读