3.3.3 基于 Web Components 的微前端
前面方法的一个变化是,每个微型前端定义一个HTML
自定义元素供容器实例化,而不是定义一个全局函数供容器调用。
Web Components
是一种浏览器提供的标准,可以将页面组件化并进行复用。在微前端中,可以使用 Web Components
来封装页面组件,并在需要的地方动态加载和渲染。
<html> <head> <title>Feed me!</title> </head> <body> <h1>Welcome to Feed me!</h1> <!-- These scripts don't render anything immediately --> <!-- Instead they each define a custom element type --> <script src="https://browse.example.com/bundle.js"></script> <script src="https://order.example.com/bundle.js"></script> <script src="https://profile.example.com/bundle.js"></script> <div id="micro-frontend-root"></div> <script type="text/javascript"> // These element types are defined by the above scripts const webComponentsByRoute = { '/': 'micro-frontend-browse-restaurants', '/order-food': 'micro-frontend-order-food', '/user-profile': 'micro-frontend-user-profile', }; const webComponentType = webComponentsByRoute[window.location.pathname]; // Having determined the right web component custom element type, // we now create an instance of it and attach it to the document const root = document.getElementById('micro-frontend-root'); const webComponent = document.createElement(webComponentType); root.appendChild(webComponent); </script> </body> </html>
这里的最终结果与前面的例子很相似,主要区别在于你选择了 "Web Components
的方式" 来做事。如果你喜欢网络组件规范,并且喜欢使用浏览器提供的功能的想法,那么这是一个很好的选择。如果你喜欢在容器应用程序和微型前端之间定义你自己的接口,那么你可能更喜欢前面的例子。
优点:
- 独立性强:
Web Components
是自包含的,可以将应用组件化并进行复用,从而提高了应用的可维护性和可复用性。 - 跨框架:
Web Components
可以跨越多个前端框架进行使用,因为它们只是一些自定义元素和样式。
缺点:
- 兼容性问题:
Web Components
目前还没有得到所有浏览器的支持,需要使用polyfill
进行兼容处理。 - 性能问题:
Web Components
的初始化和渲染需要消耗较多的时间和内存资源,可能会影响应用的性能。
3.3.4 基于主应用和子应用之间的通信机制的微前端
在微前端中,可以使用 JavaScript
通信机制,如 postMessage
和 CustomEvent
等,来实现主应用和子应用之间的数据共享和交互。可以将不同的子应用封装成 JavaScript 模块
,并在主应用中动态加载和运行。
主应用代码:
import { registerApplication, start } from 'single-spa'; registerApplication( 'sub-app', () => import('sub-app'), location => location.pathname.startsWith('/sub-app') ); start();
子应用代码:
import { registerApplication, start } from 'single-spa'; export function bootstrap() { // 初始化子应用 } export function mount() { // 渲染子应用 } export function unmount() { // 卸载子应用 } registerApplication( 'sub-app', () => import('./app.js'), () => location.pathname.startsWith('/sub-app') ); start();
在主应用中,我们使用 registerApplication
函数注册子应用,指定子应用的名称、加载子应用的函数和一个谓词函数,用于确定何时加载子应用。然后,我们调用 start
函数来启动应用程序。
在子应用中,我们首先导出了 bootstrap
、mount
和 unmount
函数,这些函数分别用于初始化、渲染和卸载子应用。然后,我们使用 registerApplication
函数来注册子应用,指定子应用的名称、加载子应用的函数和一个谓词函数,用于确定何时加载子应用。最后,我们调用 start
函数来启动子应用。
在子应用渲染后,子应用可以通过 single-spa
库提供的全局 window.System.import
函数来加载其他子应用,并通过自定义事件、postMessage
或者共享状态等方式进行通信。
优点:
- 灵活性强:主应用和子应用之间的通信可以非常灵活,可以根据需要进行定制化。
- 可扩展性强:主应用和子应用之间的通信可以使应用更容易进行扩展和升级。
- 性能较好:通信机制可以使应用之间的数据传输
缺点:
- 开发成本较高:需要开发者对通信机制有一定的了解,并且需要在主应用和子应用之间进行协调,从而增加了开发成本。
- 可维护性较差:通信机制的复杂性可能会导致代码难以维护,特别是在应用数量较多的情况下。
- 安全性问题:通信机制需要进行跨域通信,可能会存在一些安全风险。
3.3.5 基于 Webpack 5 模块联邦实现微前端
Webpack 5
模块联邦是一种新的技术,它允许多个独立的 Webpack
构建之间共享代码。这意味着您可以将应用程序分解为多个微前端,每个微前端都可以独立地开发和部署,并且它们可以在运行时动态加载和共享代码。下面是一个示例微前端应用程序,其中每个微前端都有其自己的 Webpack 构建,并使用模块联邦来共享核心代码。
首先,我们需要使用 Webpack 5
的 ModuleFederationPlugin
插件。这个插件可以让我们将不同的 Webpack
构建之间的模块共享。
我们可以创建一个微前端核心代码的仓库,然后在这个仓库中定义需要共享的模块。
// webpack.config.js const { ModuleFederationPlugin } = require('webpack').container; module.exports = { entry: './src/index.js', output: { publicPath: 'http://localhost:3001/', }, plugins: [ new ModuleFederationPlugin({ name: 'core', filename: 'remoteEntry.js', exposes: { './Header': './src/components/Header', './Footer': './src/components/Footer', }, }), ], };
在这个示例中,我们将模块名设置为“core”
,并将组件 Header
和 Footer
暴露出来,这样其他微前端应用就可以使用这些组件。
现在,我们可以在其他微前端应用中引入这些组件。
// webpack.config.js const { ModuleFederationPlugin } = require('webpack').container; module.exports = { entry: './src/index.js', output: { publicPath: 'http://localhost:3002/', }, plugins: [ new ModuleFederationPlugin({ name: 'app1', remotes: { core: 'core@http://localhost:3001/remoteEntry.js', }, }), ], };
在这个示例中,我们将远程模块“core”
定义为一个远程仓库,并指定了它的 URL
。然后我们就可以在应用程序代码中使用这些组件。
// app.js import React from 'react'; import ReactDOM from 'react-dom'; import Header from 'core/Header'; import Footer from 'core/Footer'; const App = () => ( <div> <Header /> <h1>Hello, World!</h1> <Footer /> </div> ); ReactDOM.render(<App />, document.getElementById('root'));
通过这种方式,我们可以使用 Webpack 5
模块联邦技术,将不同的微前端应用程序连接在一起,并共享代码。这可以使我们更容易地构建和维护微前端应用程序,而无需处理复杂的代码复制和管理。
优点:
- 集成化程度高:基于
Webpack 5
模块联邦,可以实现应用的代码共享和复用,避免了代码的冗余,同时也能集成化地管理应用之间的依赖关系和版本控制。 - 高性能:
Webpack 5
模块联邦支持动态加载和卸载,从而提高了应用的性能和加载速度。同时也能够做到在运行时动态加载和更新依赖,降低了应用的耦合度。 - 可扩展性强:
Webpack 5
模块联邦能够实现多个应用之间的共同开发和部署,避免了应用之间的依赖冲突,使得应用更容易进行扩展和升级。 - 集成多种方案:
Webpack 5
模块联邦不仅支持基于Web Components
、iframe
、模块化加载器
、主应用和子应用之间的通信机制
等多种微前端实现方案,还可以与其他微服务框架进行集成。
缺点:
- 学习成本较高:使用
Webpack 5
模块联邦实现微前端,需要开发者具备Webpack
相关的知识和技能,增加了开发的门槛。 - 对项目架构和部署有一定要求:需要针对项目的架构和部署进行一定的调整和优化,才能更好地使用
Webpack 5
模块联邦。
无论哪种实现路径,微前端都需要考虑一些关键问题,如应用之间的路由、状态管理、数据共享等。
4、微前端框架
4.1 Single-spa
single-spa 是最早的微前端框架,兼容多种前端技术栈。Single SPA
将自己定义为 “前端微服务的 Javascript 框架”
。简而言之,它将生命周期应用于每个应用程序。每个应用程序都可以响应 url 路由事件,并且必须知道如何从 DOM 引导、装载和卸载自身。传统 SPA 和单 SPA 应用程序之间的主要区别在于它们必须能够与其他应用程序共存,并且它们没有各自的 HTML 页面。
single-spa
是一个将多个单页面应用聚合为一个整体应用的JavaScript
微前端框架。
single-spa 宣称自己是一个用于前端微服务的 JavaScript
路由器。single-spa
的路由最好的一点是,你不需要使用一个特定的JavaScript库或框架。相反,你可以用它来在同一个页面上运行 Angular、React、Vue
和其他框架进行同步开发,最后使用一个公用的路由去实现完美的切换。
这个工具是你在使用Micro frontand开发时,在每个应用生命周期中需要应用的。
每个应用程序都必须能够响应URL路由事件,并且必须能够从DOM启动、挂载和卸载。
优点:
- 敏捷性 - 独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
- 技术栈无关,主框架不限制接入应用的技术栈,微应用具备完全自主权;
- 增量升级,在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
- 更快交付客户价值,有助于持续集成、持续部署以及持续交付;
- 维护和
bugfix
非常简单,每个团队都熟悉所维护特定的区域;
缺点:
- 无通信机制
- 不支持
Javascript
沙箱 - 样式冲突
- 无法预加载
推荐阅读:
4.2 Qiankun
Qiankun 建立在 single-spa
的基础上,提供更多的功能和开箱即用的API,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。它可以和任何 JavaScript
框架一起使用,而且和 single-spa
一样,可以让你在同一个页面上混合使用各种框架。
当你有一个具有不同技能的开发团队,并希望保持项目简单时,选择这个选项是有意义的。
风格隔离有助于防止多个JS框架之间的冲突。然而,由于混合 JavaScript
框架有可能导致冲突,乾坤给你一个JS沙盒,让你在向用户发布之前测试子应用。
优点:
- 基于
single-spa
封装,提供了更加开箱即用的 API。 - 技术栈无关,任意技术栈的应用均可 使用/接入,不论是
React/Vue/Angular/JQuery
还是其他等框架。 HTML Entry
接入方式,让你接入微应用像使用iframe
一样简单。- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了
@umijs/plugin-qiankun
供 umi 应用一键切换成微前端架构系统。 - 社区较为活跃,维护者也较多,有问题会及时得到响应;
缺点:
- 可能对一些
jQuery
老项目支持性不是特别好; - 安全和性能可能会有影响,具体取决于项目;
- 对
eval
的争议,eval函数的安全和性能是有一些争议的
4.3 Micro-App
京东推出的 Micro App 是基于 Web Components
原生组件进行渲染的微前端框架。
在micro-app
之前,业内已经有一些开源的微前端框架,比较流行的有2个:single-spa
和qiankun
。
single-spa
是通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa
要求子应用修改渲染逻辑并暴露出三个方法:bootstrap
、mount
、unmount
,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。因为qiankun
是基于single-spa
进行封装,所以这些特点也被qiankun
继承下来,并且需要对webpack配置进行一些修改。
micro-app
并没有沿袭single-spa
的思路,而是借鉴了 WebComponents
的思想,通过 CustomElement
结合自定义的 ShadowDom
,将微前端封装成一个类 WebComponents
组件,从而实现微前端的组件化渲染。并且由于自定义 ShadowDom
的隔离特性,micro-app
不需要像single-spa
和qiankun
一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改 webpack
配置,是目前市面上接入微前端成本最低的方案。
我们将所有功能都封装到一个类 WebComponent
组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。
同时micro-app
还提供了js沙箱
、样式隔离
、元素隔离
、预加载
、数据通信
、静态资源补全
等一系列完善的功能。
micro-app
没有任何依赖,这赋予它小巧的体积和更高的扩展性。
为了保证各个业务之间独立开发、独立部署的能力,micro-app
做了诸多兼容,在任何技术框架中都可以正常运行。
主要特点:
- 简单上手
- 无关技术栈:任何框架皆可使用;
- 静态资源补全;
- JS沙箱;
- 样式隔离;
- Qiankun 微前端框架的优势他都有;
4.4 无界
腾讯推出的无界,也是一款基于 Web Components + iframe
的微前端框架,具备成本低、速度快、原生隔离、功能强等一系列优点。
无界的优势:
- 多应用同时激活在线:框架具备同时激活多应用,并保持这些应用路由同步的能力
- 组件式的使用方式:无需注册,更无需路由适配,在组件内使用,跟随组件装载、卸载
- 应用级别的 keep-alive:子应用开启保活模式后,应用发生切换时整个子应用的状态可以保存下来不丢失,结合预执行模式可以获得类似
ssr
的打开体验 - 纯净无污染
- 无界利用
iframe
和webcomponent
来搭建天然的js
隔离沙箱和css
隔离沙箱 - 利用
iframe
的history
和主应用的history
在同一个top-level browsing context来搭建天然的路由同步机制 - 副作用局限在沙箱内部,子应用切换无需任何清理工作,没有额外的切换成本
- 性能和体积兼具
- 子应用执行性能和原生一致,子应用实例
instance
运行在iframe
的window
上下文中,避免with(proxyWindow){code}
这样指定代码执行上下文导致的性能下降,但是多了实例化iframe
的一次性的开销,可以通过 preload 提前实例化 - 体积比较轻量,借助
iframe
和webcomponent
来实现沙箱,有效的减小了代码量
- 开箱即用:不管是样式的兼容、路由的处理、弹窗的处理、热更新的加载,子应用完成接入即可开箱即用无需额外处理,应用接入成本也极低
4.5 Webpack Module Federation
Module Federation是 Zack Jackson
发明的一种 JavaScript
架构,后来他提议为它创建一个 Webpack
插件。Webpack
团队帮助将该插件引入了已经发布的 Webpack 5
。
模块联邦专注于微型前端,但并不局限于这些框架,从而给开发者带来很大的灵活性。这是处理代码依赖性问题的最有效工具之一,它允许通过依赖性共享来扩大捆绑规模。
例如,你可以使用模块联盟通过分块加载操作来加载远程模块。这个过程让所有的东西都从一次往返服务器的过程中并行加载。
简而言之,Module Federation
允许 JavaScript
应用程序在运行时从另一个应用程序动态导入代码。该模块将构建一个独特的 JavaScript
入口文件,其他应用程序可以通过设置 Webpack
配置来下载该文件。
它还通过启用依赖共享解决了代码依赖和增加包大小的问题。例如,如果您正在下载 React
组件,您的应用程序将不会导入 React
代码两次。该模块将巧妙地使用您已有的 React 源代码,并且只导入组件代码。最后,您可以使用 React.lazy
和 React.suspense
在导入代码由于某种原因失败时提供回退,确保用户体验不会因为构建失败而中断
其他使模块联盟有用的关键概念包括:
- 与环境无关的功能,可以在
Web
和Node.js
上发挥作用。 - 防止同级别的容器覆盖其模块的控制。
- 支持 webpack 使用的所有模块类型。
可以通过投票来影响模块联盟团队优先考虑的功能,使其成为一个面向社区的项目,朝着大多数专家认为有益的方向发展。
推荐阅读:
4.6 Medusa
medusa 是一款基于各种框架之上的微前端框架,具有极高的框架兼容性,能够运行在目前主流的几种微前端框架之上。使得乾坤、飞冰、京东微前端以及next.js能够同时运行在一个环境下。并全部采用proxy
的沙箱方案,摒弃老旧浏览器的兼容性问题,完全隔离主应用与微应用。
特性:
- 不限制使用的前端框架
- 兼容
qiankun
、飞冰、京东等微前端框架,可以直接加载,不需要任何改动 - 支持服务端渲染模式的直接使用
- 支持加载比较流行的服务端渲染框架
next.js
- 支持作为独立微前端框架来使用
- 以React组件的生命周期作为微应用的生命周期
美杜莎的使用场景十分广泛,大部分的前端框架都能无缝接入:
- 把
next.js
的应用接入到微前端体系框架中:目前next.js
服务端渲染框架的应用场景十分广泛。同时也没有一款微前端框架能够把next.js
框架集成进来的。 - 框架无关(我们还支持乾坤、飞冰、京东微前端框架):很多时候我们会遇到这样一个问题,公司的部门很多,由于各种原因,不同部门应用了不同的微前端框架。后面想做整合,却发现改造成本较大。美杜莎就解决了这样的一个问题,我们可以保持原先应用保持不变,只替换基座的框架,就能使这几种微前端应用运行在同一个页面上
- 多层级应用嵌套:理论上只要浏览器撑得住,就可以无限嵌套下去
4.7 Bit
Bit 是一个可用于构建微前端的产品解决方案,在国外比较知名。除了创建的能力,Bit
还提供了通过独立组件管理前台的可能性。
当涉及到确保每个前端得到它自己的中立和快速的构建过程时,Bit
提供了一个特殊的 CI/CD
过程,是完全组件驱动的。这意味着,不同的团队可以自由地纳入变化,没有任何延迟,以创建一个统一的项目。整个工作流程通过简单的解耦代码库、小型定义的 API
、独立的发布管道和独立的团队得到加强。
尽管微前端通常被认为是在运行时发生的组合,但Bit 允许开发人员在构建时有效地组合前端以享受两全其美:“传统单体”的安全性和健壮性以及微前端的简单性和可扩展性.
推荐阅读:
4.8 SystemJS
SystemJS 实际上并不算是微前端框架,但它确实提供了独立模块的跨浏览器管理解决方案,这是实现 MF
的关键(实际上也被 single-spa
使用)。
SystemJS
可以 转译生产工作流代码,以接近原生的速度运行。运行时测试显示,未缓存的本地模块加载时间为 1668ms
,而 SystemJS
为 2334ms
。
缓存的本地模块加载时间为 49ms
,而 SystemJS
的加载时间为 81ms
。在这样的速度下,人类用户不会发现其中的差别。
尽管速度快,但 SystemJS
为你提供了大量的功能来执行任务,如重新加载工作流、加载全局脚本和导入地图。
SystemJS
让我们可以使用不同的JS模块相关功能,如导入地图和动态导入,而不需要本地浏览器的支持。
SystemJS
可以被认为是 JS 模块的编排器。它允许我们使用不同的 JS 模块相关功能,例如动态导入、导入映射等,而不依赖于本机浏览器支持——以及所有这些,具有接近本机的性能。一些值得注意的功能包括适用于旧版浏览器的 Polyfill
、使用名称的模块导入(通过将名称映射到路径)以及对多个 JS 模块的单个网络请求(通过使用其 API 将多个模块设置为单个文件)。
SystemJS
的一些突出功能包括:通过将名称映射到路径来促进模块的导入,为老式浏览器提供 Polyfill
的能力,以及利用其API为多个JS模块执行单一的网络查询,在一个文件格式中设置多个模块的能力。
4.9 Piral
Piral 对于那些期待使用微型前端来构建门户应用的人来说是一个伟大的工具。解耦模块被称为 Pilets
,它允许你创建一个模块化的前端应用程序。
我们应该知道,一个 Pilets
是单独开发的,并带有微型前端开发所需的属性。
通过将开发生命周期分成两半,Piral能够处理整个过程。
你通常从应用程序的外壳开始,从模板开始,通过调试、构建和发布进行。然后,在调试、创建和发布之前,在各个模块(Pilets
)上工作,照顾到基础设施。
像许多顶级的微型前端框架一样,Piral是开源的,并且是社区驱动的。但与很多开源工具不同的是,你可以为Piral
的支持付费,这将改善你的Web开发工作流程。
你可以得到一个强大的框架,但你也可以在需要时得到专业帮助。
4.10 Luigi
Luigi 是一个模块化的顶级微前端框架,它可以帮助你分解你的单体网络应用,为精简重建做准备。它使一个Web应用能够直接与由其组成的微前端进行通信。
为了使这种通信无痛地运行并避免任何故障或缺陷,可以在路由、导航、用户体验元素和授权方面进行设置配置。
这个JavaScript微前端框架允许你创建一个管理用户界面驱动的本地和分布式视图。
企业级的框架被小型企业和跨国公司使用。它的不可知技术意味着你永远不会被锁定在一个特定的服务器、供应商或编码语言。
4.11 Open Components
Open Components 项目宣布其目标是“前端世界中的无服务器”。更具体地说,OC 的目标是成为微前端的框架,在一个地方提供你需要的一切,使其成为一个丰富而复杂的系统,包括从组件处理到注册表到模板甚至 CLI 工具的任何工具。OpenComponents
有两个部分:
components
是主要由html、javascript、css
组成的同构代码的小单元。它们可以选择包含一些逻辑,允许服务器端 node.js 应用程序组成用于呈现视图的模型。呈现后,它们是纯 html 片段,可以注入任何 html 页面。consumers
是网站或微型网站(小型可独立部署的网站,全部通过前门服务或任何路由机制连接),需要组件来在其网页中呈现部分内容。看看这里:
4.12 FrintJS
FrintJS 是“一个用于构建可扩展和反应式应用程序的模块化 JavaScript
框架”。它允许您加载来自单独的捆绑器的应用程序,为您的应用程序提供一个结构,并处理路由、依赖关系等问题的真实性。该项目还通过附加包支持 RN
和 Vue
,但它主要针对 React
进行了记录和测试。
在此处 可以试用或访问 GitHub 上的项目以了解更多信息。