前端如何发展到微前端
从最早期的前后端不分离,借助后端进行页面渲染,JSP、ASP、PHP技术盛行。随着项目不断扩大,这种开发形式的弊端也逐渐暴露出来,前端后端越来越复杂,前后耦合严重,不方便分工协作。然后前后端分离的模式诞生,喜欢做用户体验的工程师,专心研究前端,喜欢优化服务器性能,喜欢和数据打交道的工程师研究后端。前后端分离,各自的技术也在不断升级,后端开始向着微服务发展,前端也随着岁月的沉淀,项目变的臃肿,打包编译速度也在不断变慢,而且想要尝试新的技术时,大规模的替换旧代码风险升高,进而前端也面临着拆分,就这样前端发展到微前端时代。
微前端概念
微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
它主要解决了两个问题:
- 1、随着项目迭代应用越来越庞大,难以维护。
- 2、跨团队或跨部门协作开发项目导致效率低下的问题
特性
- 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
- 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
- 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
- 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
- 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率
微前端架构背后的核心思维
-
技术不可知主义
每个团队应该选择自己的技术栈以及技术进化路线,而不是与其他团队步调一致。在项目中可以通过引入自定义元素来提供技术栈无关的接口,同时还隐藏了复杂的内部实现。也许在微前端的语境之下,框架将不是未来架构师主要考虑的问题,如何高效的提供可复用的WebComponent会成为核心问题。
-
隔离团队之间的代码
即便所有团队都使用同样的框架,也不要共享同一个运行时环境。构建自包含的Apps。不要依赖共享的状态或者全局变量。
-
建立团队自己的前缀
在还不能做到完全隔离的环境下,通过命名规约进行隔离。对于CSS, 事件,Local Storage 以及 Cookies之类的环境之下,通过命名空间进行的隔离可以避免冲突,以及所有权。
-
原生浏览器标准优先于框架封装的API
使用 用于通信的原生浏览器事件机制 ,而不是自己构建一个PubSub系统。如果确实需要设计一个跨团队的通信API,那么也尽量让设计简单为好。
-
构建高可用的网络应用
即便在Javascript执行失败的情况下,站点的功能也应保证可用。使用同构渲染以及渐进增强来提升体验和性能。
微前端应用场景
从上面的演变过程可以看出,微前端架构比较适合大型的 Web 应用,常见的有以下 3 种形式
- 公司内部的平台系统。 这些系统之间存在一定的相关性,用户在使用过程中会涉及跨系统的操作,频繁地页面跳转或系统切换将导致操作效率低下。而且,在多个独立系统内部可能会开发一些重复度很高的功能,比如用户管理,这些重复的功能会导致开发成本和用户使用成本上升。
- 大型单页应用。 这类应用的特点是系统体量较大,导致在日常调试开发的时候需要耗费较多时间,严重影响到开发体验和效率。而且随着业务上的功能升级,项目体积还会不断增大,如果项目要进行架构升级的话改造成本会很高。
- 对已有系统的兼容和扩展。 比如一些项目使用的是老旧的技术,使用微前端之后,对于新功能的开发可以使用新的技术框架,这样避免了推翻重构,也避免了继续基于过时的技术进行开发。
微前端架构模式
了解完微前端的基本原理之后再来看看具体是如何实现的。微前端架构按集成微应用的位置不同,主要可以分为 2 类:
- 在服务端集成微应用,比如通过 Nginx 代理转发;
- 在浏览器集成微应用,比如使用 Web Components 的自定义元素功能。
一些说法认为通过构建工具在编译的时候进行集成也属于微前端范畴,比如将微应用发布成独立的npm 包,共同作为主应用的依赖项,构建生成一个供部署的 JS Bundle,但这种方式并不符合微前端的核心思想,也并不是主流的微前端实现方式,故不做深入讨论。本文只讨论服务端集成和浏览器端集成的情况
服务端集成
服务端集成常用的方式是通过反向代理,在服务端进行路由转发,即通过路径匹配将不同请求转发到对应的微应用。这种架构方式实现起来比较容易,改造的工作量也比较小,因为只是将不同的 Web 应用拼凑在一起,严格地说并不能算是一个完整的 Web 应用。当用户从一个微应用跳转到另一个微应用时,往往需要刷新页面重新加载资源。
这种代理转发的方式和直接跳转到对应的 Web 应用相比具有一个优势,那就是不同应用之间的通信问题变得简单了,因为在同一个域下,所以可以共享 localstorage、cookie 这些数据。譬如每个微应用都需要身份认证信息 token,那么只需要登录后将 token 信息写入 localstorage,后续所有的微应用就都可以使用了,不必再重新登录或者使用其他方式传递登录信息。
浏览器集成
浏览器集成也称运行时集成,常见的方式有以下 3 种。
- iframe。通过 iframe 的方式将不同的微应用集成到主应用中,实现成本低,但样式、兼容性方面存在一定问题,比如沙箱属性 sandbox 的某些值在 IE 下不支持。
- 前端路由。每个微应用暴露出渲染函数,主应用在启动时加载各个微应用的主模块,之后根据路由规则渲染相应的微应用。虽然实现方式比较灵活,但有一定的改造成本。
- Web Components。 基于原生的自定义元素来加载不同微应用,借助 Shadow DOM 实现隔离,改造成本比较大。
这也是一种非常热门的集成方式,代表性的框架有 single-spa 以及基于它修改的乾坤。
微前端框架介绍
目前较成熟的微前方案有 qiankun、micro-app、EMP 方案,下面分别分析这三个微前端方案:
基于 iframe 完全隔离的方案
作为前端开发,我们对 iframe 已经非常熟悉了,在一个应用中可以独立运行另一个应用。它具有显著的优点:
- 非常简单,无需任何改造
- 完美隔离,JS、CSS 都是独立的运行环境
- 不限制使用,页面上可以放多个
iframe来组合业务
当然,缺点也非常突出:
- 无法保持路由状态,刷新后路由状态就丢失
- 完全的隔离导致与子应用的交互变得极其困难
iframe中的弹窗无法突破其本身- 整个应用全量资源加载,加载太慢
这些显著的缺点也催生了其他方案的产生。
qiankun 方案
qiankun 方案是基于 single-spa 的微前端方案。
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它对 single-spa 做了一层封装。主要解决了 single-spa 的一些痛点和不足。通过 import-html-entry 包解析 HTML 获取资源路径,然后对资源进行解析、加载。
通过对执行环境的修改,它实现了 JS 沙箱、样式隔离 等特性。
特点
- html entry 的方式引入子应用,相比 js entry 极大的降低了应用改造的成本;
- 完备的沙箱方案,js 沙箱做了 SnapshotSandbox、LegacySandbox、ProxySandbox 三套渐进增强方案,css 沙箱做了 strictStyleIsolation、experimentalStyleIsolation 两套适用不同场景的方案;
- 做了静态资源预加载能力;
不足
- 适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
- css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
- 无法同时激活多个子应用,也不支持子应用保活;
- 无法支持 vite 等 esmodule 脚本运行;
micro-app 方案
micro-app 是基于 webcomponent + qiankun sandbox 的微前端方案。
京东 micro-app 并没有沿袭 single-spa 的思路,而是借鉴了 WebComponent 的思想,通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 webComponents 组件,从而实现微前端的组件化渲染。
特点
- 使用 webcomponet 加载子应用相比 single-spa 这种注册监听方案更加优雅;
- 复用经过大量项目验证过 qiankun 的沙箱机制也使得框架更加可靠;
- 组件式的 api 更加符合使用习惯,支持子应用保活;
- 降低子应用改造的成本,提供静态资源预加载能力;
不足
接入成本较 qiankun 有所降低,但是路由依然存在依赖;(虚拟路由已解决)多应用激活后无法保持各子应用的路由状态,刷新后全部丢失;(虚拟路由已解决)- css 沙箱依然无法绝对的隔离,js 沙箱做全局变量查找缓存,性能有所优化;
- 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
- 对于不支持 webcompnent 的浏览器没有做降级处理;
补充:或许很多小伙伴对Web Components不是很了解,它是由google推出的浏览器的原生组件,MDN对Web Components的定义是这样的:
它的三项主要技术是指:
- Custom elements(自定义元素) :一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM(影子DOM) :一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates(HTML模板) :
<template>和<slot>元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
EMP 方案
EMP是由欢聚时代业务中台自主研发的最年轻的单页微前端解决方案
- 基于
Webpack5的新特性Module Federation实现,达到第三方依赖共享,减少不必要的代码引入的目的,什么是Module Federation这里就不再赘述。 - 每个微应用独立部署运行,并通过cdn的方式引入主程序中,因此只需要部署一次,便可以提供给任何基于
Module Federation的应用使用。并且此部分代码是远程引入,无需参与应用的打包。 - 动态更新微应用:
EMP是通过cdn加载微应用,因此每个微应用中的代码有变动时,无需重新打包发布新的整合应用便能加载到最新的微应用。 - 去中心化,每个微应用间都可以引入其他的微应用,无中心应用的概念。
- 跨技术栈组件式调用,提供了在主应用框架中可以调用其他框架组件的能力(目前已支持互相调用的框架及使用方式请参阅官方文档)。
- 按需加载,开发者可以选择只加载微应用中需要的部分,而不是强制只能将整个应用全部加载。
- 应用间通信,每一个应用都可以进行状态共享,就像在使用npm模块进行开发一样便捷。
- 生成对应技术栈模板,它能像
create-react-app一样,也能像create-vue-app一样,通过指令一键搭建好开发环境,减少开发者的负担。 - 远程拉取ts声明文件,
emp-cli中内置了拉取远程应用中代码声明文件的能力,让使用ts开发的开发者不再为代码报错而烦恼。
特点
- webpack 联邦编译可以保证所有子应用依赖解耦;
- 应用间去中心化的调用、共享模块;
- 模块远程 ts 支持;
不足
- 对 webpack 强依赖,老旧项目不友好;
- 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
- 子应用保活、多应用激活无法实现;
- 主、子应用的路由可能发生冲突;
无界方案
无界微前端方案基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。