本文译者为 360 奇舞团前端开发工程师
原文标题:How Vercel adopted microfrontends
原文作者:Mark Knichel、 Dan Fein、Brian Emerick
原文地址:https://vercel.com/blog/how-vercel-adopted-microfrontends
了解 Vercel 如何通过微前端缩短构建时间并提高开发者的效率,同时保持良好的用户体验。
Vercal 的主网站曾是一个大型的 Next.js 应用程序,服务于我们的网站访问者和登录后的仪表板。然而,随着 Vercal 的增长,这种设置暴露出一些需要改进的地方。构建时间变长,依赖管理变得更加复杂,工作流程也需要优化。即使是小改动也会触发完整的构建过程,影响独立开发和持续集成(CI)管道。
很明显,需要做出改变。
通过重新思考我们的架构,我们转向了垂直微前端,这带来了更简单的开发体验,并且预览构建时间和本地开发编译时间提高了40%以上。通过移除其他微前端的代码来简化依赖关系,减轻了页面重量,提升了最终用户的性能,特别是在核心网络指标如最大内容绘制(LCP)和交互到下一个绘制(INP)方面有所改善。
基于 Vercal 对微前端的支持,这次重构不仅改善了开发者体验(DX),还揭示了进一步优化的领域。通过大规模应用程序迁移的实际应用,使平台对所有用户更加流畅。这篇博客分享了我们的操作过程,但也展示了我们可以如何使整个过程更加顺畅。随着我们不断改进,我们也在使单一应用程序的构建更快——在不牺牲最终用户体验的前提下,进一步提升开发者体验。
让我们来看看我们是如何做到这一点的。
利用 Turbo
我们的第一步是专注于改进本地编译、构建时间和持续集成(CI)工作流。Vercel 的 monorepo 使用了 Turborepo,这使得我们可以通过 Vercel 远程缓存和 --affected 等功能来优化任务。采用 Turbopack 在本地开发中提供了显著的速度提升。
然而,当我们进行这些改进时,我们意识到单个应用程序模型对于 Vercel.com 来说也不再必要。应用程序内部的逻辑分割意味着我们可以避免在每次构建和编译时做不必要的工作,从而带来更大的开发者效率提升。
权衡水平拆分或垂直拆分
微前端提供了一种将大型应用程序分解为更小、独立单元的方法,允许跨产品领域和工程团队并行开发。虽然单个应用程序具有凝聚力,但随着应用程序和组织的增长,微前端有助于解决扩展问题。主要有两种策略:
垂直微前端:按路径拆分,每个页面由一个单独的应用程序处理
水平微前端:按功能拆分,多个应用程序在同一页面上运行
微前端的两种常见方法:单个页面中的多个微前端(水平拆分)或让单个微前端管理整个页面(垂直拆分)
没有绝对正确的答案,每种方法都有其取舍。根据使用场景,每种方法都可以带来显著的好处。Luca Mezzalira 关于微前端的书籍是这方面的一个很好的资源。
垂直拆分提供了凝聚力,但在不同微前端支持的应用程序之间切换时可能会导致硬导航。技术如预加载和像 Chromium 的预测规则这样的 API 可以有所帮助,但它们也有限制,比如增加资源使用和有限的浏览器支持。
对于水平拆分,测试、发布、监控和调试会变得更加复杂,因为多个微前端在同一页面上运行。
我们采用了垂直微前端——将应用程序拆分为用户很少跨越的逻辑部分——以减少构建时间并简化依赖关系,同时保持统一的单一代码库。
前期的测试显示,这种方法可以将构建时间减半,并在开发和生产过程中简化所有权,使开发人员感觉像是在处理一个单一的应用程序。
我们的迁移路径
下一步是将我们的单体前端应用程序迁移到更小的垂直应用中,而不会中断开发或用户体验。我们将应用程序分解为三个核心区域:
- 登录后的面板(logged-in dashboard)
这些区域自然是有区别的。用户很少在这几个区域之间切换,而且它们各自维护着不同的用户界面,这使得它们非常适合分离。
用户在单个微前端中体验到软导航,而在微前端之间移动时体验到硬导航
由于我们使用了 Next.js,因此我们利用了 Next.js 的多区域(Multi-Zones)功能,该功能支持垂直微前端架构。
逐步迁移是我们优先考虑的策略。我们选择创建全新的应用程序,而不是 fork 原始代码库。虽然 fork 在表面上看起来更简单,但它会引入一些风险。共享组件(如头部、底部和设计系统)需要在多个代码仓库中进行复制和维护,这会导致同步问题和潜在的不一致性。
通过继续使用单体仓库(monorepo),并将代码集中到单体仓库中的各个包内,我们确保了共享组件的一致性,同时仍然能够轻松地逐步拆分关键区域。工具如 Turborepo 和 Dependency Cruiser 有助于简化这一过程。
总结一下,我们的做法包括:
1.使用 Next.js 的多区域功能来支持垂直微前端架构。
2.优先进行逐步迁移,而不是 fork 原始代码库。
3.继续使用单体仓库,并将代码集中到单体仓库内的各个包中。
4.使用 Turborepo 和 Dependency Cruiser 等工具来简化管理和维护。
Vercel 的 monorepo 在 “apps” 文件夹中由独立的微前端构建,同时共享包被提升出来以实现跨所有应用程序的无缝访问
通过使用特性标志(feature flags),我们能够逐步将流量从仍在运行的单体前端应用引导至新的微前端。这种方法使我们能够最小化风险,并在移除旧代码之前验证性能改进的效果。当新页面已经无错误地服务了一周以上的流量后,我们将旧代码从现有应用中移除,逐步减少依赖关系和构建时间。
译者注:feature flags是软件开发中常见的一种技术,更合理的翻译是特征切换,功能开关等,本质是一个隐藏在业务代码里面的一个控制逻辑。触发某个条件后就切换某种功能,常见场景就是灰度测试的时候。
在增量迁移期间,该页面同时存在于原始前端整体和新的微前端中。feature flags控制路由,直到微前端版本完全上线
通过这种单体仓库设置和垂直拆分的方法,我们感到非常自信并掌控了整个过程。我们能够快速推进工作,同时保持一致性,减少构建时间,并使应用程序开发变得简单。
经验教训和权衡
在成功采用微前端架构为 Vercel.com 提供支持之后,我们将同样的方法扩展到了其他网站,比如 2024 年 Next.js 大会页面,该页面作为一个独立的应用程序运行,与 nextjs.org 分离。借助这种基础设施,我们可以将不同的区域提取为独立的应用程序,加快开发周期,因为 Next.js 大会团队和 Next.js 开发团队可以快速且独立地迭代。
然而,过程中也遇到了一些挑战。我们遇到了一些问题,并努力确保性能不会下降,特别是使用像 Speed Insights 这样的工具来监控实际使用情况,以及使用 Vercel 工具栏来监控布局偏移和交互时机。
我们很早就意识到,在本地和预览环境中测试微前端存在挑战。Draft 模式等功能无法正常工作,性能瓶颈,如硬导航,也是已知问题。为了应对这些问题,我们采用了诸如使用 Chromium 的特性进行prefetch和预渲染等策略。
权衡的一个例子:
为了减少硬导航的影响,我们首先会在链接变得可见时下载初始页面的资源(JavaScript 和 CSS),以便预先填充浏览器缓存。这意味着当用户导航时,这些资源可以从缓存中加载。然而,页面仍然需要渲染并获取剩余的资源,例如 HTML。
我们进一步采取措施,在用户互动时(例如点击链接)对页面进行预渲染。这会在后台渲染页面,包括所有网络请求和处理,因此当用户到达页面时,感觉几乎是瞬间完成的。这种方法在性能和资源使用之间取得了平衡——只在适当的时间进行prefetch和预渲染,以避免过度占用设备资源,但仍能提供理想的体验。
页面在用户交互时预渲染或prefetch,提供流畅的导航,同时优化性能和资源使用。
展望未来
随着迭代速度的提升,我们将加倍努力在整个平台范围内增强微前端的功能。我们的重点在于优化路由、简化预览工作流以及优化硬导航性能。我们还致力于提升工作台体验,并解决工作流挑战,从而从头到尾改善微前端开发。
转向垂直微前端极大地提高了开发者的效率,并缩短了构建时间。团队现在可以更加独立地工作,同时通过共享包保持一致,创建了一个既能平衡自主性又能实现统一开发的系统。
随着我们继续完善这一架构,我们对其潜力感到非常兴奋。无论您是在探索微前端,还是坚持单个应用程序的开发,Vercel 都致力于确保最佳的开发者体验。