微前端框架核心技术揭秘

标签: 技术干货 前端开发 微前端 | 发表时间:2022-02-22 10:02 | 作者:否子戈
出处:https://11.146.83.18
微前端框架核心技术揭秘

2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”(Micro Frontend),其后该概念在web领域逐渐落地,在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义,带你走近微前端框架,揭秘那些“不为人知”的巧妙技术实现。

概念

什么是微前端呢?虽然它在2016年就被提出,但是直至今天,我们仍然只能描述它的轮廓,无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述:

  • 微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时,它们也可以进行并行开发。——《前端架构:从入门到微前端》
  • 微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域,做他们关注和专注的事情。团队是跨职能的,从数据库到用户界面开发端到端的功能。——译,micro-frontends.org
  • 微前端的核心价值在于 “技术栈无关”,这才是它诞生的理由,或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者,2020.11.20晚阿里云微前端线下沙龙
  • 真正要解决的是,当技术更新换代时,应用可兼容不同代际的应用。

微前端是一种架构,而非一个独立的技术点。我个人从两个角度去看微前端,一个是应用结构上,微前端是多个小应用聚合为一的应用形式;一个是团队意识上,微前端架构下,每个团队只负责独立(封闭)的功能,而且需要包含从服务端到客户端,团队协作意识与以往有较大不同。

微前端方案

如何在技术上落地实现微前端的概念呢?在前端技术领域出现了如下三种技术方案:

  • 基于接口协议的:子应用按照协议导出几个接口,主应用在运行过程中调用子应用导出的这几个接口
  • 基于沙箱隔离的:主应用创建一个隔离环境,让子应用基本不用考虑自己是在什么环境下运营,按照普通的开发思路进行开发即可
  • 基于模块协议的:主应用把子应用当作一个模块,和模块的使用方式无异

三种方案各有优劣,我们不能立即下结论哪一种更好。

方案类型 典型技术 优点 缺点 共同点
接口协议 single-spa 比较自由,可自主封装 无法满足很多场景
  • 子应用/模块互不干涉
  • 技术栈无关
沙箱隔离 qiankun 开发思维简单直接 沙箱带来的性能等问题
模块协议 webpack module federation 用模块思维理解引用 脱离构建工具无法使用

就目前市面上的情况而言,基于沙箱隔离的微前端方案占据了主导,也就是本文将要深入阐述的微前端框架们,也都是这类方案。其中原因,笔者认为最主要的一点,是基于沙箱隔离的方案可以让应用以最小的成本,从原本的单体大应用迁移到微前端架构上来。

微前端框架对比评测

微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎,市面上有非常多的微前端框架,笔者在2021年做过一次收集,比较有典型意义。(虽然在那之后还出现了新的微前端框架,但其大部分原理一致,因此,以下这些框架足以说明情况。)

  • Mooa:基于Angular的微前端服务框架
  • Single-Spa:最早的微前端框架,兼容多种前端技术栈。
  • Qiankun:基于Single-Spa,阿里系开源微前端框架。
  • Icestark:阿里飞冰微前端框架,兼容多种前端技术栈
  • console-os 是在阿里云控制台体系中孵化的微前端方案, 定位是面向企业级的微前端体系化解决方案。
  • Module Federation:webpack给出的微前端方案
  • Luigi:一套复杂的分布式前端应用解决方案
  • FrintJS:自主解决依赖的微前端框架
  • PuzzleJS:一套复杂的前后端编译时相结合的微前端解决方案
  • ngx-planet:基于angular的微前端框架
  • 麦饭(mfy):精巧简易的微前端框架

除了webpack的联邦模块方案需要结合构建来做,比较特殊外,其他方案都是在运行时完成应用聚合。

“子应用独立运行”指子应用不需要放到基座应用这个大环境下就能自己跑,便于调试和被不同基座引入。

“子应用嵌套子应用”是一个比较特殊的点,目前市面上能做到的框架不多。

微前端框架核心技术

在微前端架构中,存在“主应用”和“子应用”两个层级,而微前端框架的主要任务就是让子应用能够在主应用中有效运行。如上文所述,目前较多的微前端框架是基于(或支持)沙箱隔离实现的主子应用运行机制,笔者自己实现的小型微前端框架“ 麦饭”也属于此类,因此,本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是 资源加载环境隔离两大问题,此外,还有路由、通信等问题。

资源加载

微前端框架需要从服务端拉取子应用的代码文件,并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案,现在常见的有两种方案,分别是:以JS文件作为入口;以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本,获得JS导出的内容,但是这样,仅能加载脚本资源,无法加载CSS等样式资源。而以HTML文件为入口,则可以通过HTML文件内的文件引用,把对应的所有JS、CSS文件都一起加载,而且,web站点都是以HTML文件作为入口,这也正好可以让子应用的开发者按照web开发的思路来写子应用。

笔者在写麦饭这个框架的时候,希望直接引入子应用就能跑,所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件,这个函数可以根据入口文件,解析子应用的全部资源,并做缓存。

解析资源

框架在获得HTML入口文件地址后,通过HTTP请求获得该文件的内容,对内容进行解析,解析时需要做资源树分析,也就是通过HTML读取所有资源文件,比如link, script[src]。在读取资源时,可能还需要读取资源本身又引入的资源。大致逻辑如下图:

在解析过程中,还需要根据registerMicroApp(麦饭提供的注册接口)的配置,决定CSS rules怎么处理。解析获得CSS的技巧,是通过 <style>.sheet 读取 CSSStyleSheet 对象,从中抽离出所有CSS样式规则,再按配置逻辑生成最终的样式规则。

预加载/懒加载

在设计上,一个子应用的资源有两种可选加载形式。在麦饭中,假如你希望提前预加载子应用资源,可以在registerMicroApp时直接传入 importSource(…),这个函数一执行,就会去请求资源回来并做缓存。但是,假如你不需要预加载,你想在子应用需要进入界面时(或打算让子应用进入界面时)才加载资源,则配置为 () => importSource(…) ,这种配置会在子应用执行 bootstrap 的时候才去请求资源。

环境隔离

环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的,两个子应用之间,可能存在相互污染的问题,这就要求微前端框架实现一种能力,让子应用运行在自己的一个隔离环境中,从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有:

  • iframe:样式和脚本运行的隔离,缺点在于无法全屏弹出层
  • ShadowDOM:样式隔离,缺点在于弹出层被挂在document.body下面,而样式被放在ShadowDOM内部,无法正确渲染弹出层
  • 快照沙箱
  • 代理沙箱

也有框架把这些方案结合起来,在不同的场景下,主动或被动的使用其中的一种方案。其中,快照沙箱和代理沙箱是两种比较独特的技术方案:

快照沙箱

多个子应用在页面上相互切换,而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。

这种方案只适合同一时间只运行一个子应用的场景,例如腾讯云控制台。当子应用进入界面的时候,给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时,把window清理干净,再把快照上的属性重新添加到window上,复原了子应用挂载前的window。

代理沙箱

代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现:

1. 创建代理对象

比如上面提到window可能被污染。那就创建一个window的代理对象,例如fakeWin,实现如下:

这样处理之后,我们在读取时可能读取到原始window上的值,但是一旦我们写入新属性之后,再读就读到刚才写入的值,但对于原始的window来说,没有被污染。

2. 创建运行沙箱

要使代理对象作为全局对象给子应用的脚本使用,必须把子应用放在一个沙箱里面跑,这个沙箱使用我们制作的代理对象作为全局变量,这样子应用的脚本就会操作代理对象,从而与其他子应用起到代理隔离的效果。具体实现如下:

上面代码里面的window, document, location等,都是前面创建好的代理对象。

当然,这里只给出了一些最核心思路的代码,实际上在真正实现时,还要考虑各种特殊情况,需要进行多方面的处理。

通过代理沙箱,子应用就可以在主应用中独立运行,而不会对主应用上的其他子应用产生负面影响。不过,值得一提的是,由于代理沙箱实际上虚拟了一个给子应用的环境来运行,也就意味着需要消耗更多的计算资源,会给子应用的性能带来一定影响。同时,由于这种虚拟环境在某些情况下必须连接到真实环境进行操作,或者从另外一面反过来说,虚拟环境中不一定能提供子应用所需要的全部依赖,这就会导致子应用中某些功能失效,甚至影响整个子应用的表现效果。

路由映射

如果子应用有自己的路由系统,处理不好,子应用在切换路由时会污染父应用,导致浏览器url发生变化,结果把当前页面切到另外一个地方去了。为了解决这种问题,麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的,所以,不同层的应用得到的location是不同的,基座应用使用浏览器的location,但是它的子应用则不是,修改浏览器的url之后,可以通过路由映射机制,伪造子应用得到的url。具体实现是通过创建一个临时的iframe,利用代理沙箱的能力,将子应用的location代理到iframe里面的location上去。

得益于代理沙箱,子应用的url变化不会导致浏览器的url变化。

映射逻辑需要写一个map和reactive配置项,当浏览器的url发生变化时,通过map映射到子应用内部。子应用内部url发生变化时,通过reactive映射到浏览器,这样即使用户在某一时刻刷新浏览器,也可以通过url映射关系,准确还原子应用当前的界面。

挂载

在麦饭中,子应用需要通过一个 <mfy-app> 标签来决定子应用挂载在什么地方。和qiankun等框架不同,qiankun需要在子应用中决定挂载点,但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以,子应用在哪里挂载应该由父应用决定。

子应用被放在 <mfy-app> 中,给了开发者一些特殊的能力:

  • 可以放在 v-if 内部,DOM节点被移除后挂回来,子应用还在
  • 动画效果
  • keepAlive

在实现 <mfy-app> 时用到了一些比较 hack 的技巧。比如需要借助 <mfy-app> 这个节点所在作用域的顶层节点,在顶层节点DOM对象上挂载一些数据,通过这个技巧,确保节点被移除后,再被挂载回来时,还能正确还原之前界面。

keepAlive则是在 <mfy-app> 节点没有被移除的情况下,子应用执行 unmount 时,并没有实际销毁子应用构建的 DOM 树,而是放在内存中,当子应用再次 mount 的时候,直接把这个内存里面的 DOM 树挂载到 <mfy-app> 内部。

通信/应用树

这部分是麦饭设计中最复杂的部分,也是最终与其他微前端框架区别的地方。

我构建了一个这样的树状数据结构,称之为“应用树”。它表达了基于 MFY 开发的微前端应用中,应用于子应用的引用关系。

scope

scope概念是指一个应用起来之后,会创建一个scope(作用域),这个 scope 保存了该应用的一些运行时信息,同时通过了通信的接口方法。一个应用可能会有多个子应用,这些子应用都有自己的 scope,上下级应用之间可以通过scope完成通信,比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令,接到这个指令后,两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令,而 parent_app 再把这个指令转发给了 child_app_1,这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。

rootScope 是一个特殊的scope,它对应的是基座应用,是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象,所以,利用 rootScope 可以完成跨层应用间的直接通信(虽然不推荐)。

connectScope

每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现,在同一层,实现逻辑有点像react hooks,你不需要关心你处于应用树的哪个位置,对于子应用开发团队而言,只需要在代码中使用connectScope()函数,就可以直接连接到自己所在的作用域。如果你实现过react hooks的话,应该能理解它的一个实现原理。但是由于一些实现上的限制,你不能异步执行connectScope,必须在代码第一次执行时,同步调用connectScope获取当前子应用的scope。

状态共享

“如果子应用1修改了用户的某个状态,子应用2怎么对这个修改做出响应?”

这个问题涉及到一个状态共享问题。由于我在设计时,坚持每个子应用团队应该封闭开发的理念,开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用,或者还需要依赖其他应用的状态变化,这会让我在开发的时候一直处于对当前应用状态的未知状态,那这样就没法调试和测试了。因此,设计中我直接拒绝实现子应用间的状态共享。

但是在实际使用过程中,这种需求是存在的。因此,我建议使用通信的方式解决,子应用1发出一个消息,通过 rootScope,通知网络我改变了用户状态,那么其他子应用在接受到这个消息之后,自己决定是否要重新渲染界面。

思考

本文虽然已经通过笔者实现麦饭这个小型微前端框架,详细的阐述了一个微前端框架的核心技术实现,但是,也同时遗留了很多问题:

  • 跨域加载子应用问题
  • 子应用自己还要加载资源(angularjs模板)绝对路径问题
  • 登录态怎么传递?
  • 多语言怎么配置?
  • 代码共享(依赖)怎么处理?
  • 运行时对象多个怎么办?(例如每个子应用都有自己的jQuery)
  • 跨应用加载相同资源怎么办?(例如同时请求一个api拉取数据)

微前端不是万能的,坑也很多,所以应了那句话“没有银弹”。

结语

微前端是一种架构形式,一旦采用这种架构,就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式,因此,开发团队最好经过比较长一段时间的调研之后,才决定启用这种架构。从本文中,你也会发现,要实现微前端框架的核心能力,需要使用一些看上去不那么优雅的hack方法,既然是hack方法,就存在一定的弊端,比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点,在实际项目中,还需要面临更多问题,但这并不是说我在劝退大家,而是希望大家在选择时,根据实际的需求决定,不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣,可以在文章下面留言,我们一起探讨有关微前端框架的实现技术。

相关 [前端 框架 核心] 推荐:

微前端框架核心技术揭秘

- - 腾讯CDC
2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”(Micro Frontend),其后该概念在web领域逐渐落地,在前端技术领域出现了繁多的微前端框架. 本文将向你介绍有关微前端的概念、意义,带你走近微前端框架,揭秘那些“不为人知”的巧妙技术实现. 虽然它在2016年就被提出,但是直至今天,我们仍然只能描述它的轮廓,无法给它清晰下定义.

前端框架你究竟选什么

- - 博客 - 伯乐在线
在做web开发的时候难免遇到一个问题,那就是,选择什么样的框架. Apache基金会今天发布了Flex 4.8版本, 这是Adobe将Flex捐献给Apache基金会后发布的第一个版本. 需要注意的是,Flex目前还在孵化阶段,还不是Apache的正式项目,Flex 4.8也不是一个正式的Apache版本.

[转][转]前端开发框架对比

- - heiyeluren的blog(黑夜路人的开源世界)
来源: http://www.ibm.com/developerworks/cn/web/1404_wangfx_jsframeworks/. 本文选取了 Bootstrap、jQuery UI、jQuery Mobile、Sencha ExtJS、Sencha Touch、Sencha GXT、Dojo、Dojo Mobile、Mootools、Foundation、YUI、Kissy、QWrap 等 16 个国内外前端开发框架进行初步的横向比较,可以作为大家挑选前端开发框架的初步参考.

16个非常有用的前端CSS框架

- - Jackchen Design 1984
网页是设计风格渐渐走向几个特点. 一种是创意类而另一种就是框架类. 创意类的网页是让网页更趋向于艺术风格,而框架类的网页则是为了加快网页设计的开发速度以及各种标准化的统一管理. 好的团队都会用一种高效的框架来进行快速开发. 更牛的团队会相应的开发自己的框架,或者对一些开源框架进行二次定制修改来更大程度的发挥效用.

关于前端框架的一些观点

- - 博客 - 伯乐在线
说起前端框架,我个人主张有框架不如无框架,这个观点要先从框架和库的区别说起. 我所理解的库,解决的是代码或是模块级别的复用或者对复杂度的封装问题;而框架,更多的是对模式级别的复用和对程序组织的规范,这里的模式是指比如 MVC,为了实现 M 和 V 的解耦,通过 IOC 或是 PubSub 等手段,把丑陋的耦合由经常变化的业务代码转移到不经常变化的框架内部消化.

优秀的开源前端框架Bootstrap发布2.0.1版

- - InfoQ cn
Bootstrap是著名的社交网站、微博的先驱Twitter在2011年8月推出的开源WEB前端框架,集合CSS和HTML,使用了最新的浏览器技术,为快速WEB开发提供了一套前端工具包,包括布局、网格、表格、按钮、表单、导航、提示等等. 使用Bootstrap可以构建出非常优雅的前端界面,而且占用资源非常小.

模块化高扩展性的前端框架 KISSY

- - TaoBaoUED
模块化高扩展性的前端框架 KISSY. 注:本文为 2013 年 3 月程序员杂志同名文章的完整版. 伴随着淘宝的快速发展,诞生已三年多的 KISSY 也取得了巨大的成长. 目前应用于阿里集团的多个业务团队,特别是淘宝,天猫,一淘的绝大多数业务都采用了 KISSY, 满足了从前台的 web page 到后台的 web app 再到移动端应用(起步阶段)的广泛需求.

酷酷的前端MVC框架AngularJS(二)HelloWorld

- - ITeye博客
angularJS在github上面进行一个代码的托管. 地址:https://github.com/angular/angularjs.org  . 注意:需要兼容IE8的同学请下载1.3之前的版本,在1.3之后已经放弃了IE8,估计是为了以后大版本升级做铺垫吧. 各个版本的下地址在这里https://github.com/angular/angular.js/releases.

HTML5 跨屏前端框架 zui 1.0 正式版本发布!

- - 开源中国社区最新新闻
我们非常高兴的向大家介绍我们团队开发的zui框架. 我们团队在开发禅道、蝉知和然之系统时,也涉及到ui框架选型的问题. 我们先后经历了最初的表 格布局,到后来的yui框架,再到后来的bootstrap框架,再到最后我们综合这些ui布局方法和框架的各种优缺点,逐步形成了我们自己的zui框 架. 在此感谢我们团队的catouse同学.