什么是微前端
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用可以独立运行、独立开发、独立部署。
微前端的概念由ThoughtWorks于2016年提出,此后很快被业界所接受,并在各互联网大厂中得到推广和应用。
微服务与微前端有很多相似之处,都是希望将某个单一的单体应用,转化为多个可以独立运行、独立开发、独立部署、独立维护的服务或者应用的聚合,从而满足业务快速变化及分布式多团队并行开发的需求。微服务与微前端不仅仅是技术架构的变化,还包含了组织方式、沟通方式的变化。微服务与微前端原理和软件工程,面向对象设计中的原理同样相通,都是遵循单一职责(Single Responsibility)、关注分离(Separation of Concerns)、模块化(Modularity)与分而治之(Divide & Conquer)等基本的原则。
微前端的优缺点
优点
微前端如此受到重视,是由于其特点为开发团队带来了大量的收益。下面就分别描述一下其优点。
可以与时俱进,不断引入新技术/新框架
前端技术栈日新月异,推陈出新的速度绝对是冠绝群雄。如何在维护好遗留系统的前提下,不断引入新技术和新框架,提高开发效率、质量、用户体验,成为每个团队需要认真对待的问题。微前端可以很好的实现应用和服务的隔离,互相之间几乎没有影响,可以很好的支持团队引入新技术和新框架。
局部/增量升级
对于许多组织来说,追求增量升级就是他们迈向微前端的第一步。对他们来说,老式的大型单体前端要么是用老旧的技术栈打造的,要么就充斥着匆忙写成的代码,已经到了该重写整个前端的时候了。一次性重写整个系统风险很大,我们更倾向一点一点换掉老的应用,同时在不受单体架构拖累的前提下为客户不断提供新功能。
为了做到这一点,解决方案往往就是微前端架构了。一旦某个团队掌握了在几乎不影响旧世界的同时为生产环境引入新功能的诀窍,其他团队就会纷纷效仿。现有代码仍然需要继续维护下去,但在某些情况下还要继续添加新功能,现在总算有了解决方案。
到最后,我们就能更随心所欲地改动产品的各个部分,并逐渐升级我们的架构、依赖关系和用户体验。当主框架发生重大变化时每个微前端模块都可以按需升级,不需要整体下线或一次性升级所有内容。如果我们想要尝试新的技术或互动模式,也能在隔离度更好的环境下做试验。
代码简洁、解耦、更易维护
微前端体系下,每个小模块的代码库要比一个单体前端的代码库小很多。对开发者来说这些较小的代码库处理起来更简单方便。而且微前端还能避免无关组件之间不必要的耦合,让代码更简洁。我们可以在应用的限界上下文处划出更明显的界限,更好地避免无意间造成的这类耦合问题。
独立部署
就像微服务一样,微前端的一大优势就是可独立部署的能力。这种能力会缩减每次部署涉及的范围,从而降低了风险。不管你的前端代码是在哪里托管,怎样托管,各个微前端都应该有自己的持续交付管道;这些管道可以将微前端构建、测试并部署到生产环境中。我们在部署各个微前端时几乎不用考虑其他代码库或管道的状态;就算旧的单体架构采用了固定、手动的按季发布周期,或者隔壁的团队在他们的主分支里塞进了一个半成品或失败的功能,也不影响我们的工作。如果某个微前端已准备好投入生产,那么它就能顺利变为产品,且这一过程完全由开发和维护它的团队主导
组织更具扩展能力,其团队更加独立自治。
解藕代码库、分离发布周期还能带来一个高层次的好处,那就是大幅提升团队的独立性;一支独立的团队可以自主完成从产品构思到最终发布的完整流程,有足够的能力独立向客户交付价值,从而可以更快、更高效地工作。为了实现这一目标需要围绕垂直业务功能,而非技术功能来打造团队。一种简单的方法是根据最终用户将看到的内容来划分产品模块,让每个微前端都封装应用的某个页面,并分配给一个团队完整负责。相比围绕技术或“横向”问题(如样式、表单或验证)打造的团队相比,这种团队能有更高的凝聚力。
缺点
有得必有失,在引入微前端的过程中难免会引入一些新的问题。只不过我们认为这些风险都能控制在合理水平上,微前端终究还是利大于弊的。
重复依赖
不同应用之间依赖的包存在很多重复,由于各应用独立开发、编译和发布,难免会存在重复依赖的情况。导致不同应用之间需要重复下载依赖,额外再增加了流量和服务端压力。
团队之间更加分裂
大幅提升的团队自治水平可能会让各个团队的工作愈加分裂。各团队只关注自己的业务或者平台功能,在面向用户的整体交付方面,会导致对用户需求和体现不敏感,和响应不及时。
微前端应用场景
兼容遗留系统
经常会有团队需要在兼容已有系统的前提下,使用新框架去开发新功能。遗留系统功能已经完善,并且稳定运行,团队没有必要,也没有精力去将遗留系统重构一遍。此时团队如果需要使用新框架,新技术去开发新的应用,使用微前端是很好的解决方案。
应用聚合
大型互联网公司都会为用户提供很多应用和服务,如何为用户呈现具有统一用户体验的应用聚合成为必须解决的问题。而在大型商业公司内部,往往部署有大量的软件服务。如何为员工提供服务聚合,提供员工工作效率,成为企业内部IT建设的重中之重。前端聚合已成为一个技术趋势,目前比较理想的解决方案就是微前端。
团队间共享
不用应用之间往往存在很多可以共享和功能和服务,如果在团队之间进行高质量的共享成为提高研发效率的一条重要途径。微前端可以采用组件或者服务的方式进行团队间的技术共享。其低内聚高耦合的共享,使得高质量的共享成为可能。
局部/增量升级
一个大的产品由很多应用和服务组成,很多时候只需要对部分应用和服务进行升级。如果是单体应用,升级耗时长,风险高,影响可服务性。而前端可以只对需要的应用和服务进行升级,不会影响其他应用和服务。升级效率高,风险低,不影响其他应用和服务的可服务性。
微前端要克服的几个障碍
既然微前端具有如此多的优点,您是不是已经跃跃欲试,想要马上在团队里引入了呢?且慢,在引入微前端的过程中会遇到一些障碍,我们先看看会遇到那些障碍吧。
资源的隔离
由于存在不同应用各自定义CSS和全局变量的情况,应用聚合时需要考虑彼此之间的影响。应用JS沙箱和CSS隔离等相关技术,使各应用之间互不影响。
应用的注册
Html Entry 和 Config Entry,是关于如何注册子应用信息。
对性能的影响
按需加载、公共依赖加载和预加载,是关于性能的,这些很重要,否则虽然上了微前端,但性能严重下降,或者由于升级引起线上故障,就得不偿失了。
应用间通信
父子应用通讯,顾名思义,无需解释。
应用嵌套/并行
子应用嵌套 和 子应用并行 是微前端的进阶应用,在某些场景下会用到。
微前端实现的几种方式
路由分发式微前端
路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。
目前,通过路由分发式的微前端架构应该是采用最多、最容易实现的 “微前端” 方案。但这种方式实际上只是多个前端应用的聚合并不是一个完整的整体。每次用户从A应用跳转到B应用的时候,实际上是访问不同的html页面。
它适用于以下场景:
- 不同技术栈之间差异比较大,难以兼容、迁移、改造
- 项目不想花费大量的时间在这个系统的改造上
- 现有的系统在未来将会被取代
- 系统功能已经很完善,基本不会有新需求
使用iFrame创建容器
使用iframe标签嵌套将另一个HTML页面嵌入到当前页面中。iframe可以创建一个全新的独立的宿主环境,这意味着我们的前端应用之间可以相互独立运行。采用iframe有几个重要的前提:
- 网站不需要 SEO 支持
- 拥有相应的应用管理机制。
在采用iframe的时候,我们需要做这么两件事:
- 设计管理应用机制:在什么情况下,我们会去加载、卸载这些应用;在这个过程中,采用怎样的动画过渡,让用户看起来更加自然。
- 设计应用通讯机制:直接在每个应用中创建 postMessage 事件并监听,并不是一个友好的事情。其本身对于应用的侵入性太强,因此通过 iframeEl.contentWindow去获取iFrame容器内的Window对象是一个更简化的做法。随后,就需要定义一套通讯规范:事件名采用什么格式、什么时候开始监听事件等等。
自制框架兼容应用
不论是基于Web Components的Angular,或者是VirtualDOM的React 等,现有的前端框架都离不开基本的HTML元素DOM。
那么,我们只需要:
- 在页面合适的地方引入或者创建 DOM
- 用户操作时,加载对应的应用(触发应用的启动),并能卸载应用。
第一个问题,创建 DOM 是一个容易解决的问题。而第二个问题,则一点儿不容易,特别是移除 DOM 和相应应用的监听。当我们拥有一个不同的技术栈时,我们就需要有针对性设计出一套这样的逻辑。
尽管Single-SPA已经拥有了大部分框架(如 React、Angular、Vue 等)的启动和卸载处理,但是它仍然适合于微前端的加载和卸载。可以自己设计和实现一个微前端框架。虽然,这种方式的上手难度相对比较高,但是后期订制及可维护性比较方便。在不考虑每次加载应用带来的用户体验问题,其唯一存在的风险可能是:第三方库不兼容。
但是,不论怎样,与iFrame相比,其在技术上更先进。同时与iframe类似,我们仍然面对着一系列的不大不小的问题:
- 需要设计一套管理应用的机制。
- 对于流量大的toC应用来说,会在首次加载的时候,会多出大量的请求。
组合式集成:将应用微件化
组合式集成,即通过软件工程的方式在构建前、构建时、构建后等步骤中,对应用进行一步的拆分,并重新组合。
从这种定义上来看,它可能算不上并不是一种微前端——它可以满足了微前端的三个要素,即:独立运行、独立开发、独立部署。但是,配合上前端框架的组件 Lazyload 功能——即在需要的时候,才加载对应的业务组件或应用,它看上去就是一个微前端应用。与此同时,由于所有的依赖、Pollyfill已经尽可能地在首次加载了,CSS样式也不需要重复加载。
常见的方式有:
- 独立构建组件和应用,生成 chunk 文件,构建后再归类生成的 chunk 文件。(这种方式更类似于微服务,但是成本更高)
- 开发时独立开发组件或应用,集成时合并组件和应用,最后生成单体的应用。
- 在运行时,加载应用的 Runtime,随后加载对应的应用代码和模板。
应用间的关系如下图所示:
weijian.jpg
组合式集成对比
这种方式看上去相当的理想,即能满足多个团队并行开发,又能构建出适合的交付物。
但是,首先它有一个严重的限制:必须使用同一个框架。对于多数团队来说,这并不是问题。采用微服务的团队里,也不会因为微服务这一个前端,来使用不同的语言和技术来开发。当然了,如果要使用别的框架,也不是问题,我们只需要结合上一步中的自制框架兼容应用就可以满足我们的需求。
其次,采用这种方式还有一个限制,那就是:规范!在采用这种方案时,我们需要:
- 统一依赖。统一这些依赖的版本,引入新的依赖时都需要一一加入。
规范应用的组件及路由。避免不同的应用之间,因为这些组件名称发生冲突。 - 构建复杂。在有些方案里,我们需要修改构建系统,有些方案里则需要复杂的架构脚本。
- 共享通用代码。
- 制定代码规范。
因此,这种方式看起来更像是一个软件工程问题。
纯Web Components技术构建
Web Components是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 Web 应用中使用它们。
它主要由四项技术组件:
- Custom elements,允许开发者创建自定义的元素。
- Shadow DOM,即影子 DOM,通常是将 Shadow DOM 附加到主文档 DOM 中,并可以控制其关联的功能。而这个 Shadow DOM 则是不能直接用其它主文档 DOM 来控制的。
- HTML templates,即template和slot元素,用于编写不在页面中显示的标记模板。
- HTML Imports,用于引入自定义组件。
每个组件由 link 标签引入:
<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">
随后,在各自的 HTML 文件里,创建相应的组件元素,编写相应的组件逻辑。一个典型的Web Components应用架构如下图所示:
image
Web Components 架构
可以看到这边方式与我们上面使用iframe的方式很相似,组件拥有自己独立的 Scripts 和 Styles,以及对应的用于单独部署组件的域名。然而它并没有想象中的那么美好,要直接使用纯Web Components来构建前端应用的难度有:
- 重写现有的前端应用。我们需要使用Web Components来完成整个系统的功能。
- 上下游生态系统不完善。缺乏相应的一些第三方控件支持,这也是为什么jQuery相当流行的原因。
- 系统架构复杂。当应用被拆分为一个又一个的组件时,组件间的通讯就成了一个特别大的麻烦。
Web Components中的Shadow DOM更像是新一代的前端DOM容器,遗憾的是并不是所有的浏览器,都可以完全支持Web Components。
结合Web Components构建
Web Components离现在的我们太远,可是结合Web Components来构建前端应用,则更是一种面向未来演进的架构。或者说在未来的时候,我们可以开始采用这种方式来构建我们的应用。好在,已经有框架在打造这种可能性。
就当前而言,有两种方式可以结合Web Components来构建微前端应用:
- 使用Web Components构建独立于框架的组件,随后在对应的框架中引入这些组件
- 在Web Components中引入现有的框架,类似于iframe的形式
前者是一种组件式的方式,或者则像是在迁移未来的 “遗留系统” 到未来的架构上。
在Web Components中集成现有框架
现有的Web框架已经有一些可以支持Web Components的形式,诸如 Angular支持的createCustomElement,就可以实现一个Web Components形式的组件:
platformBrowser()
.bootstrapModuleFactory(MyPopupModuleNgFactory)
.then(({injector}) => {
const MyPopupElement = createCustomElement(MyPopup, {injector});
customElements.define(‘my-popup’, MyPopupElement);
});
在未来,将有更多的框架可以使用类似这样的形式,集成到Web Components应用中。
集成在现有框架中的 Web Components
另外一种方式,则是类似于Stencil的形式,将组件直接构建成Web Components形式的组件,随后在对应的诸如,如 React 或者 Angular 中直接引用。
如下是一个在 React 中引用 Stencil 生成的Web Components的例子:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'test-components/testcomponents';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
在这种情况之下,我们就可以构建出独立于框架的组件。
同样的 Stencil 仍然也只是支持最近的一些浏览器,比如:Chrome、Safari、Firefox、Edge 和 IE11
复合型
复合型,就是针对实际的应用场景,选取上面的几个类型挑几种组合起来解决实际问题。
微前端架构选型指南
那么如何依据不同的情况来选择合适的方案呢。
快速选型指南图:
image
微前端选型指南
关键点的相关解释如下:
-
框架限制: 在现实中多数组织需要兼容遗留系统,在技术选型上会有这样那样的限制。
-
IE问题: 不论是在几年前,还是在今年,我们实施微前端最先考虑的就是对于IE的支持。在我遇到的项目上,基本上都需要支持IE,因此在技术选型上就受限一定的限制。而在我们那些不需要支持IE的项目上,他们就可以使用Web Components技术来构建微前端应用。
-
依赖独立: 即各个微前端应用的依赖是要统一管理,还是要在各个应该中自己管理。统一管理可以解决重复加载依赖的问题,独立管理会带来额外的流量开销和等待时间。
微前端方案的对比:
方式 | 开发成本 | 维护成本 | 可行性 | 同一框架要求 | 实现难度 | 潜在风险 |
---|---|---|---|---|---|---|
路由分发 | 低 | 低 | 高 | 否 | ★ | 这个方案太普通了 |
iFrame | 低 | 低 | 高 | 否 | ★ | 这个方案太普通了 |
应用微服务化 | 高 | 低 | 中 | 否 | ★★★★ | 针对每个框架做定制及 Hook |
微件化 | 高 | 中 | 低 | 是 | ★★★★★ | 针对构建系统,如 webpack 进行 hack |
微应用化 | 中 | 中 | 高 | 是 | ★★★ | 统一不同应用的构建规范 |
纯 Web Components | 高 | 低 | 高 | 否 | ★★ | 新技术,浏览器的兼容问题 |
结合 Web Components | 高 | 低 | 高 | 否 | ★★ | 新技术,浏览器的兼容问题 |
同样的,一些复杂概念的解释如下:
应用微服务化,即每个前端应用一个独立的服务化前端应用,并配套一套统一的应用管理和启动机制,诸如微前端框架 Single-SPA 或者 mooa 。
微件化,即通过对构建系统的 hack,使不同的前端应用可以使用同一套依赖。它在应用微服务化的基本上,改进了重复加载依赖文件的问题。
微应用化,又可以称之为组合式集成,即通过软件工程的方式,在开发环境对单体应用进行拆分,在构建环境将应用组合在一起构建成一个应用。详细的细节,可以期待后面的文章《一个单体前端应用的拆解与微服务化》
微前端方案的对比:复杂方式
之前看到一篇微服务相关的 文章,介绍了不同微服务的区别,其采用了一种比较有意思的对比方式特别详细,这里就使用同样的方式来展示:
架构目标 | 描述 |
---|---|
a. 独立开发 | 独立开发,而不受影响 |
b. 独立部署 | 能作为一个服务来单独部署 |
c. 支持不同框架 | 可以同时使用不同的框架,如 Angular、Vue、React |
d. 摇树优化 | 能消除未使用的代码 |
e. 环境隔离 | 应用间的上下文不受干扰 |
f. 多个应用同时运行 | 不同应用可以同时运行 |
g. 共用依赖 | 不同应用是否共用底层依赖库 |
h. 依赖冲突 | 依赖的不同版本是否导致冲突 |
i. 集成编译 | 应用最后被编译成一个整体,而不是分开构建 |
那么,对于下表而言,表中的 a~j 分别表示上面的几种不同的架构考虑因素。
方式 | a | b | c | d | e | f | g | h | i |
---|---|---|---|---|---|---|---|---|---|
路由分发 | O | O | O | O | O | O | |||
iFrame | O | O | O | O | O | O | |||
应用微服务化 | O | O | O | O | |||||
微件化 | O | O | - | - | O | - | |||
微应用化 | O | O | O | - | - | O | - | O | |
纯Web Components | O | O | O | O | O | - | - | O | |
结合Web Components | O | O | O | O | O | O | O |
图中的 O 表示支持,空白表示不支持,- 表示不受影响。
微前端的业务拆分
- 按照业务拆分
- 按照权限拆分
- 按照变更频率拆分
- 按照组织结构拆分
- 跟随后端微服务拆分
这些分类方法,直接看字面应该不会有歧义了,如何选择,就要看实际情况了
微前端框架介绍
https://github.com/CanopyTax/single-spa
https://github.com/ionic-team/stencil
https://github.com/phodal/mooa
参考资料
书籍
《前端架构:从入门到微前端》
文章
《干货分享:蚂蚁金服前端框架和工程化实践》
《大前端时代下的微前端架构:实现增量升级、代码解耦、独立部署》
《用微前端的方式搭建类单页应用(美团微前端方案)》
《微前端的设计理念与实践初探》
《微前端的那些事儿》
《微前端如何落地》
网友评论