微前端究竟好在哪?


微前端架構是一種設計方法,其中,前端應用被分解為多個松散而協同工作的半獨立“微應用”。微前端的思想來源於微服務,其名稱也遵循了微服務的命名方式。那么,微前端的優勢和好處在哪?讓我們一起通過這篇微前端教程來了解。

微前端模式的好處包括:

  1. 微前端架構可能更簡潔,因此更易於推理和管理。
  2. 多個獨立的開發團隊更容易協同開發單個前端應用。
  3. 微前端模式可以讓“新”應用與 " 舊 " 應用並行工作,從而提供了一種遷移手段。

盡管微前端最近在業內引發了很多關注,但到目前為止並沒有出現一種絕對主流的實現方式,也沒有公認“最佳”的微前端框架。實際上,根據目標和需求的不同,可行的方法有很多。要了解其中一些比較知名的實現,請參見附注。

本文將提供一份微前端教程,重點在於具體的實現,主要介紹微前端架構中的重要問題及其可能的解決方案。

我們的實現稱為 Yumcha。“Yum cha”在粵語中的字面意思是“喝茶”,但其日常含義是“出去吃點心”。這里的想法是,宏應用(我們將其稱為組合式頂級應用)中的各個單獨的微應用類似於外出野餐時帶的許多大小不一的籃子。

有時我們將 Yumcha 稱為“微前端框架”。如今,“框架”這個術語通常用來指代 Angular、react、vue.js 或其他類似的 Web 應用上層架構,但我們所說的完全不是這種框架。我們稱 Yumcha 為框架只是為了方便起見:實際上,它是一組工具和一些薄層,用於構建基於微前端的應用。

 

微前端教程第一步:為組合應用作標記

首先我們來深入探討,如何定義一款 " 宏應用 " 以及組成它的眾多微應用。標記(Markup)一直是 Web 的核心要素,因此只需使用以下標記即可定義我們的宏應用:

<html> <head> <script src="/yumcha.js"></script> </head> <body> <h1>Hello, micro-frontend app.</h1> <!-- HERE ARE THE MICROAPPS! --> <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal> <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal> </body> </html>

使用標記定義宏應用后,我們就可以充分利用 HTML 和 css 的功能來布局和管理我們的微應用。例如,一個微應用可以處在另一個微應用的上方,或者位於其側面,或者位於頁面的一角,或者位於折疊面板的一個格子內,或者可以一直隱藏起來直到發生某種情況再現身,或者永久地保留在背景中 。

我們將用於微應用的自定義元素命名為 yumcha-portal ,因為“portal”是 portal 提案中針對微應用推薦的術語,這一提案是為微前端定義標准 HTML 元素的早期嘗試。

 

實現自定義元素

我們應該如何實現呢?因為它是自定義元素,所以當然是作為 Web 組件來實現了!我們有許多高水平的方案可供挑選,幫助我們編寫和編譯微前端 Web 組件。在本文中,我們將使用 Polymer 項目的最新版本 LitElement。LitElement 支持基於 TypeScript 的語法糖,為我們處理了大多數自定義元素樣板。為了使 yumcha-portal 在我們的頁面上可用,我們必須將相關代碼作為 script包括在頁面內,之前的代碼就是這樣做的。

但是 yumcha-portal 到底是做什么的呢?首先它可以使用指定的源創建一個 iframe:

render() {
    return html`<iframe src=${this.src}></iframe>`; }

這里,render 是標准的 LitElement 渲染 hook,使用它的 html 標記模板字面量。對於某些普通的場景來說,這種初級功能可能就夠用了。

 

在 iframe 中嵌入微應用

大家對 iframe 這個 HTML 元素又愛又恨,但實際上它們提供了極其有用的、堅如磐石的沙箱行為。但在使用 iframe 時還有很多問題需要注意,這些問題可能會對我們應用的行為和功能產生影響:

  • 首先,iframe 在大小控制和自身布局方面有一些眾所周知的怪癖。
  • 當然,css 會與 iframe 完全隔離 ,這有利有弊。
  • 瀏覽器的“后退”按鈕將正常工作,但是 iframe 的 當前導航狀態不會反映在頁面的 URL 中 ,所以我們既不能通過剪切和粘貼 URL 來使微應用狀態與宏應用保持一致,也無法對其進行深層鏈接。
  • 根據我們的 CORS 設置,從外部與 iframe 通信 可能需要通過 postMessage 協議 。
  • 必須處理 跨 iframe 邊界的身份驗證 事宜。
  • 一些屏幕閱讀器可能會在 iframe 邊界出錯 ,或者需要 iframe 具有可以向用戶告知的標題。

如果不使用 iframe 的話,上面的某些問題也可以避免或減輕,我們稍后會討論這種替代方法。

從正面的角度來說,iframe 有自己獨立的 Content-Security-Policy(內容安全政策,CSP)。另外,如果 iframe 指向的微應用使用了 Service Worker 或實現了服務端渲染,那么一切都會按照預期正常工作。我們還可以為 iframe 指定各種沙箱選項以限制其能力,例如禁止導航到頂部框架等。

一些瀏覽器已經支持,或正在計划為 iframe 支持 loading=lazy 屬性,該屬性會推遲加載非首屏的 iframe,直到用戶將頁面滾動到它們附近才開始加載,但這並未提供對延遲加載的細粒度控制,這種控制才是我們想要的。

iframe 的真正問題在於,它們的內容需要多個網絡請求才能獲取。首先接收頂級的 index.html,然后加載它的腳本,接下來解析它的 HTML——但是隨后,瀏覽器必須為 iframe 的 HTML 發送另一個請求,等待接收它,解析並加載其腳本,然后渲染 iframe 的內容 。在許多情況下,iframe 的 JavaScript 也得單獨加載,調用自己的 API;等到這些 API 調用返回,為視圖准備的數據處理完畢之后才能顯示出有意義的數據。

這可能會導致我們不希望看到的延遲和渲染偽像,尤其在涉及多個微應用時更容易出現這類問題。如果 iframe 的應用實現了 SSR 還能好一些,但還是不能避免額外的往返行程。

因此,我們在設計門戶實現時面臨的一項主要挑戰就是處理往返問題。我們的目標是,只用單個網絡請求就獲取整個頁面和頁面上的所有微應用(包括每個微應用會填充的內容)。這個問題的解決方案就是 Yumcha 服務器。

 

Yumcha 服務器

本文介紹的微前端解決方案的關鍵要素,就是設置一個專用服務器來處理微應用組合。這個服務器會代理對托管各個微應用的服務器發出的請求。當然,我們需要花些功夫來設置和管理這個服務器。一些微前端方法(例如 single-spa)為了簡化部署和配置工作,會盡量精簡這種特殊服務器的設置任務。

但是建立這種反向代理的成本會被我們從中獲得的收益抵消。實際上,如果沒有這種反向代理,我們基於微前端的應用就有一些重要的行為無法實現。有很多商業和免費的方案可以用來設置這種反向代理服務器。

反向代理除了將微應用請求路由到合適的服務器外,還會將宏應用請求路由到宏應用服務器上。這個宏應用服務器以特殊方式為組合應用提供 HTML。它會通過代理服務器的 URL 從瀏覽器收到對 index.html 的請求,然后獲取 index.html,對其進行簡單但重要的轉換后返回它。

具體來說,HTML 將為 yumcha-portal 標記解析,從 Node.js 生態系統中找一款合適的 HTML 解析器即可。使用 yumcha-portal 的 src 屬性可以聯系上運行微應用的服務器,並獲取其 index.html——包括服務端渲染的內容(如果存在)。結果將作為 script 或 template 標記插入 HTML 響應中,這樣就不會被瀏覽器執行。

這套設置的優點包括:首先,在對組合頁面的 index.html 發出請求時,服務器可以從各個微應用服務器中整體檢索各個頁面,包括 SSR 渲染的內容(如果存在) ——並向瀏覽器提供一個完整的頁面(包括可用於填充 iframe 的內容),而無需額外的服務器往返(使用不多的 srcdoc 屬性)。代理服務器還可以完全掩蓋微應用來源的細節。最后,由於所有應用請求都來自同一來源,因此它簡化了 CORS 問題。

回到客戶端, yumcha-portal 標記被實例化並找到服務器放置在響應文檔中的內容,並在適當的時間渲染 iframe,然后將內容分配給其 srcdoc 屬性。如果我們不使用 iframe(請參見下文),則與 yumcha-portal 標簽對應的內容(如果我們正在使用的話)會插入自定義元素的 shadow DOM 中,或者直接內聯到文檔中。

至此,我們已經有了一個基於微前端應用的半成品。

這只是 Yumcha 服務器眾多有趣功能的冰山一角。比如說,我們想要添加一些功能來控制來自微應用服務器的 HTTP 錯誤響應的處理方式,或者控制響應速度非常慢的微應用的應對策略——如果一個微應用沒有響應,我們可不想永遠等下去!

這套 Yumcha 宏應用 index.html 轉換邏輯很容易用無服務器 lambda 函數的方式實現,也可以作為服務端框架(如 Express 或 Koa)的中間件來實現。

 

基於存根的微應用控件

回到客戶端,在我們實現微應用的過程中,要做到高效、延遲加載和無垃圾的渲染還有一個重要的層面需要關注。我們可以使用 src 屬性(發出另一個網絡請求),或使用 srcdoc 屬性(由服務器為我們填充內容)來為每個微應用生成 iframe 標記。但是在這兩種情況下,這個 iframe 中的代碼都會立即啟動,包括加載其所有腳本和鏈接標簽、引導程序、任何初始 API 調用以及相關的數據處理——即使這個微應用根本沒有被用戶訪問過。

我們針對此問題的解決方案是,一開始將頁面上的微應用表示為較小的未激活存根,等待將來激活。可以使用未充分利用的 Intersection-Observer API,通過微應用的視圖區域來激活,或者更常見的是通過外部發送的預先通知來激活。當然,我們也可以指定立即激活微應用。

不管是哪種方式,只有在激活了微應用后 iframe 才會真正渲染,其代碼才會被加載和執行。就我們使用 LitElement 的實現而言,假設激活狀態由一個 activated 的實例變量表示,我們將得到以下內容:

render() {
  if (!this.activated) return html`{this.placeholder}`; else return html` <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`; }

 

微應用間通信

盡管根據定義,構成宏應用的多個微應用是松散耦合的,但它們仍需要能夠相互通信。例如,導航微應用需要發出通知,告知用戶剛剛選擇的某個微應用應被激活,並且要激活的應用需要能夠接收此類通知。

為了符合我們的極簡主義思想,我們希望避免引入很多傳遞消息的機制。相反,基於 Web 組件的精神,我們將使用 DOM 事件。我們提供了一個簡單的廣播 API,該 API 會預先通知所有存根,告訴它們即將發生哪個事件,然后等待該事件類型下面所有請求激活的事件激活完畢,最后將事件分派給文檔,這樣任何微應用都可以偵聽到它。假設我們所有的 iframe 都來自同一來源,我們就可以從 iframe 一路來到頁面,找出引發事件的元素,反之亦然。

 

路由

在當今時代,我們所有人都希望 SPA 中的 URL 欄代表應用的視圖狀態,這樣我們就可以剪切、粘貼、發送郵件、記錄文本和鏈接到這個 URL,從而直接跳轉到應用內的頁面。但在微前端應用中,應用狀態實際上是很多狀態的組合,每個微應用對應一個狀態。我們如何表示和控制這一切呢?

解決方案是將每個微應用的狀態編碼為單個復合 URL,並使用小型的宏應用路由,該路由知道如何將這些復合 URL 組合在一起或者拆分開來。遺憾的是,這要求每個微應用都有特定於 Yumcha 的邏輯:從宏應用路由接收消息並更新微應用的狀態,反之則是通知宏應用路由這個狀態的變化,以更新復合 URL。例如,特定於 Angular 的可能是 YumchaLocationStrategy,或特定於 react 的元素。

 

非 iframe 情況

如上所述,在 iframe 中托管微應用確實存在一些缺點。有兩種替代方案:直接將它們直接內嵌在頁面的 HTML 中,或者將它們放置在 shadow DOM 中。兩種選擇都在某種程度上抵消了 iframe 的優缺點,但具體的方式有所不同。

例如,必須以某種方式合並獨立的微應用 CSP 策略。假設屏幕閱讀器等輔助技術支持 shadow DOM(目前還不是全部平台都支持),它們不用 iframe 的效果會更好。使用 Service Worker 的“作用域”概念注冊一個微應用的 Service Worker 應該是很簡單的,盡管該應用必須確保其 Service Worker 以該應用的名稱而不是 "/”注冊。與 iframe 相關的布局問題均不適合內聯或 shadow DOM 方法。

但是,使用諸如 Angular 和 React 之類的框架構建的應用可能不適合內聯或 shadow DOM。對於這些情況,我們可能還是會使用 iframe。

內聯和 shadow DOM 方法在 CSS 層面有所不同。CSS 會被完全封裝在 shadow DOM 中。如果由於某種原因我們確實想與 shadow DOM 共享外部 CSS,則必須使用可構造的樣式表之類的東西。對於內聯的微應用來說,所有 CSS 將在整個頁面中共享。

最后,在 yumcha-portal 中實現內聯和 shadow DOM 微應用的邏輯很簡單。我們獲取給定的微應用的內容(這個微應用由服務器邏輯作為 HTML template 元素插入頁面),克隆它,然后將它附加到 LitElement 中稱為 renderRoot 的位置(一般來說是元素的 shadow DOM,至於內聯,也就是非 shadow DOM 的情況也可以設置到元素自身,也就是 this)。

可是等等!宏應用服務器提供的內容是整個 HTML 頁面。我們不能將帶有 html、head 和 body 標簽的微應用 HTML 頁面插入到宏應用的某個 HTML 頁面中,顯然不行。

我們利用 template 標簽的怪癖來解決這個問題,在這個標簽中包裝了從宏應用服務器獲取到的宏應用內容。事實上,當現代瀏覽器遇到 template 標記時,盡管它們沒有“執行”它,但還是會 " 解析 " 它,這樣就移除了諸如 html 、 head 和 body 標記之類的無效內容,同時保留其內部內容。因此 head 中的 script 和 link 標記以及 body 的內容都會留下來,這正是我們想要的,這樣就可以將微應用內容正常插入頁面了。

 

微前端架構:細節決定成敗

如果(a)事實證明微前端是一種更好的架構方法,並且(b)我們可以弄清楚如何在實現它們的過程中滿足當今 Web 領域的眾多實際需求,微前端就可以在 Web 應用的生態系統中打下基礎。

關於第一個問題,沒有人聲稱微前端是適用於所有用例的架構。特別是,單個團隊沒有什么理由采用微前端來開發新內容。下面這個問題留給讀者思考:在哪些環境中,哪些類型的應用采用微前端模式比其他方法效果更好?

在實現和可行性方面,我們已經意識到有很多細節需要關注,其中包括本文沒有提到的一些細節,尤其是身份驗證和安全性、代碼重復和 seo 等。盡管如此,我希望本文為微前端提供一種基本的實現方法,這種方法進一步完善后就可以滿足實際需求。

資源搜索網站大全 https://www.renrenfan.com.cn

基礎知識

什么是微前端?

微前端是一種新模式,其中 Web 應用程序 UI(前端)由一些半獨立的片段組成,可以由不同的團隊使用不同的技術來構建。微前端架構類似一種后端架構,其中后端由一些半獨立的微服務組成。

什么是微前端架構?

微前端架構為微前端框架的結構元素提供了方法。它還定義了它們之間的關系,控制 UI 片段的組裝和通信方式,以實現最佳的開發人員和用戶體驗。

微服務可以有 UI 嗎?

答案是肯定的。微前端模式通常采用的方法是將微前端的一個片段(可能實現為微前端 Web 組件)與微服務配對以提供其 UI。

如何定義微服務?

一個微服務是體系結構中的一個元素,在該體系結構中,應用程序被構造為一些互操作服務的集合。如果前端采用微前端模式,則一個微服務可以與一個微前端配對。

微前端和 Web 組件之間有什么關系?

微型前端和 Web 組件(自定義元素)可能有幾種關系。Web 組件提供了一種基於自然標記的方式來描述組成微前端應用的微應用。微前端應用中的各個微應用本身可以使用 Web 組件構建。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM