解密微前端:"巨石应用"的诞生

7,583 阅读10分钟

随着我们中后台系统的复杂,往往会遇到多个团队独立维护的子应用接入统一的主应用中,这些子应用往往独立开发、独立部署、彼此完全解耦,这时候往常的单一应用无法满足业务的增长需求。而微前端便是用来解决随着时间的推移业务复杂度的提升,某个单应用演变为难以维护的“巨石应用”。

本文并不是一个源码解析以及上手教程的文章,我希望从一个宏观的角度介绍下微前端并且简单聊一下微前端在我们现在项目中的一些思考。

先聊聊背景

我一直认为抛离业务的技术改造都是没有太大价值并且很难走远的。这里我先简单介绍下我对目前项目所做的一个架构演变以及一些简单思考。我目前所处一个大型的 B 端项目团队,系统根据业务域划分的子应用有几十个,系统 PV 百万+,所以在这样一个庞大的系统中我们的系统架构也经历了以下一些变化。

模版架构

在我刚加入团队的时候,我们的系统还是一个前后端未完全分离的架构,模版提供了一个入口挂载根节点,前端在根节点上渲染 React / Vue 应用。

不难发现这样的架构存在一定的弊端:

  • 后端解析模版,挂载模版;
  • 后端管控路由,前端失去了对路由的掌控权,并且多个团队路由规则管控较难;
  • 每一次前端发布需要先发布静态资源再发布模版,发布繁琐容易出错;
  • 这样的开发方式相对来说较为古老,对于喜欢捣鼓新事物的前端来说很难激发热情;

但是值得庆幸的是,这时候我们的系统已经有一定的分治思想了,主应用(这个时候应该说是 layout 层)和子应用单独挂载在不同的模版片段上,这也为后面的 iframe 和微前端改造减少了不少工作量

其实,这样的架构也有一定微前端的影子:) 。

iframe 架构

后面随着后端微服务化的转型,后端已经不去关心路由的管控和页面的挂载,转而提供更加原子化的微服务。而对我们前端来说:

  • 需要不依赖后端模版;
  • 需要管控路由并制定统一的路由规则;
  • 主子应用的沙箱隔离;
  • 尽量对子业务的改动做到最小化,很多业务包袱很重;
  • 快速落地并实现高可用(没办法,开发时间永远是一个打败很多选项的因素...);

但是随着 iframe 架构的落地以及后续迭代的进行,我们也发现了 iframe 方案的一些弊端:

  • 通信方式简单,简单的 postmessage API 并不能满足业务的需求;
  • 样式割裂,iframe 会导致诸如 Dialog 这样子的全局蒙层仅在 ifame 区块内展示,使我们系统更像是“拼凑”出来的;
  • 性能瓶颈,路由的切换会导致 iframe 内子应用的重新加载,性能堪忧;
  • 跨域问题,chrome80 的 samesite 策略会导致 iframe 方案的跨域 cookie 无法带给后端;

微前端架构

最后在今年中旬的时候我这边将 iframe 架构升级到了微前端 + iframe 并存的架构,并开发落地了一系列微前端相关的开发工具链(喜大普奔...)。

so,为什么不是 iframe?

在我看来,微前端是一个思想,是一种开发模式和架构的演变,诸如 qiankun、icestark 等框架也仅是微前端实现的落地,合理的 iframe 实现未尝不算是一种微前端实现的落地。我们原来的 iframe 架构设计一定程度上也算是一种微前端思想,并且在我看来,iframe 对于这种跨团队的中后台巨无霸项目依然有着天然的优势,理由如下:

  • 框架无关:iframe 只加载部署完应用链接;
  • 独立沙箱:iframe 拥有浏览器的独立沙箱,子应用和主应用完全不用考虑 js & css 污染问题;
  • 开发简单:仅仅一个 iframe 标签;

但是,与之而来的便是 iframe 的一些痛点:

  • UI 体验不好:iframe 仅在制定的渲染块内渲染,而子应用的一些类似于带遮罩层的 Modal 等全局的 UI 组件只会在 iframe 内呈现;
  • 通信困难:iframe 强大的沙箱机制带来的副作用就是父子应用通信困难,仅仅一个 postmessage API,跨域 cookie、Promise 等等都难以实现;
  • 路由丢失:子应用的 url 改变无法及时同步父应用,随着页面的刷新,子应用的路由状态丢失;
  • 加载时间长:每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程;

尽管以上这些痛点或多或少的配合着一些 hack 的工具以及开发规范都有一定的解决方案,但是有更好的选择为什么不尝试呢:)

那么,什么是微前端?

这里我不去讲概念,道理大家都懂,概念一搜全都是。例如微前端很官方的诠释:

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently

就像巨石应用并不是一蹴而就的,接下来我来通过一个巨石应用的演变来向大家介绍我理解的微前端。

单个页面

起初我们的系统可能仅有一个业务模块。路由硬编码在项目里,layout 层和业务子系统也写在一起。

多个页面

随着业务的增长,我们的系统接入了更多的业务模块,这个时候其实通过一定的路由配置和多页的配置,项目也还算是没太大问题。

但是这个时候需要引起警觉,如果再接入了更多的业务模块还停留在当前的模式下,项目该怎么维护?

更多的页面?巨石应用!

随着业务更进一步的增长,接入的业务模块越来越多,不仅我们以导航维度扩充的子应用增多,甚至诸如首页这样子的页面上也会有归属于多个业务域的区块。总结起来就会分为两类场景:

  • 单实例:一个或多个页面对应一个子应用,同一时刻仅有一个子应用渲染展示;
  • 多实例:一个页面包含多个子业务应用区块,同一时候有多个实例渲染展示;

这时如果没有很好的处理和子系统的拆分,那么我们的应用就会变为巨石应用...

  • 开发迭代并上线一个巨石应用比上线多个子应用要蛋疼的多...
  • 多人(多团队)维护一个项目,你永远不知道别人做了啥或者将要做啥...
  • 往往这样子的应用,大家都是做加法而不去做减法,使得项目越来越大,无用代码越来越多...

微前端,分而治之!

这时候我们往往将系统根据业务域的划分拆分成不同的子应用,而承载这些子应用的 layout 层我们拆分为主应用,各个子应用独立开发独立发布,并且由不同的业务团队维护,以此来解决复杂的单体应用带来的各种开发维护问题。

可以看出,微前端便是采取分治的思想来避免单体应用演变成巨石应用的。

在我看来,在微前端的思想中,重点强调了几点:

  • 独立性:微前端的主应用以及各个子应用独立开发、独立部署,并且在一定程度上微前端子应用可以独立于主应用单独运行;
  • 沙箱隔离:子应用有自己单独的运行时,各个子应用之间的状态不被共享;
  • 框架无关:子应用完全可以采取不同的框架进行开发,因为现有微前端框架实现中,主应用仅是加载子应用构建后的 bundle;

我们的选择

现在市面上的微前端框架有很多,例如阿里内部就有 icestark、qiankun 两大比较成熟的开源微前端框架,以及社区上 singleSPA 等。那么我们该如何选择适合我们项目的微前端框架呢,这里我简单罗列了我在选择微前端框架时候的一些思考:

  • 改动成本:任何业务团队都不能逃避这个话题,你怎么说服产品和老板需要这么长时间的技术改造(还存在一定风险)、你怎么说服子应用方配合改造(相信我,子应用方不会愿意去做 > 1 天改造的...);
  • 沙箱隔离:这个就不多说了,很多子应用会集成自己的埋点&监控体系,这样的体系往往会挂载或者劫持全局变量的;
  • 路由不变性:在我们 iframe 架构中,已经有一套比较完善的路由规则,那么如何保证路由不变的前提下去做架构升级呢?
  • 通信不变性:同上,在我们的 iframe 架构中,已经有一套比较成熟的通信工具,那么能不能在保证 API 不变的前提下完成微前端的通信呢?或者干脆微前端可以服用 iframe 的通信工具?
  • 兼容 iframe:这么庞大的系统不是一下子就迁移到微前端的,并且我们也不打算抛弃 iframe 架构(不肯能全部业务都迁移的...)。那么,如何保证两套架构的共存?更进一步,如何保证两套架构共存然后还能共享一套路由规则?
  • 灰度控制:子应用的大版本发布往往会伴随着灰度控制,那么如何保证微前端接入后也能有完整的灰度流程?是做一个版本管控的工具还是直接使用 htmlEntry?
  • “远古”应用接入:远古应用保持 iframe 嵌入就最好了,那么我们能不能贪一点也能支持“远古巨龙”的丝滑接入呢?

最后,结合上面的一些思考以及我们现在的系统架构,我选择了 qiankun 来落地我们的微前端方案。并且为了保证微前端接入以及版本管控的便捷,我们落地了一些微前端的生态链工具。

结语

以上便是我在做微前端改造时候结合业务系统的一些思考,如果有不对的地方欢迎指正。之后我也会输出 qiankun 的源码解析以及我所做的一些微前端工具的原理分析等文章(撒花...)。

🏆 技术专题第四期 | 聊聊微前端的那些事......