引言

不知为何,笔者入行以来就经常接触微前端相关的项目,即便第一次接触的时候微前端还没有成为主流大项目的解决方案,而且当时笔者还是个萌新,所以微前端从笔者入行以来就成为了笔者魂牵梦绕的技术方案之一。因此笔者对于微前端有着许多胡思乱想的成果,时至今日,觉得时机成熟,笔者决定整理一篇文章,以飨读者。

如有错误,还请不吝斧正。

什么是微前端

微前端(Micro Frontends)这个概念是在 2016 年底在 Thoughtworks 提出,这个概念主要是将被广泛应用于服务端的 微服务 定义拓展至前端。

在此之前,前端应用就已经因为 SPA 等技术的出现,开始飞速发展,开始承担越来越多的职责,功能也越来越多样化和丰富,这种增长导致项目也越来越庞大以至于庞大到到一种程度之后被称之为 巨石应用

而这种 巨石应用 随着时间的推移很快的就会暴露很多问题。笔者最初接触的微前端项目的前身就是类似的巨石应用,十分的庞大,一个应用需要十几个人来开发维护,从全链路来讲,你的开发、打包、测试、发布都会因为巨大体量带来的负重感而接近奔溃,其中尤其是公共事务的处理变成了一种痛苦。举个开发期间的小例子,当时 Redux 里存储的公共数据十分的庞大,随便一个开发者想要修改里面的数据流都得检查半天会有什么副作用,为了防止有不可控的副作用,于是基本上只新增数据流不修改或删除数据流,这就导致巨石应用会变成巨屎项目。同样公共组件,公共接口等公共事务的更新都会让开发者开发的举步维艰。除此之外开发运行项目、打包项目也变得及其缓慢,测试、发布等操作也会因为过慢过长变得让人恶心。

穷则变,变则通。

微前端背后的思想认为:现代复杂的 Web 应用或者网站,往往是由很多相对独立的功能模块组合而成的,而对这些模块负责的也应该是互相独立的多个团队
在这种思想的指导下,许多巨石应用应声而解,成为了很多独立的微小的前端应用,这种处理方式带来了很多好处:

  1. 更小、更有凝聚力和可维护的代码库
  2. 更具可拓展性的团队
  3. 有更多的方式可以对应用进行升级、更新乃至重写

最终大家对于微前端广泛的定义是:

一种将单独可交付的前端应用组成一个更大的整体的架构风格

另外,在软件架构领域,从来没有银弹和免费的午餐,任何架构都是有代价的。一些微前端实现会导致依赖重复,还有一些从业务层面无法解耦的公共事务处理将会被弄得支零破碎。即便如此,笔者还是相信这些副作用和风险是可控的,并且相对于其副作用和风险,微前端带来的好处远超于此。

什么场景需要

Be water, my friend.

巨石应用(拆解)

巨石应用场景便是上文提到的场景,当我们的项目越来越大难以维护的时候,不管是顺势而为还是参考其他架构思维我们基本都会选择将应用拆解,将压力和负担分散开来。
这种场景使用微前端架构的难度往往是动态的,其难度取决于项目本身的复杂度是怎么样,耦合度是怎么样的。

复杂度和耦合度越高,下面的问题解决起来就越困难:

  • 怎么拆?从什么纬度拆?业务纬度?页面纬度?数据纬度?
  • 公共事务怎么处理?提升公共事务纬度统一管理还是分散管理?
  • 要不要拆?以哪种技术再组合?是否能承受切换架构的适应期的痛苦?

除此之外还有很多其他场景也会遇到的技术难题在这个场景也会被放大,并且如果拆的不漂亮可能会面临不同与单体应用但同样难受的痛苦。

这种场景有如下几个特点:

  • 技术栈统一
  • 公共事务繁多
  • 基本无通用解决方案

组合应用(整合)

这种场景通常比拆解巨石应用的场景简单的多,因为在此之前应用之间本身就是独立的,不需要思考上一个场景所面临绝大多数问题,因为本身就遵守单一职责原则。
但由于组合应用和其子应用之间都是互不了解的,其公共事务如何开放兼容各个子应用,业务上如何沟通好都是其独有的难题。

这种场景有如下几个特点:

  • 技术栈往往不统一
  • 公共事务较少
  • 社区有较多通用解决方案

其他

软件设计就是与未来打赌。

截止目前,笔者还没有见过从项目诞生之初就决定采用微前端架构的应用,基本上都是从旧有项目上改造成微前端架构的。但是,笔者认为这与微前端发展时间尚短的原因有关,未来不仅可能会有从头至尾都是采用微前端架构的应用,同时还可能会有完整链路的微前端框架(不是库)。

当然以上笔者主观猜测,如果你有其他场景可以跟笔者分享一下。

小节

实际上,有很多大型应用它自身代码质量的问题远大于其体量大小所导致的问题,架构永远是一件平衡的事情,微前端并非没有成本,如何平衡成本和收益之间的事情需要开发者自身根据其自身场景来具体平衡,微前端只是提供了开发者另外一种解决问题的纬度。

优势

增量升级

对比那些还在使用传统单体应用的团队来说,他们会因为逐渐落伍的技术栈以及因为赶工而产生的代码愈发渴望去重构他们的单体应用,而这种重构的成本十分的高昂,牵一发而动全身。而如果你使用了微前端的架构,那么你就可以选择一部分一部分的去重构,而不用因为一股脑儿重构或者升级带来的难度而苦恼。

简单、解耦的代码库

从对微前端的定义我们可知,每个单独的微前端的项目体量一定是小于整个单体应用的项目体量。较小的代码库对于开发者而言往往以为着更容易维护。尤其避免了那些本不应该耦合而耦合的组件,仅对当前子项目进行封装。
微前端实际上并不能完全代替旧有的干净的代码。微前端并不能直接让项目的代码变成高质量代码,但它可以使得一些错误的开发策略变得困难起来,比如说当你决定写一个跨应用的代码的时候,因为并没有在单体应用中写起来这么方便,你就需要认真思考应该怎么写好跨应用的代码,从而你就会再进一步思考这种代码是否有其存在的必要,这就是解耦策略带来的良好的副作用,单一职责原则在其中会无形的体现。

独立部署

和微服务一样,微前端的独立部署能力是关键的一环,他会分散部署的压力和风险。理论上来说无论前端代码是以什么形式管理的,都应该有其单独的 CI/CD 策略,每个单独的应用都不用考虑其他应用是否会给其带来风险。无论在成为微前端架构之前是以何种策略部署的,成为微前端架构之后都理当可以也应该独立部署。
微前端 - 图1

团队自治

这是将代码解耦和将项目发布流程独立的好处之一,我们不同的应用之间可以有着不同的团队去维护,其中独立的团队并不只限于独立的开发者,也可以是独立的产品设计和项目经理。举个比较典型的例子,笔者所接触的一个微前端项目就是如此,每个应用都有自己的产品设计和项目经理,每个团队的工作内容不会因为其他应用的项目周期而影响。
微前端 - 图2

小结

微前端将我们庞大的整体分解成微小的个体,从而使得每个应用全链路可以独立的运行和发展,而无需过渡协调。

弊端

辩证法唯物论,是无产阶级的宇宙观。

难度大

正如前文“什么场景需要”这一小节中提到的,项目复杂度和耦合度越高的微前端改造难度就越高,这对于改造者或者架构师而言绝对是一个技术挑战。并且社区的解决方案不一定适用于彼时彼刻的场景,微前端的改造又基本上都是全链路的改造,需要改造者或者架构师具备较广的知识领域。一旦改造不好可能得不偿失,需要改造者或者架构师谨慎设计改造方案。

成本高

  • 适应成本高,在切换微前端架构的初期,一定会有一段时间的阵痛期,期间需要不停的发现问题,承受问题,解决问题。如何更平滑的过渡过去也是一个学问。
  • 团队学习成本高,不管是团队的旧成员和新成员都需要时间去适应和学习应用切换架构后的应用,这个时候文档就非常关键了。

社区方案

先盗张图吧,这张图大概体现了一个通用微前端解决方案需要解决的问题。实际上,当微前端架构在真实场景落地时遇到的问题并不止这些。

微前端 - 图3

Single-SPA

single-SPA 是最早出现的相对成熟的通用微前端解决方案,它有如下几个特点:

  • 技术栈无关
  • 独立部署
  • 增量升级
  • 异步加载

single-SPA 是个比较薄的库,做的事情没有很多,笔者看完源码后更倾向于它是个模块加载器。
每个子应用都需要暴露其不同生命周期时的函数,然后 single-SPA 会根据配置,在路由变化的时候,调用不同的钩子函数以完成应用的加载和卸载等。
但因为有很多真实场景经常遇到的问题都没有覆盖,所以有很多东西都需要开发者自己去完成。

Qiankun

qiankun 是一个基于 single-SPA 实现的微前端解决方案,可以理解为 single-SPA 的加强版。
它提前帮开发者解决了很多 single-SPA 在真实场景经常会遇到的问题,除了 single-SPA 自带的一些特性,它还实现了如下的特性:

  • HTML Entry 的接入方式:解决 single-SPA JS Entry 的所带来的众多弊端
  • 样式隔离:使用动态的 stylesheet 来使得样式的隔离
  • JS 沙箱:采用 proxy 或者原始 diff 的方式来清除和还原每个子应用的 JS 上下文,使得每个应用的上下文不会互相污染
  • 资源预加载:会在非弱网环境下利用浏览器空余时间提前加载未加载的应用

总结

实际上在笔者看来,微前端是一种另类的模块化方案,只不过模块的概念被放大到了应用层面。因为笔者发现前端模块化的方案也是出于对前端项目的 高聚合、低耦合 的目的而诞生的,思路不磨而合。鉴于笔者对于前端模块化历史的一些了解,笔者认为微前端的出现并不具备偶然性,它是历史必然会出现的产物

以史为镜,可以知兴替。

除此之外,笔者认为前端工程化野蛮生长诸侯乱战的时代可能也快要结束了,一方面是社区的各种技术创新已经处于沉淀期,另一方面各种整合方案已经初有苗头。

其他

JS 沙箱有哪些技术方案?

CSS 隔离有哪些技术方案?

参考