攜程度假零成本微前端框架-零界
一、 前言
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)。