開源 | 攜程度假零成本微前端框架-零界

陶然陶然發表於2022-11-01

   一、 前言

  1.1 微前端的含義

  在研發一個系統的初期,我們可以把所有程式碼放到一個專案中。隨著企業的發展,業務邏輯越發複雜和專業化,又會細分出不同的研發團隊,獨立負責其中某一部分。

  每個開發團隊有他們各自的迭代節奏,很難在耦合的同一個專案中,滿足所有團隊的需求。我們很自然地會將整個系統拆解到多個子應用/子專案中,他們可以獨立開發、獨立部署,但共同協作支撐了系統的整體功能。

  當上述系統拆解過程,發生在後端時,它被稱之為——微服務;當它發生在前端時,則被稱之為——微前端。

  從某種意義上,微前端是預設值,不需要額外的努力。瀏覽器一開始就實現了透過超連結的方式,支援多個 HTML 頁面之間跳轉。

  Tim Berners-Lee, a British scientist, invented the World Wide Web (WWW) in 1989, while working at CERN. The Web was originally conceived and developed to meet the demand for automated information-sharing between scientists in universities and institutes around the world.

  1989年,英國科學家蒂姆-伯納斯-李在歐洲核子研究中心工作時發明了全球資訊網(WWW)。全球資訊網最初是為了滿足世界各地大學和研究所的科學家之間自動分享資訊的需求而構思和開發的。

  Web 自它被發明開始,就已經是一種服務於跨團隊(不同大學、不同科學組織)之間的溝通與協作的資訊科技。

  但是,樸素的頁面跳轉,往往會在頁面過渡階段產生白屏,在體驗上不能滿足我們的需求。

  因此,當我們說“微前端”時,我們想要達到的目標是:

  不同的前端團隊,可以獨立開發和部署他們的應用,滿足自己的迭代需求

  多個前端子應用之間的協作與切換,不應該產生不可接受的使用者體驗下降

  1.2 微前端的型別

  我們可以把微前端按照其拆解的顆粒度,分成:

  頁面級微前端(page-level):每個子應用獨享一個頁面,子應用之間的切換就是頁面之間的跳轉/切換。

  區域級微前端(section-level):在同一個頁面中,存在兩類區域:

  a. 共享區域,如頂部選單欄、側邊欄等,由所有子應用共享。

  b. 切換區域,通常作為主體內容呈現,子應用在該區域做區域性切換。

  頁面級微前端(page-level)是瀏覽器的預設功能,但體驗不佳;因此,當前大部分微前端框架,致力於區域級微前端(section-level),代表框架有 Qiankun,Single-Spa 等。

  區域級微前端的主要實現思路,可以粗略概括如下:

  代理或劫持 window 環境,讓多個子應用及其依賴的前端框架,可以互不干涉地獨立執行

  每個子應用註冊了“建立”與“銷燬”等生命週期,等待主應用根據 url 去驅動和排程它們

  區域級微前端(section-level)可以很好地解決某一類微前端場景(如複雜的後臺系統),子應用恰好擁有相同的介面風格,甚至相同的 Layout,如頂部選單欄、側邊欄等模組,只有內容主體部分有差異。

  然而,在另一些場景中,我們可能仍然需要頁面級微前端(page-level)。

  子應用之間擁有不同的 UI 風格,甚至不同的 Layout,它們之間的切換,就是整頁的切換,而不是區域性的切換。

  我們不希望子應用為了迎合區域級微前端(section-level)的接入要求,而做出巨大的調整,甚至改變開發方式。

  子應用需要同時存在,並且可以在切換過程中,以滑入/滑出的動畫方式轉場,在回退過程中,可以自動保持捲軸位置等。

  etc。

  今天我們要介紹的——零界微前端,就屬於上述頁面級微前端(page-level),它克服了子應用切換過程的體驗問題。

   二、 零界介紹

  2.1 設計理念

  成本可控。接入成本不應該隨著應用的接入數量增加而指數級地上升,接入 2 個應用和接入 100 個應用考慮的問題應該是一致的。

  真正的技術無關。無論應用使用的是什麼技術棧、渲染方式是SSR還是CSR、應用型別是SPA還是MPA,都可以無縫接入。

  零耦合。微應用和主應用之間、微應用和微應用之間,完全沒有依賴關係。應用的接入和退出不會對應用本身和已經接入的應用帶來任何副作用。

  2.2 基本工作原理

  零界作為頁面級微前端(page-level)解決方案,在架構上和區域級微前端(section-level)大體一致,但在實現方式上有所不同。

  零界採用經典的基座應用 + 配置的方式來管理子應用。

  在零界中,基座又叫做shell 。shell 只做兩件事:存放微應用和排程微應用。

  所有微應用都載入在iframe中,零界透過 shell 管理多個iframe的載入和切換。

  然而,iframe 會帶來路由不同步的問題。零界透過 history api 如 pushState 和 replaceState,將當前啟用的頁面的地址,同步到瀏覽器位址列裡的 location 中,保持了URL 一致。

  與市面上微前端框架最大的不同是,在零界中沒有生命週期、Event Bus等複雜的概念,而是監聽微應用的跳轉行為,透過將跳轉記錄儲存在瀏覽器中, 把所有的微應用串聯起來。每一次微應用的跳轉,新的頁面會以 iframe 的形式載入至零界微前端,並且不會立即釋放之前微應用的記憶體,可以快速回退。為了避免過多的 iframe 導致頁面卡頓,零界限制了 iframe的最大數量。

  特點:

  無需改造原有程式碼。技術棧無關,無需擔心前端開發的難度。

  幾乎零接入成本。每個頁面只需引入一個 script 檔案,即可加入零界微前端機制。

  無重新整理切換頁面。提供無重新整理頁面切換的 SPA 體驗,給使用者一致性的體驗。

  安全可靠。所有頁面可隨時退出零界微前端機制,迴歸原始狀態。

  狀態同步。重新整理頁面不會丟失路由狀態,頁面回退更快展示,並保留前一頁的捲軸以及頁面狀態。

  隔離。完全隔離了每個頁面的css和js,避免了各個應用之間的變數汙染。

  2.3 為什麼是 iframe

  構建區域級微前端(section-level)時,由於 iframe 使用簡單、自帶程式級別隔離等特性,許多開發者都曾考慮使用iframe 構建微前端,但最終都不約而同地放棄了這個方案。

  讓我們結合下圖,再回顧下利用 iframe 構建區域級微前端(section-level)可能會帶來的具體問題。  

區域級微前端頁面示意圖

  (1)DOM 割裂嚴重。蒙層只能覆蓋其中一個微應用(一塊藍色區域),無法遮住整個應用(整個粉色區域);

  (2)通訊困難。不同的微應用同時存在於一個頁面,微應用之間需要額外的通訊,而 iframe 只能透過 postmessage 傳遞序列化的訊息,無法滿足需求;

  (3)載入慢。一個頁面中通常存在多個微應用,微應用會頻繁掛載、解除安裝,iframe 每一次載入都是一次上下文的重新構建;

  (4)路由狀態丟失。重新整理頁面後 iframe 會回到首次載入的狀態;

  可以看出,這些痛點是由 iframe 自帶的特性導致的,不只是針對區域級微前端(section-level),而是使用 iframe 時要考慮的通用性問題。

  現在,我們再站在頁面級微前端(page-level)的角度,逐一思考上面的問題:  

零界微前端頁面示意圖

  (1)DOM 割裂嚴重;無需解決✅ 如上圖所示,所有微應用的展示都是全屏的,不存在蒙層無法全域性展開的問題。

  (2)通訊困難;無需解決✅ 各個微應用之間原本就屬於完全不同的應用,所以並不用側重於應用之間的通訊。

  (3)載入慢;無需解決✅ 在頁面級微前端(page-level)中,每次進入頁面只會載入一個微應用(iframe)。

  (4)路由狀態丟失;問題同樣存在於頁面級微前端

  也就是說,我們只需要解決瀏覽器歷史記錄同步的問題,就可以最大化利用 iframe 的特性,這就是零界選擇 iframe 管理微應用的原因。

   三、 如何使用

  3.1 基本使用

  如上圖所示,假設我們現在需要做到上面展示的home Page,page A,page B 和 page C 這4個頁面無重新整理切換的效果,應該如何實現呢?

  如果他們是同一個應用的不同元件,則可以透過 React 或 Vue 的 TransiitonGroup 等元件快速實現。

  但是,如果他們是 4 個樸素的HTML頁面/應用,可能很難透過傳統前端框架實現,甚至,大多數區域級微前端(section-level)也無法完成。

  而在零界中,每個微應用都是全屏的,分別存放在 iframe 裡,可以透過操作 iframe 的方式來操作微應用,就像把樣式疊加在普通的 DOM 元素上一樣。

  零界針對 H5 頁面模擬了 Native App 中 WebView 切換的機制,也就是上圖的切換效果,接入零界即可開箱即用。

  讓我們來看下如何搭建零界微前端。

  第 一 步,建立零界shell。

  假設 4 個頁面的地址分別為:

  localhost:3000/demo/index.html

  localhost:3000/demo/pageA.html

  localhost:3000/demo/pageB.html

  localhost:3000/demo/pageC.html  

  如上圖所示,無需透過 npm/yarn 安裝,也無需呼叫任何函式,只需要對一個普通的HTML頁面做兩個改動就可以完成 shell 的搭建:

  (1)設定接入零界的微應用的匹配路徑。

  (2)引入零界shell指令碼,引入後就可獲得零界的能力。

  第二步,接入零界。

  在 4 個應用的 HTML中,分別在 head 標籤裡寫入下面的程式碼  

  我們在接入零界的微應用上,也只做了兩個改動:

  (1)配置開啟/關閉零界。

  (2)引入零界 page 指令碼。

  以上就是構建零界微前端的所需的所有程式碼。

  透過這樣接入有以下 3 個好處:

  (1)沒有學習成本,直接引入

  (2)不影響應用本身的 SEO

  (3)在零界中以子應用執行和應用獨立執行沒有區別,他們的路由路徑一致

  另外,我們可以發現,當微應用不能匹配 shell 中配置的路徑,或者微應用關閉了零界時,都無法接入零界。

  所以,當一個應用接入零界後導致無法正常訪問時,可以透過配置化的方式遠端關閉零界,這個頁面就會退化為普通頁面,而不必等待 shell 去改變配置。並且,這樣既不影響零界中已有的微應用跳轉,也不影響零界中的微應用跳轉至這個頁面。

  3.2 零界進階

  上文展示了樸素頁面的切換,體驗了零界在 H5 頁面的滑入滑出的效果。然而樸素頁面並不能滿足我們實際的需求。

  想象一下這樣一個場景:有多個 CSR 應用,他們共享同一個 Sidebar,但擁有不同的 Content,直接展示它們都會有一段白屏,我們希望在切換時,消除白屏,直接看到更完整內容的頁面。

  這是一個常見的 B 端專案最佳化需求,區域級微前端(section-level)和頁面級微前端(page-level)都可以提供解決方案。

  在現代web開發模式中,通常將頁面中的內容按功能、區域劃分為不同的元件,以提高程式碼複用性、擴充套件性。因此 Sidebar 和 Content 可以視為兩個不同的元件。

  區域級微前端(section-level)和頁面級微前端(page-level)對應用中的元件有不同的處理方式,產生了不同的最佳化策略:

  區域級微前端(section-level)以元件(區域)為單位,拆分原應用,並重構元件。之後,會從元件的角度,考慮如何在基座應用中主動掛載、解除安裝,達到想要的效果。

  頁面級微前端(page-level)以頁面為單位,在不改動原有應用元件的情況下,聚合所有應用。所以聚合之後,會從應用的角度,考慮如何被動式地對內部元件進行最佳化。

  透過區域級微前端解決,大概分為 4 步:

  (1)將每個應用中的 Sidebar 和 Content 拆分出來。

  (2)把每個 Content 作為一個微應用單獨部署,並配置基礎資訊、新增生命週期。

  (3)將 Sidebar 直接放入基座應用中,或者,作為一個微應用單獨部署。

  (4)建立基座應用,註冊所有的微應用。

  在切換應用時,只需解除安裝前一個應用的 Content,載入下一個應用的 Content 既可,共享的 Sidebar 部分並沒有變動,完全模擬了在一個應用中的切換體驗。

  但是,受限於樣式隔離、執行效能、子應用保活等因素,需要在市面上現有的區域級微前端(section-level)解決方案中權衡,很難找到完全滿足訴求的方案。並且無論是哪種改造方案,都需要較高改造的成本,這個成本還會隨著應用的數量指數級上升。

  讓我們來看下頁面級微前端會如何解決。

  零界提供了另一種思路,不侵入式地改變原有應用的前提下,最佳化應用之間的互動。

  改造分為 2 步:

  (1)建立零界 shell,配置接入微應用的路徑

  (2)在所有接入的應用中,引入零界 page 指令碼

  至此,和之前展示的樸素頁面切換效果一致,但是頁面的跳轉還是產生割裂感。  

  為了提升使用者體驗,在零界微前端切換頁面時,頂部會展現 Progressbar,表示頁面切換的進度。

  並且,受 Puppeteer 和 Playwright 這兩個e2e測試工具的啟發,零界模擬了裡面的 waitFor 功能,如上圖所示,表示等待 Sidebar 元件展示到頁面後,再進行頁面切換。這樣當多個應用在擁有相同 Sidebar 的頁面之間切換時,Sidebar 的部分在視覺上是固定的,只有 Content 發生變化,透過這種方式在多頁應用中獲得沉浸式的體驗。

  不僅如此,還可以透過 timeout 設定最長等待時間,一旦超過等待時間,頁面則會強制切換。

  這種最佳化方式帶來以下幾個好處:

  應用的 Content 和 Sidebar 的互動,並不需要額外的機制,因為它們本來就是同一個應用的不同元件。

  應用不必須攜帶相同的 Sidebar,隨著業務的發展需要可以更靈活地決定自己的 UI,零界不會是應用擴充套件的瓶頸。

  讓我們來對比下最佳化前後的效果,為了能更直觀地感受其中的差異,我們把網速調整為高速3G。

  未經過任何最佳化,每個頁面都是不同的應用(網速:高速3G)

  經過零界最佳化後(網速:高速3G)

  可以非常明顯地看出,經過零界最佳化後,多頁應用的跳轉更為流暢,並且支援快速回退頁面。

  細心的讀者可能發現,這兩個動圖的 URL 不一致。這裡僅展示零界帶來的最佳化效果,透過本地 Node 代理伺服器完成零界跳轉,所以和應用原有 URL 不同。在開發企業級專案時,通常不存在這個問題,可以透過SLB等方式快速解決。

  另外,值得一提的是,零界文件也是基於零界微前端構建的,可以直接體驗零界在 MPA 中切換的效果,有興趣的話可以檢視零界文件。

   總結

  至此,我們介紹了零界的基本原理和使用方式,其適用場景可以概括如下:

  資源整合。組合多個已有的中大型應用,無需重構。

  MPA最佳化。將 MPA 的跳轉提升至 SPA 的體驗。

  H5端體驗最佳化。低成本透過框架的能力達到 WebView 的切換效果。

  雖然零界是頁面級微前端(page-level)解決方案,但這並不表示它和區域級微前端(section-level)是衝突的。如果你想快速體驗微前端,或者你的專案由於現有微前端框架可能帶過高的成本而遲遲沒有落地,那麼可以先嚐試使用零界,在零界中可以隨時退出,不會帶來任何副作用。若嘗試後不能滿足需求,再考慮接入顆粒度更細緻的區域級微前端(section-level)。

  如果你對這個專案感興趣,歡迎一起來為零界做貢獻。

來自 “ 攜程技術 ”, 原文作者:工業聚、樂文;原文連結:http://server.it168.com/a2022/1028/6770/000006770793.shtml,如有侵權,請聯絡管理員刪除。

相關文章