Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浅谈微前端的两种粒度 - 掘金 #21

Open
zepang opened this issue Jan 8, 2022 · 0 comments
Open

浅谈微前端的两种粒度 - 掘金 #21

zepang opened this issue Jan 8, 2022 · 0 comments

Comments

@zepang
Copy link
Owner

zepang commented Jan 8, 2022

前言

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

于是我们做了一些微前端的技术调研,主要是乾坤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 开发一个新的框架才行了。
https://juejin.cn/post/6871145516026527757

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant