【譯】協調微前端

協調微前端

image.png

如今是時候討論如何協調微前端了。javascript

首先,關於微前端應該是什麼樣子,有兩種思路,如上一篇文章 中所述,我解釋了微前端的不一樣實現:一個微前端對應着一塊用戶界面的區域,其中微前端是 SPA 或單個頁面。前端

當咱們考慮基於應用的不一樣邏輯區域(如標題,頁腳,付款表單等)的微前端實現時,咱們將面臨不一樣的挑戰,例如: 哪一個團隊將彙總聚合的視圖? 咱們如何避免每一個團隊的外部依賴? 哪一個團隊對彙總視圖中的問題負責? 咱們如何確保應用的特定區域與父容器沒有緊密耦合? 咱們怎樣才能肯定依賴關係之間沒有衝突? 咱們是在運行時仍是編譯時組裝? 若是咱們決定在運行時建立頁面,那麼咱們的應用服務器層是否能夠擴展? 內容是否能夠緩存,能夠的話持續多長時間? 咱們如何確保開發流程不受分佈式團隊的影響?java

還有許多其餘問題(技術和組織上的)可能使咱們的生活方式(life way)變得更加複雜。 有趣的是,這種方式沒有提供預期的好處。Spotify 大規模地作了許多工做,回滾到了基於 SPA 的更「經典」的架構。git

方便起見,咱們將咱們的微前端定義爲 SPA 或單頁,在編譯時生成一版,以免在合成層發生任何可能的意外。es6

不管如何,這種方法也面臨着一些挑戰,大概主要的是理解咱們如何協調咱們的微前端,這是本文的重點。github

協調層能夠位於客戶端服務器端邊緣端;解決方案取決於協調層對咱們的應用應該是多麼「智能」。web

服務器端或邊緣端協調器

服務器端或邊緣端協調器意味着對於任何深層連接或器質性(organic)流量,必須經過應用服務器或邊緣解決方案(例如 lambda@edge)來分析咱們的域名,在這兩種狀況下咱們都須要維護與靜態 HTML 文件(又名微前端)對應的 URL 映射。編程

舉個例子,若是用戶從咱們的應用註銷,咱們應該卸載通過身份驗證的微前端並加載登陸/註冊微前端,所以應用服務器或邊緣上運行的代碼應該知道要提供哪一個 HTML 文件,來服務咱們將使用 SPA 的每一個 URL 或 URL 組。bootstrap

考慮到咱們能夠直接在服務器上快速更改微前端映射而不會對客戶端產生任何影響,這種技術能夠毫無問題地工做,可是它提出了一些潛在的挑戰,例如找到在微前端之間共享數據的最佳方法是瀏覽器內存儲的一些限制,而且對服務器進行太多往返是不理想的,特別是對於慢速鏈接。數組

另外一個挑戰是找到初始化應用的解決方案,考慮到咱們將總體塊分紅多個子域的微前端,咱們是否會在每次加載新的微前端時初始化應用?咱們是否要使用服務器端渲染在 HTML 中存儲配置?咱們如何在微前端之間進行溝通?當有突發流量時,咱們如何擴展咱們的應用服務器?

這些是實現服務器端或邊緣端協調器的一些挑戰。

客戶端協調器

另外一種可能的方法是建立一個客戶端協調器,負責:

  • 初始化應用
  • 將應用的配置共享給全部微前端
  • 根據用戶的狀態加載/卸載微前端
  • 微前端之間的路由
  • 暴露一個用於在微前端和客戶端協調器之間進行交互的 API

此解決方案的好處之一是你能夠更好地控制應用初始化。

若是設計得很好,客戶端協調器就不須要常常更改,所以會至關穩定。

它提供了可供各類微前端使用的附加功能,但它不是特定領域的,當咱們的目標是從他們運行的平臺(瀏覽器而不是移動設備或智能電視)中抽象出咱們的微前端時,它也是一個很好的解決方案。)。

主要的弊端是初始化的投資,爲了肯定這個協調器應該處理哪一個功能,巨大的風險暗藏其中,這一層上的錯誤可能會炸燬整個應用和新功能的實現,若是沒有很好地協調,可能會減慢其餘團隊建立跨團隊依賴的速度。

在 DAZN 中,咱們選擇了一個名爲 引導程序(bootstrap) 的客戶端協調器。

引導程序具備上面列出的全部職責以及與咱們的用例相關的額外職責,實際上,引導程序正在抽象運行應用的平臺的 I/O API,這樣每一個微前端都是在不知道平臺已加載的狀況下完成的。

經過這種技術,咱們能夠在多個智能電視,控制檯或機頂盒上重複使用微前端,而無需重寫特定設備的實現,除非實現存在內存泄漏或性能問題。

每次用戶在瀏覽器中鍵入咱們的域名或在智能電視上打開應用時,都會提供引導程序,它始終存在,而且在整個用戶會話期間從不卸載。

DAZN 加載流

讓咱們嘗試進一步擴展引導程序,以瞭解它背後的主要思想:

初始化應用

引導程序應負責設置應用上下文,首先要了解用戶是否通過身份驗證,並根據應用初始化咱們能夠加載正確的微前端。

應在此階段管理應用爲整個應用設置上下文所需的任何其餘有意義的信息。

它能夠是靜態的配置(JSON)或動態的(須要消費 API),不管哪一種方式,咱們的前端都有一個外部配置容許咱們在不須要引導程序版本的狀況下更改系統的某些行爲。

例如,配置能夠爲應用生命週期提供有價值的信息,例如功能切換,用戶界面的本地化標籤等。

微前端路由

引導程序明確地負責微前端之間的路由,在咱們的實現中,咱們在引導程序和每一個微前端之間有 2 個路由擴展。

引導程序沒有咱們應用的整個 URL 映射,而是在內存中加載根據用戶狀態和經過用戶的交互或深層連接請求的 URL,加載相應微前端的映射。

這兩個維度容許咱們加載正確的微前端並留給處理 URL 的微前端代碼來管理組成它的不一樣視圖。

這裏的經驗法則是爲微前端分配特定的第二級路徑,這樣就能夠更容易地解決微前端的範圍,例如,當用戶鍵入 mydomain.com/account/* 時,應該加載認證微前端。反而當用戶點擊 mydomain.com/support/* 等連接時,應加載幫助頁面的微前端。

在每一個微前端內部,咱們能夠決定使用其餘路徑,例如 mydomain.com/support/help-page-Amydomain.com/support/help-page-B,這樣就能夠在微前端沒有經過應用的多個部分傳播它時,保留域名知識。

這裏的主要內容是:咱們在具備客戶端協調器的微前端應用中,有兩種類型的路由,一種是在引導級別的全局路由,另外一種是在微前端內部的本地路由。

微前端的生命週期

正如咱們以前提到的,每一個微前端應該經過引導程序加載,可是如何實現?

例如,Single-spa 使用 javascript 文件做爲安裝新微前端的入口點。

在 DAZN 中,咱們採用了不一樣的方法,由於只使用 javascript 文件加載微前端會排除在編譯時使用服務器端渲染的可能性,這對咱們來講是一個有趣的選擇,能夠爲咱們的用戶提供更快的反饋。它們能夠從一個微前端過渡到另外一個微前端。

微前端剖析:HTML,JavaScript 和 CSS 文件

考慮到 HTML 文件基本上是一個具備特定模式的 XML 文件,引導程序可使用 DOMParser 加載和解析附加在其自身內的全部相關節點的文件,來加載微前端。這是解析 XML 或 HTML 字符串的標準接口。

能夠在引導程序的 DOM 樹中附加 body 或 head 標籤內的任意內容。

潛在地,咱們還能夠決定爲咱們須要附加的全部標記定義特定屬性,以便快速選擇它們。

不管如何,整體思路是解析 HTML 文件並在引導程序中附加加載微前端所需的內容,所以微前端 HTML 文件中存在的任何外部依賴項(如 JavaScript 或 CSS 文件)都將被追加,並所以經過瀏覽器加載。

這種簡潔方法的一個巨大好處是,它不是以自我爲中心的(opinionated),任何人均可以以一個新的微前端開始工做,而不是學習咱們決定處理微前端的方式,由於最後,只要微前端輸出的結果是前端三板斧:HTML,JavaScript 和 CSS 文件。

我錄製了一個限制鏈接的視頻,以顯示引導程序如何將 DOM 元素附加到自身內部,由於你將看到有 4 個階段:

  • 肯定要加載的微前端,
  • 加載微前端的 HTML,
  • 解析它,
  • 用於在頁面中顯示微前端的相關標籤。

這是一個很是簡單但有效的機制!

一個慢動做視頻,用於顯示引導程序如何從微前端加載自身內部的節點:youtu.be/TKhXupQxf1M

添加到每一個微前端的附加功能,是能夠在安裝或卸載以前和以後執行某些操做,這樣微前端能夠執行任何邏輯,來清理附加到 window 對象的任何對象或任意的其餘邏輯到在前面提到的 4 個生命週期的方法之一中運行。

引導程序負責觸發微前端生命週期方法,並在加載下一個微前端以前清理內存,此操做可確保在不一樣的微前端使用的庫的不一樣版本或相同版本中不會發生衝突.

引導程序的內存和依賴關係管理

如今是時候深刻研究微前端的內存管理了,考慮到引導程序每次加載一個微前端,如上一篇文章中所述,而且每一個微前端都沒有與另外一個微前端共享任何庫或依賴,咱們可能最終會出現微前端加載 React v.15 和接下來加載 React v.16 的狀況。

與此同時,咱們但願可以自由選擇每一個微前端內部的任意技術和庫版本,由於保留業務和技術知識的開發團隊應該提供最佳的實現選擇,而不是在整個過程當中進行不斷的權衡。整個應用一般在咱們使用單頁面應用時就正好就緒了。

在這個階段,我相信很容易猜到咱們面臨的挑戰,由於微前端使用的任何庫或框架都會在全局 window 上附加對象,而在 Javascript 中咱們沒法直接控制垃圾收集器,但咱們能夠方便地處理刪除給定對象的全部引用和實例的元素。

爲了實現這個目標,額外的引導責任是跟蹤任意的微前端附加到 window 對象上的對象,並在卸載微前端以後,加載新的前端以前清理 window 對象JavaScript 中的元編程喜悅 🎉)。

引導程序獲取附加到 window 對象的全部鍵的快照,並在加載新的微前端以前刪除它們,這樣咱們就能夠跟蹤應該刪除的內容,而無需複製內存中的全部的對象,經過這個數組的簡單遍歷,咱們就能夠刪除 window 中卸載的微前端使用的對象。

用於在引導程序和微前端之間進行通訊的 API 層

最後一點值得一提的是引導程序經過 window 對象公開的 API 層。

若是你問本身咱們如何共享數據並在微前端之間進行通訊,那麼引導程序就是答案!

請記住,咱們的實現是基於咱們每次老是加載一個微前端的假設,而且咱們基於用應用的子域切分微前端,你很快就會意識到跨越微前端共享的數據不會常常發生,若是你在定義全部子域的初始會話中運行良好的話。

在微前端之間共享數據很是簡單,引導程序共享一些 API 用於存儲和檢索全部微前端可訪問的信息,由你決定哪一個存儲更方便你的實現以及你想要添加到對象的限制類型在本地存儲。

考慮到引導程序是在平臺和微前端之間用 vanilla JavaScript 編寫的一個微小層,它負責初始化應用,咱們還須要公開一個 API 層來抽象 I/O 層,以便存儲或從中檢索信息。微前端 使用多個設備須要具備不一樣的 API 來存儲和檢索文件,由於 Web 在全部這些平臺上存儲 API 並不老是一致的。

要強調的另外一個重要部分是從靜態 JSON 文件或 API 中檢索的配置,該文件一般與全部微前端共享,以瞭解它們運行的​​上下文(例如,根據國家/地區或語言共享特定配置)。

當咱們設計引導程序暴露的 API 時,最重要的是試圖進行前瞻性思考,由於引導程序應該是一個在每一個版本都不會改變的層,不然你可能會破壞與微前端的一些約定並將微前端耦合起來。引導功能可能會危及在多個子域中拆分業務域所作的全部出色工做。

總結

在這篇文章中,咱們探討了協調微前端的可能性,咱們深刻探討了在 DAZN 中被稱爲引導程序的客戶端協調器,特別是,咱們已經看到了這種方法的好處和挑戰,以及咱們如何應付、解決它們。

值得一提的是,咱們看到引導程序有 3 個主要職責:

  • 微前端之間的路由(加載,卸載和生命週期方法)
  • 初始化應用
  • 爲微型前端通訊和網絡存儲公開 API 層

在分享這些帖子以後,我常常收到的一個問題是,引導程序是不是開源的,這個答案是咱們正在考慮的問題,但咱們如今不能承諾具體的時間(這也是我之因此沒有在這篇文章中分享代碼的緣由,再次道歉🙏)。

我真的但願你可以更清楚地瞭解如何構建你的下一個微前端項目,若是不能隨意嘗試,那麼我能夠寫下一篇文章供你思考! ✌️

相關文章
相關標籤/搜索