如今是时候讨论如何协调微前端了。javascript
首先,关于微前端应该是什么样子,有两种思路,如上一篇文章 中所述,我解释了微前端的不一样实现:一个微前端对应着一块用户界面的区域,其中微前端是 SPA 或单个页面。前端
当咱们考虑基于应用的不一样逻辑区域(如标题,页脚,付款表单等)的微前端实现时,咱们将面临不一样的挑战,例如: 哪一个团队将汇总聚合的视图? 咱们如何避免每一个团队的外部依赖? 哪一个团队对汇总视图中的问题负责? 咱们如何确保应用的特定区域与父容器没有紧密耦合? 咱们怎样才能肯定依赖关系之间没有冲突? 咱们是在运行时仍是编译时组装? 若是咱们决定在运行时建立页面,那么咱们的应用服务器层是否能够扩展? 内容是否能够缓存,能够的话持续多长时间? 咱们如何确保开发流程不受分布式团队的影响?java
还有许多其余问题(技术和组织上的)可能使咱们的生活方式(life way)变得更加复杂。 有趣的是,这种方式没有提供预期的好处。Spotify 大规模地作了许多工做,回滚到了基于 SPA 的更“经典”的架构。git
方便起见,咱们将咱们的微前端定义为 SPA 或单页,在编译时生成一版,以免在合成层发生任何可能的意外。es6
不管如何,这种方法也面临着一些挑战,大概主要的是理解咱们如何协调咱们的微前端,这是本文的重点。github
协调层能够位于客户端,服务器端或边缘端;解决方案取决于协调层对咱们的应用应该是多么“智能”。web
服务器端或边缘端协调器意味着对于任何深层连接或器质性(organic)流量,必须经过应用服务器或边缘解决方案(例如 lambda@edge)来分析咱们的域名,在这两种状况下咱们都须要维护与静态 HTML 文件(又名微前端)对应的 URL 映射。编程
举个例子,若是用户从咱们的应用注销,咱们应该卸载通过身份验证的微前端并加载登陆/注册微前端,所以应用服务器或边缘上运行的代码应该知道要提供哪一个 HTML 文件,来服务咱们将使用 SPA 的每一个 URL 或 URL 组。bootstrap
考虑到咱们能够直接在服务器上快速更改微前端映射而不会对客户端产生任何影响,这种技术能够毫无问题地工做,可是它提出了一些潜在的挑战,例如找到在微前端之间共享数据的最佳方法是浏览器内存储的一些限制,而且对服务器进行太多往返是不理想的,特别是对于慢速链接。数组
另外一个挑战是找到初始化应用的解决方案,考虑到咱们将总体块分红多个子域的微前端,咱们是否会在每次加载新的微前端时初始化应用?咱们是否要使用服务器端渲染在 HTML 中存储配置?咱们如何在微前端之间进行沟通?当有突发流量时,咱们如何扩展咱们的应用服务器?
这些是实现服务器端或边缘端协调器的一些挑战。
另外一种可能的方法是建立一个客户端协调器,负责:
此解决方案的好处之一是你能够更好地控制应用初始化。
若是设计得很好,客户端协调器就不须要常常更改,所以会至关稳定。
它提供了可供各类微前端使用的附加功能,但它不是特定领域的,当咱们的目标是从他们运行的平台(浏览器而不是移动设备或智能电视)中抽象出咱们的微前端时,它也是一个很好的解决方案。)。
主要的弊端是初始化的投资,为了肯定这个协调器应该处理哪一个功能,巨大的风险暗藏其中,这一层上的错误可能会炸毁整个应用和新功能的实现,若是没有很好地协调,可能会减慢其余团队建立跨团队依赖的速度。
在 DAZN 中,咱们选择了一个名为 引导程序(bootstrap) 的客户端协调器。
引导程序具备上面列出的全部职责以及与咱们的用例相关的额外职责,实际上,引导程序正在抽象运行应用的平台的 I/O API,这样每一个微前端都是在不知道平台已加载的状况下完成的。
经过这种技术,咱们能够在多个智能电视,控制台或机顶盒上重复使用微前端,而无需重写特定设备的实现,除非实现存在内存泄漏或性能问题。
每次用户在浏览器中键入咱们的域名或在智能电视上打开应用时,都会提供引导程序,它始终存在,而且在整个用户会话期间从不卸载。
让咱们尝试进一步扩展引导程序,以了解它背后的主要思想:
引导程序应负责设置应用上下文,首先要了解用户是否通过身份验证,并根据应用初始化咱们能够加载正确的微前端。
应在此阶段管理应用为整个应用设置上下文所需的任何其余有意义的信息。
它能够是静态的配置(JSON)或动态的(须要消费 API),不管哪一种方式,咱们的前端都有一个外部配置容许咱们在不须要引导程序版本的状况下更改系统的某些行为。
例如,配置能够为应用生命周期提供有价值的信息,例如功能切换,用户界面的本地化标签等。
引导程序明确地负责微前端之间的路由,在咱们的实现中,咱们在引导程序和每一个微前端之间有 2 个路由扩展。
引导程序没有咱们应用的整个 URL 映射,而是在内存中加载根据用户状态和经过用户的交互或深层连接请求的 URL,加载相应微前端的映射。
这两个维度容许咱们加载正确的微前端并留给处理 URL 的微前端代码来管理组成它的不一样视图。
这里的经验法则是为微前端分配特定的第二级路径,这样就能够更容易地解决微前端的范围,例如,当用户键入 mydomain.com/account/* 时,应该加载认证微前端。反而当用户点击 mydomain.com/support/* 等连接时,应加载帮助页面的微前端。
在每一个微前端内部,咱们能够决定使用其余路径,例如 mydomain.com/support/help-page-A 或 mydomain.com/support/help-page-B,这样就能够在微前端没有经过应用的多个部分传播它时,保留域名知识。
这里的主要内容是:咱们在具备客户端协调器的微前端应用中,有两种类型的路由,一种是在引导级别的全局路由,另外一种是在微前端内部的本地路由。
正如咱们以前提到的,每一个微前端应该经过引导程序加载,可是如何实现?
例如,Single-spa 使用 javascript 文件做为安装新微前端的入口点。
在 DAZN 中,咱们采用了不一样的方法,由于只使用 javascript 文件加载微前端会排除在编译时使用服务器端渲染的可能性,这对咱们来讲是一个有趣的选择,能够为咱们的用户提供更快的反馈。它们能够从一个微前端过渡到另外一个微前端。
考虑到 HTML 文件基本上是一个具备特定模式的 XML 文件,引导程序可使用 DOMParser 加载和解析附加在其自身内的全部相关节点的文件,来加载微前端。这是解析 XML 或 HTML 字符串的标准接口。
能够在引导程序的 DOM 树中附加 body 或 head 标签内的任意内容。
潜在地,咱们还能够决定为咱们须要附加的全部标记定义特定属性,以便快速选择它们。
不管如何,整体思路是解析 HTML 文件并在引导程序中附加加载微前端所需的内容,所以微前端 HTML 文件中存在的任何外部依赖项(如 JavaScript 或 CSS 文件)都将被追加,并所以经过浏览器加载。
这种简洁方法的一个巨大好处是,它不是以自我为中心的(opinionated),任何人均可以以一个新的微前端开始工做,而不是学习咱们决定处理微前端的方式,由于最后,只要微前端输出的结果是前端三板斧:HTML,JavaScript 和 CSS 文件。
我录制了一个限制链接的视频,以显示引导程序如何将 DOM 元素附加到自身内部,由于你将看到有 4 个阶段:
这是一个很是简单但有效的机制!
一个慢动做视频,用于显示引导程序如何从微前端加载自身内部的节点:youtu.be/TKhXupQxf1M
添加到每一个微前端的附加功能,是能够在安装或卸载以前和以后执行某些操做,这样微前端能够执行任何逻辑,来清理附加到 window 对象的任何对象或任意的其余逻辑到在前面提到的 4 个生命周期的方法之一中运行。
引导程序负责触发微前端生命周期方法,并在加载下一个微前端以前清理内存,此操做可确保在不一样的微前端使用的库的不一样版本或相同版本中不会发生冲突.
如今是时候深刻研究微前端的内存管理了,考虑到引导程序每次加载一个微前端,如上一篇文章中所述,而且每一个微前端都没有与另外一个微前端共享任何库或依赖,咱们可能最终会出现微前端加载 React v.15 和接下来加载 React v.16 的状况。
与此同时,咱们但愿可以自由选择每一个微前端内部的任意技术和库版本,由于保留业务和技术知识的开发团队应该提供最佳的实现选择,而不是在整个过程当中进行不断的权衡。整个应用一般在咱们使用单页面应用时就正好就绪了。
在这个阶段,我相信很容易猜到咱们面临的挑战,由于微前端使用的任何库或框架都会在全局 window 上附加对象,而在 Javascript 中咱们没法直接控制垃圾收集器,但咱们能够方便地处理删除给定对象的全部引用和实例的元素。
为了实现这个目标,额外的引导责任是跟踪任意的微前端附加到 window 对象上的对象,并在卸载微前端以后,加载新的前端以前清理 window 对象JavaScript 中的元编程喜悦 🎉)。
引导程序获取附加到 window 对象的全部键的快照,并在加载新的微前端以前删除它们,这样咱们就能够跟踪应该删除的内容,而无需复制内存中的全部的对象,经过这个数组的简单遍历,咱们就能够删除 window 中卸载的微前端使用的对象。
最后一点值得一提的是引导程序经过 window 对象公开的 API 层。
若是你问本身咱们如何共享数据并在微前端之间进行通讯,那么引导程序就是答案!
请记住,咱们的实现是基于咱们每次老是加载一个微前端的假设,而且咱们基于用应用的子域切分微前端,你很快就会意识到跨越微前端共享的数据不会常常发生,若是你在定义全部子域的初始会话中运行良好的话。
在微前端之间共享数据很是简单,引导程序共享一些 API 用于存储和检索全部微前端可访问的信息,由你决定哪一个存储更方便你的实现以及你想要添加到对象的限制类型在本地存储。
考虑到引导程序是在平台和微前端之间用 vanilla JavaScript 编写的一个微小层,它负责初始化应用,咱们还须要公开一个 API 层来抽象 I/O 层,以便存储或从中检索信息。微前端 使用多个设备须要具备不一样的 API 来存储和检索文件,由于 Web 在全部这些平台上存储 API 并不老是一致的。
要强调的另外一个重要部分是从静态 JSON 文件或 API 中检索的配置,该文件一般与全部微前端共享,以了解它们运行的上下文(例如,根据国家/地区或语言共享特定配置)。
当咱们设计引导程序暴露的 API 时,最重要的是试图进行前瞻性思考,由于引导程序应该是一个在每一个版本都不会改变的层,不然你可能会破坏与微前端的一些约定并将微前端耦合起来。引导功能可能会危及在多个子域中拆分业务域所作的全部出色工做。
在这篇文章中,咱们探讨了协调微前端的可能性,咱们深刻探讨了在 DAZN 中被称为引导程序的客户端协调器,特别是,咱们已经看到了这种方法的好处和挑战,以及咱们如何应付、解决它们。
值得一提的是,咱们看到引导程序有 3 个主要职责:
在分享这些帖子以后,我常常收到的一个问题是,引导程序是不是开源的,这个答案是咱们正在考虑的问题,但咱们如今不能承诺具体的时间(这也是我之因此没有在这篇文章中分享代码的缘由,再次道歉🙏)。
我真的但愿你可以更清楚地了解如何构建你的下一个微前端项目,若是不能随意尝试,那么我能够写下一篇文章供你思考! ✌️