【微前端】使用微前端解决碎片化的用户界面问题实践
推荐超级课程:
@TOC
引言
在不断变化的网络开发领域,保持涉及不同团队的各种应用程序之间的连贯和无缝的用户体验需要付出巨大的努力。
本文记录了我们如何采用微前端架构来解决现有应用程序中碎片化UI的固有问题,为更敏捷、可扩展和可维护的开发和发布环境铺平了道路。
单体架构的挑战
传统的单体前端架构将所有功能捆绑到单一代码库中。这在我们一个云前端应用程序开发的初期阶段很有帮助,但后来出现了几个挑战:
代码库的复杂性:
随着应用程序的增长,代码库各部分之间的摩擦程度增加,导致出现几个问题。
应用程序的不同组件变得高度依赖彼此。这使得在不影响应用程序其他部分的情况下更新应用程序的一部分变得具有挑战性。
复杂性显著上升。代码库变得更难导航和理解,使得开发者添加新功能变得困难。这也减缓了开发过程,因为理解组件之间的连接需要更多的时间和精力。
维护变得更加负担重重。即使是小的更改也需要在所有组件上进行测试,以确保没有副作用发生。这也增加了回归问题的几率,新的更新可能会破坏现有功能。
部署瓶颈:
每次部署更新或新功能都需要重新部署整个应用程序,这非常耗时。
构建管道需要运行完整的测试套件。对于大型应用程序来说,这个延长的周期可能会延迟部署过程。为了更新一个组件,我们需要部署整个应用程序,这可能导致回归。因此,与重新部署相关的风险增加,这阻碍了频繁的更新。
团队自主性:
为了部署新功能或更新,团队需要同步他们的发布计划。这是具有挑战性的,因为不同团队有不同的时间表和优先级。
一个团队所做的更改可能会影响应用程序的其他部分,这需要彻底的沟通和协调,以确保更新不会引起冲突或错误。
可扩展性问题:
扩展单个应用程序涉及到为整个应用程序分配更多资源,即使只有特定组件需要扩展。
识别和解决性能瓶颈变得更加复杂。应用程序的一部分的瓶颈可能会影响整个系统的性能,使得难以实现最佳性能和可扩展性。
在我们这样的动态环境中,单体架构的挑战由于需要不同团队高效协作而放大。管理单体代码库所需的相互依赖、不同的时间表和协调可能会减缓开发过程。
微前端解决方案
微前端是一种设计方法,其中应用程序由更小且可独立部署的前端单元组成。这些单元由不同的团队独立开发、测试和部署,每个团队负责自己的UI部分。这种方法将许多传统与后端开发中的微服务相关联的好处带到了前端世界。
我们的我们前端应用程序完美地实现了微前端的有效实施。它为开发者提供了一个集中化的平台,使得开发者可以通过单个页面访问所有工具和服务。这种集中化简化了新开发者对不同工具的访问和发现。它有一个启动模板,帮助开发者快速上手我们前端应用程序并开始构建他们的应用程序。通过提供标准化的用户界面,我们前端应用程序确保所有集成应用程序具有一致的外观和感觉。
插图显示,容器应用程序有一个包含变量v1和v2的公共头部,这些变量在应用程序中共享。在主页上,我们有链接到各种不同的微前端应用程序,点击后会渲染它们各自的应用程序界面。
架构
我们前端应用程序采用了双方法无缝加载微前端。它结合了运行时与JavaScript集成和服务器端组合的方法来加载不同的微前端。一个带有页面头部的公共容器应用程序使用服务器端组合来插入页面特定内容。
页面渲染流程
- 客户端请求
- 动作:用户通过浏览器访问我们前端应用程序平台。
- 结果:浏览器向服务器发送请求初始HTML页面。
- 服务器端组合
- 动作:服务器接收请求并开始组合最终的HTML页面。
- 过程:服务器根据请求的URL识别所需的微前端。服务器在HTML模板中为微前端包含占位符。
- 加载JavaScript包
- 动作:组合的HTML页面发送到客户端,其中包括所需微前端的脚本标签。
- 结果:浏览器加载HTML模板中指定的微前端的JavaScript包。
- 渲染微前端
- 动作:每个加载的JavaScript包初始化其各自的微前端。
- 过程:每个微前端暴露一个全局函数作为其入口点。主容器应用程序调用这些函数以在其指定的占位符中渲染微前端。
- 动态更新和交互
- 动作:用户与应用程序交互时,可能需要动态更新。
- 过程:主容器应用程序和微前端可以通过自定义事件和共享状态管理进行通信。地址栏URL或其他共享数据的更改会触发微前端之间的更新。
- 最终页面组合
- 动作:完全组合且可交互的页面呈现给用户。
- 结果:用户体验到一个无缝且连贯的应用程序,尽管它是由多个独立开发的微前端组成的。
服务器端组合
我们以这样的方式分割代码,每部分代表一个可以由独立团队交付的自包含领域概念。每个应用程序都有自己的:
- 最终位于web服务器上的HTML文件。
- 部署管道,允许我们部署对某一页面的更改,而不影响或考虑其他任何页面。
部署后,它提供一个URL来连接到该应用程序。我们匹配位置,然后渲染特定的URL。
server {
listen 8080;
server_name localhost;
root /usr/share;
index index.html;
# 根据URL决定插入哪个HTML片段
location /cloud-console/apps/{appName} {
proxy_pass http://appName:8080/;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 所有位置都应该通过index.html渲染
error_page 404 /index.html;
}
容器应用程序有一个服务器负责渲染和服务每个微前端,前端有一个服务器向其他服务器发出请求。
运行时与JavaScript集成
这种方法在运行时动态加载和管理Web应用程序的不同部分,而不是在构建过程中编译所有内容。这是集成微前端最灵活的方法。每个微前端都通过<script>
标签包含在页面中,并在加载后暴露一个全局函数作为其入口点。
window['app-handler'] = {
mount: ({ element, props }: { element: HTMLElement; props: { _isPlugin: boolean } }) => {
ReactDOM.render(<App />, element);
},
unmount: (container: HTMLElement, isPlugin: boolean) => {
if (container) ReactDOM.unmountComponentAtNode(container);
},
};
这种方法允许我们独立部署每个bundle.js文件。与iframe不同,它提供了完全的灵活性,以我们选择的方式构建微前端之间的集成。我们已经扩展了这种方法,例如只在需要时下载每个JavaScript包。
这种方法的灵活性,结合独立部署的能力,使其成为我们最首选和实践的方法。
微前端景观中的样式挑战
在微前端景观中,CSS的样式设置可能会出现问题。如果不同的团队使用相同的选择器,它可能在同一页面上呈现不一致。由于选择器是由不同的团队编写的,代码分布在不同的仓库中,发现和管理它们可能会很困难。我们正在使用CSS模块或CSS-in-JS库来解决这些问题,因为它们可以确保样式在开发者意图的地方应用。
实现视觉一致性
通过共享的可重用UI组件库,可以在微前端之间实现视觉一致性。共享UI库确保了常见的UI元素在应用程序的不同部分看起来和行为都是一样的。重用这些组件也为团队节省了开发时间和精力,促进了效率和统一的用户体验。
跨应用程序通信
为了在我们前端应用程序上搭载的应用程序之间实现无缝通信,我们实施了以下措施:
- SDK仓库包含为React应用程序实现的自定义路由器。这个路由器作为主容器应用程序和单个微前端之间的桥梁,使得在不同应用程序部分之间的导航和交互更加顺畅。
- 为了实现不同微前端之间的实时通信和同步,我们前端应用程序 SDK提供了自定义事件。这些事件在通知应用程序某些参数更改时至关重要,确保我们前端应用程序的所有部分都是同步的。
- 地址栏URL用于在所有微前端之间管理和共享公共数据变量。这种方法确保了共享数据可以被任何微前端轻松访问和修改。
后端通信
前端的后端(BFF)架构为每个前端应用程序提出了一个服务器端组件。由于所有客户端应用程序都有自己前端的后端,这种模式非常适合我们的用例与后端通信。我们根据BFF架构范式为我们前端应用程序创建了一个API网关。
前端向中间网关层发送请求,然后由网关层确定合适的后端来处理请求并将响应返回给前端。前端每个API调用都包括一个区分符,由网关层处理。
迁移到微前端的好处
减少破坏性更改的风险
微前端允许进行增量升级,使团队能够独立于其他部分更新他们的应用程序部分。这意味着团队可以按照自己的节奏采用新的改进。
我们前端应用程序组件库和SDK在设计时考虑了版本控制。每个微前端可以在不引起冲突的情况下操作不同版本的依赖项,确保了平滑的升级过程。
通过逐步升级组件,减少了广泛问题或停机时间的风险。团队可以在生产部署之前单独测试他们的更改。
解耦代码库
我们从使用React构建的单页应用程序开始。我们引入了一些产品,并逐渐开始将各种应用程序引入我们前端应用程序。随着时间的推移,我们拥有了一个跨越多个团队和多个组件耦合挑战的庞大单体应用程序。
通过微前端,模块化架构确保了组件之间的独立性,减少了在做出更改时产生副作用的风险。通过定义清晰的边界,它最小化了组件之间的共享代码和依赖性。这种分离有助于防止在单个代码库中做出更改时出现的连锁反应。
独立部署
每个微前端都有自己的CI/CD管道,用于构建、测试并将代码部署到生产环境。这确保了一个微前端的更改不会影响其他微前端。团队可以逐步部署更新和新功能,减少了广泛应用程序故障的风险。
提高团队自主性和协作
我们始终坚信实验、学习和成长的文化。如果团队对所有问题都使用相同的框架和工具,那么它们就无法实现自主。通过微前端,团队可以为他们的应用程序尝试最新的技术、框架或新工具。
每个团队都对其应用程序拥有完全控制权,从开发到部署。这减少了团队之间持续协调的需要,使它们能够专注于特定的任务和可交付成果。
现在,团队可以在他们的限定范围内工作,这减少了团队间的依赖和冲突。沟通变得更加流畅,因为团队只需要在共享接口和集成点上协调。
未来路线图
- 设计差异:由于多个团队正在开发他们的用户界面,他们可能无法看到整体大局。因此,控制台可能看起来像是由许多补丁组成的,无论是在风格还是UI/UX方面都不一致。我们正在尝试通过共享一个所有团队都可以使用的公共库和样式指南来解决这一问题。
- 操作复杂性:目前,所有应用程序都在一个资源中部署,这给了其他团队对其应用程序资源维护控制的能力较少。我们正在努力将每个应用程序分离并在其自己的资源中部署。
结论
在开发者控制台中采用微前端代表了解决大型应用程序中碎片化用户界面挑战的显著进步。将单体前端分解为可管理的、可独立部署的单元,提高了开发者平台用户界面的可维护性和可扩展性。这种策略不仅提升了用户体验,还支持快速交付功能。