微前端:我的经验教训

2025-05-28

微前端:我的经验教训

你好呀!

在过去 6 个月的工作中,我一直忙于分析、实验、测试,有时甚至承受痛苦,同时探索微前端作为一种架构决策的深度、优点和缺点。

我将要描述的一切,以及我将要表达的所有观点,都来自对已投入生产的代码的深入分析。我对微前端没有任何立场。只是因为我坚信,当你遇到问题时,软件架构解决方案总是好的(因此,它就是解决方案!);而当你遇到问题时,仅仅为了集成软件并不需要的创新而追随潮流,它总是坏的。

关键在于,在真正需要的时候使用架构模式,而不是为了跟风或听上去很流行。微前端有很多非常有效的用例,它们可能是最佳解决方案。

这篇文章很长,所以请喝杯咖啡、茶或分段阅读。

微前端起源于什么时候?

微前端是前端模拟后端的微服务。

基本上,几年前,随着数字化转型的爆炸式增长(企业,特别是大型组织迁移到网络来运营业务),网络软件系统开始变得越来越大,成为包含一切的庞大整体。

在某个时候,该架构开始变得臃肿和难闻,架构师和开发人员想出了不同的方法和策略来分解代码和团队,并有专门的发布周期。

微服务

单体架构并不总是坏的,但有时很难管理

当你拥有一个非常庞大的系统,其各个部分紧密耦合,这意味着它们相互依赖,无论你对某个部分进行什么更改、升级、更新、修复等,无论多么小,都需要你将整个单体应用发布到更高的版本。这也会影响到所有依赖于该单体应用各个部分的内容,例如子项目。

即使使用语义版本控制来控制依赖关系,即使进行模块化,即使我们将团队分开并赋予他们某些部分的所有权,开发和维护大型耦合系统也是一项非常具有挑战性的任务。

这些是微服务架构类型背后的根本原因:希望将大型系统拆分成多个部分,使每个组件和负责的团队在许多方面都保持独立。

  • 技术
  • 生命周期
  • 流程
  • 运营

特别是与事件驱动策略、API 优先概念和云平台相结合时,微服务已被证明可以解决上述一些问题,但由于需要复杂且精心设计的编排,因此也引入了自己的问题。

前端微服务

如果分离后端成功了,为什么分离前端却不行呢?

实际上,后端的分裂,有专门的团队来负责某项功能或服务,要求我们在前端采取自己的措施。

微前端

如果我们真的想确保这些组件完全独立,那就别无选择。这就是微前端的诞生。

微应用

说实话,在企业中这并不是什么新鲜事。我们已经这样做了很多年了。

我们可以通过两种方式来实现这一点:每个超链接或 URL 都有一个单页应用程序,或者我们在页面区域中嵌入一个功能齐全的微应用程序,通常使用 iframe。

微应用

这种方法的优点是

  • 开发应用程序的团队可以完全独立,选择自己的技术和周期
  • 应用程序完全封装在 iframe 中,因此不会干扰主机的 javascript 或 css

缺点是

  • 如果在页面(或路由)中初始化多个微应用实例,则对性能的影响可能非常大,并且可能存在依赖关系重复,因为样式或字体等共享资产无法在主机和应用之间共享
  • 如果应用程序集成到构建系统中,可能有机会共享某些依赖项,甚至可以进行一些代码拆分/摇树操作,但是,团队将失去独立性,因为他们被绑定到构建过程所在的系统周期中。
  • 路由可能会变得非常复杂,特别是当导航树非常深时
  • 用户体验整合将充满挑战。诸如本地化、国际化以及统一风格的保留等方面,将需要更多努力、协调一致,或者制定一份记录详尽的风格指南。
  • 如果通过 iframe 集成,您将需要额外的代码来处理 iframe 调整大小。由于 iframe 是完全封装的,您将无法共享任何资产
  • 如果通过 iframe 集成,则需要采取额外的 SEO 和 A11y 措施

不会改变

  • 如果你正在使用捆绑应用程序,你仍然需要更新主系统依赖版本,并且这个小变化需要发布,然后你才能看到独立团队对微应用程序所做的任何更新

Web 组件

另一种流行但尚未得到充分验证(*) 的集成微前端的方式是通过 Web Components W3C 标准。对于 Web 开发新手或从未听说过 Web Components 的人来说,Web Components 是一种 Web 标准,它允许通过 DOM 生成自定义元素(例如自定义 HTML 元素),这些元素的属性可以是自定义的,也可以是从 HTMLElement(或任何扩展的元素类)父级继承的。

也可以在 Web 组件之上构建 JavaScript 框架。许多库支持扩展功能,并提供必要的 polyfill,以确保 Web 组件能够跨浏览器支持。正如您所想象的,与任何 W3C 规范一样,Web 组件会随着时间的推移而得到全面支持,而作为 Web 组件的一部分,customElement、Shadow DOM API 等功能目前还只是略有改进。

)*当我说不太证实时,我的意思是这种模式在大型企业生产系统中成功使用的记录较少

Web 组件的优点

  • Web 组件的封装程度取决于是否使用 Shadow DOM。我想说,这既有优点也有缺点,取决于你的用例。
  • 当封装程度较低时,Web 组件可以利用通用资产,从而更容易实现更集成的用户体验和一致的外观和感觉,特别是对于企业标识而言
  • 能够使用公共和共享代码,减少通过网络传输的数据量,并防止重复,从而提高运行时的性能
  • SEO 和可访问性被视为与任何 HTML 子部分一样,因此无需额外的努力,

Web 组件的缺点

  • 如果你必须支持非常老的浏览器,那么就需要发送额外的代码来填充它们
  • 如果每个组件背后的团队都是独立的,他们可以做出自己的选择,你能预见在一个页面中引导和初始化多个框架对性能的影响吗?

页面上初始化多个框架
灾难

不会改变

  • 与微应用相同

如果你正在使用捆绑应用程序,你仍然需要更新主系统依赖版本,并且这个小变化需要重新构建、发布和部署,然后你才能看到独立团队对 Web 组件包所做的任何更新

编排,或者将整个微部分整合在一起

无论您的微部件是独立的 Web 组件还是微应用程序,您都需要以某种方式将它们整合在一起。

只有当您指定一个团队专门负责我们称之为“主持人”的工作时,这才有可能。

在大型企业系统中,主机通常由一个CMS加上一个大型基础设施组成,用于支持它。管理主机的团队应该负责编排和组合,并确保以下方面能够实现:

  • 初始化配置(特别创作!)
  • 路由
  • 组件之间的通信
  • 状态管理
  • 提供公共/共享代码
  • 任何其他用户触发的基于事件的 API 的可用性

使其由事件驱动!

无论您是将微前端作为微应用集成到 iframe 或 Web 组件中,还是将其导入到更大的应用程序中,保持良好用户体验的关键在于确保所有组件能够相互通信。您可以使用发布/订阅机制策略来实现这一点,例如这个

发布/订阅的工作方式非常简单。您发布一个主题,所有监听或订阅的用户都会以您指定的任何方式对该发布做出反应。这对于微前端来说是一种非常棒的机制,因为消息或主题的发布者不需要知道监听者。但是,请记住,订阅者显然需要知道他们需要订阅的主题。

发布/订阅机制

有非常非常轻量级的库可以实现这一点,如果我们正在引导一个框架,它可能有自己首选的技术或推荐的模式。

但是如果你正在引导不同的框架......

这时事情又变得混乱了!如果你正在引导不同的框架,而每个框架都有各自的状态管理模式和风格,那么将它们组合起来可能会特别具有挑战性。

每个组件都会定义自己的 store,我们必须想办法同步它们。在我看来,这违背了单一事实来源的初衷,我不太认同这种做法。尽管我们已经在大型应用程序中有效地集成了 Vuex 和 Redux store,但这绝非易事。

组合多种状态管理模式违背了拥有单一事实来源或唯一存储的目的

如果您要将多个部分拼接在一起,并且要处理用户身份验证/授权等,那么记住这一点非常重要。

与浏览器原生控件的反应一致性

这方面也至关重要。多个微部件对浏览器 API(例如历史记录)的行为有所不同,可能会让用户感到困惑,并导致非常糟糕的体验。每个团队都应该确保每个组件在状态变化时都发布其历史记录状态。

书签也可能成为一项挑战。通常,用户希望将某个路由或处于特定状态的页面添加到书签中。如果您要组合使用不同技术的微前端,这可能会变得非常困难。

依赖 Web API 将会有所帮助

现代的、经久不衰的浏览器提供了大量的 API,您不仅可以而且应该利用它们来执行组合。即使并非所有浏览器都支持所有 API,或者有些浏览器只提供部分支持,它们在减少开发工作量、简化组合抽象以及使用全局可用的方法方面仍然非常有用。

Web 或浏览器 API

一些用于微前端组合的最有用的 API 和接口是:

  • 突变观察者
  • 路口观察员
  • 历史
  • 渠道消息
  • 拖放
  • 画中画
  • 推送 API(^)
  • 服务工作者和 WebSocket

当然还有所有客户端存储机制和缓存(^)

(^)这些仍处于实验阶段,您可以在这里找到它们。

测试部分

显然,单独测试微前端组件应该由其所属团队负责。但是,如果您将其集成到页面中,并且希望它与页面的其他部分进行通信和同步状态,那么您也需要测试该聚合。

为那些对你来说像黑盒子一样的东西设计自动化测试绝非易事。所以,即使这部分不是问题,也会是另一个挑战。

所以您不推荐微前端?

再说一遍,不是。我不是这个意思。我相信微前端才是出路,尤其是在以下情况下:

  • 你必须整合在不同环境下工作的团队的开发(外部存储库、不同的组织等)
  • 每个部分都有非常不同的周期(例如功能 A 需要每日发布,功能 B 可能需要一些维护),并且您不想因为单个(或几个,但最小)功能的缺陷而重新部署整个系统

为了实现这一点,您需要...

...至少

  • 非常可靠的事件驱动策略
  • 负责编排、路由和公共资源的团队
  • 一份记录详尽、定义明确的风格指南
  • 性能预算和加速负载(特别是感知负载)的机制。

特别是在企业界呢?

在我看来,微前端架构对于产品来说非常有用。

假设你拥有一个包含多种不同功能的应用程序。假设它是一个搜索引擎。你可以在前端将其分解成如下组件:

  • 表单元素(输入、按钮、复选框、单选按钮等)
  • 原子文本元素(链接、标签、描述)
  • 图像
  • 预告片
  • 分页
  • 延迟加载机制(用于图像和无限滚动或加载更多)

您需要后端服务来针对索引运行查询并返回结果。

当然应该有一个(前端)构建系统、一个状态管理系统或发布/订阅模块、一个统一的 API/服务来从前端运行查询。

利用它们,您可以组合搜索框、方面或过滤器、搜索结果、相关结果、特色结果,甚至定制建议,并结合分析或目标层。

我认为最重要的是,这些功能中的每一个都映射到业务能力或领域

您可以轻松形成集群并将每个集群分配给一个独立的团队。

您很可能希望用户登录并保存他们的查询。您会有一个团队负责身份验证和授权,他们还会使用其他团队的组件(例如表单元素)。

搜索页面

所有这些团队仍将在风格系统的指导下工作,以保留用户体验和产品的特性。

但企业网站环境又如何呢?

但企业网站通常不是一个产品,而是一个由创建网页的组件组成的网页,用于提供信息或获取产品和服务。

虽然你可以将每个组件分配给不同的团队负责,但团队配置不太可能允许这样做。而且业务战略也不太可能支持这种拆分。

通常,企业站点的团队规模不会很大,以至于无法组建一个完全跨职能的团队来负责一些较小的功能。即使可以,有时也不太合理。

网站背后通常只有一个业务部门。他们负责产品或服务的广告或商业化,所有组件在技术和战略上都紧密耦合。

规则可能存在例外。有些服务你仍然可以从整体中分离出来,分配给独立的团队负责,比如我们之前提到的用户管理,甚至是我们建议的搜索机制。

但更有可能的是,企业平台拥有一个组件库或存储库,供多个网站使用。搜索功能中使用的图片组件与其他地方/组件/模块/模板中使用的组件完全相同。因此,所有组件对于网站背后的业务利益相关者都同等重要。

企业中真正有效的用例

当你需要集成由远程团队开发的微部件时,情况也是如此。我不想使用“独立”这个词,因为我仍然认为 100% 独立不会对你的用户体验和性能有任何帮助。你仍然希望以一致的 UI/UX 形式保留你的企业形象。你仍然希望页面组件之间保持状态一致性、同步和通信。你仍然希望确保在技术选择上有一定的整合性,以防止冲突和性能下降。

您仍然需要一些决策矩阵来管理和控制。作为一名关注性能的前端架构师,我不希望 Angular 9、10 和 11、Vue 2 和 3 以及 React 组件在网页中初始化,请求它们大量的附件,从而破坏用户体验(以及页面!)。

因此,在这种情况下,拥有 100% 独立、孤立的团队的想法对我来说似乎并不可行。

但是,如果一个团队致力于一个真正孤立的功能,那么肯定值得采用独立的发布周期,以避免需要重建和重新部署整个整体来对第三方组件进行小修复。

要使用该商品,您需要实现模块联合 (Module Federation)。要了解我关于模块联合的了解,请等待我的下一篇文章。

决定,决定……

这些幻灯片取自我在 HolyJS 会议上的演讲,提供了一些我创建的图形,用于帮助进行架构决策,例如基于独立部署或封装的需要。

https://slides.com/anfibicreativa/micro-frontends/fullscreen

即幻灯片 34、35 和 52 至 56。如果您位于黄色区域内,则可能需要考虑不要使用微前端。

编辑:您可以称赞我的阅读,听我与 John Papa、Ward Bell、Dan Wahlin 和 Craig Shoemaker 在他们的播客中讨论这个话题https://webrush.io/episodes/episode-113-micro-front-ends-with-natlia-venditto

祝大家节日愉快!

圣诞快乐

文章来源:https://dev.to/this-is-learning/micro-frontends-my-lessons-learned-1pcp
PREV
React Refs:完整故事 可变数据存储 带有 Refs 的可视化计时器 DOM 元素引用 组件引用 类组件引用 单向流 将数据添加到 Ref 使用 useEffect 回调 Refs useState Refs 结论
NEXT
JavaScript 中的信号