【譯】協調微前端

NO IMAGE

協調微前端

【譯】協調微前端

現在是時候討論如何協調微前端了。

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

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

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

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

無論如何,這種方法也面臨著一些挑戰,大概主要的是理解我們如何協調我們的微前端,這是本文的重點。

協調層可以位於客戶端服務器端邊緣端;解決方案取決於協調層對我們的應用應該是多麼“智能”。

服務器端或邊緣端協調器

服務器端或邊緣端協調器意味著對於任何深層鏈接或器質性(organic)流量,必須通過應用服務器或邊緣解決方案(例如 [email protected])來分析我們的域名,在這兩種情況下我們都需要維護與靜態 HTML 文件(又名微前端)對應的 URL 映射。

舉個例子,如果用戶從我們的應用註銷,我們應該卸載經過身份驗證的微前端並加載登錄/註冊微前端,因此應用服務器或邊緣上運行的代碼應該知道要提供哪個 HTML 文件,來服務我們將使用 SPA 的每個 URL 或 URL 組。

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

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

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

客戶端協調器

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

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

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

如果設計得很好,客戶端協調器就不需要經常更改,因此會相當穩定。

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

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

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

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

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

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

【譯】協調微前端

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

初始化應用

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

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

它可以是靜態的配置(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 文件基本上是一個具有特定模式的 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 層

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

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

相關文章

Flutter系列一:Flutter快速入門

大前端性能總結

【全是乾貨】談談如何學習一項新技能,沒有理論,全是實戰

你可能不知道的Markdown騷操作