京東出品微前端框架 MicroApp 介紹與落地實踐
作者團隊:平臺業務研發部 - iPaaS 團隊(馬國華、肖寧、周廉貴、秦傑)
前言
Micro App 是由京東零售 iPaaS 前端研發團隊推出的一款微前端框架,不同於目前流行的開源框架,它從組件化的思維實現微前端,旨在降低上手難度、提升工作效率。它是目前市場上接入微前端成本最低的方案,並且提供了 js 沙箱、樣式隔離、元素隔離、預加載、資源地址補全、插件系統、數據通信等一系列完善的功能。Micro App 與技術棧無關,也不和業務綁定,可以用於任何前端框架和業務。
本篇文章中我們會從業務背景、實現思路介紹 Micro App,也會詳細介紹它的使用方式和技術原理。
業務背景
隨着這些年互聯網的飛速發展,很多企業的 web 應用在持續迭代中功能越來越複雜,參與的人員、團隊不斷增多,導致項目出現難以維護的問題,這種情況 PC 端尤其常見,許多研發團隊也在找尋一種高效管理複雜應用的方案,於是微前端被提及 的越來越頻繁。
微前端並不是一項新的技術,而是一種架構理念,它將單一的 web 應用拆解成多個可以獨立開發、獨立運行、獨立部署的小型應用,並將它們整合爲一個應用。
在實際業務中,我們也遇到同樣的問題,並且在不同的業務場景下嘗試了各種解決方案,如 iframe、npm 包、微前端框架,並對各種方案的優劣進行了對比。
**iframe:**在所有微前端方案中,iframe 是最穩定的、上手難度最低的,但它有一些無法解決的問題,例如性能低、通信複雜、雙滾動條、彈窗無法全局覆蓋,它的成長性不高,只適合簡單的頁面渲染。
**npm 包:**將子應用封裝成 npm 包,通過組件的方式引入,在性能和兼容性上是最優的方案,但卻有一個致命的問題就是版本更新,每次版本發佈需要通知接入方同步更新,管理非常困難。
**微前端框架:**流行的微前端框架有 single-spa 和 qiankun,它們將維護成本和功能上達到一種平衡,是目前實現微前端備受推崇的方案。
由於 iframe 和 npm 包存在問題理論上無法解決,在最初我們採用 qiankun 作爲解決方案,qiankun 是在 single-spa 基礎上進行了封裝,提供了 js 沙箱、樣式隔離、預加載等功能,並且與技術棧無關,可以兼容不同的框架。
0****1
業務訴求
qiankun 雖然優秀,但依然無法滿足我們的預期。第一個問題是在我們實際使用場景中,每個接入微前端的項目運行已久,且每個項目由不同的人員和團隊負責,如何降低對源代碼的侵入性,減少代碼修改和溝通成本,這是我們非常關心的點,所以我們需要一種比 qiankun 接入成本更小的方案。第二個問題是在多方應用接入的情況 下,沙箱並不能完美規避所有問題 (比如子午線的埋點衝突問題),但 qiankun 並沒有提供處理此類不可預料的問題的能力。在不停的摸索中,我們找到一種極致簡潔的實現思路,它像使用組件一樣簡單,只修改一點點代碼就可以接入微前端,並且還提供插件系統,賦予開發者靈活處理問題的能力。
實現思路
微前端分爲主應用和子應用,主應用也稱爲基座應用,是其它應用的容器載體,子應用則是被嵌入方。我們分別從主應用和子應用的角度出發,探尋一種更簡潔和有效的接入微前端的方式。
0****1
關於 qinkun 和 single-spa 的思考
在 single-spa 和 qiankun 中都是通過監聽 url change 事件,在路由變化時匹配到渲染的子應用並進行渲染。這種基於路由監聽渲染是 single-spa 最早實現的,作爲出現最早、最有影響力的微前端框架,single-spa 被很多框架和公司借鑑,也導致目前實現的微前端的方式大多是基於路由監聽。
同時 single-spa 要求子應用修改渲染邏輯並暴露出三個方法:bootstrap、mount、unmount,分別對應初始化、渲染和卸載,這也導致子應用需要對入口文件進行修改。這個特點也被 qiankun 繼承下來,並且需要對 webpack 配置進行一些修改。
基於路由監聽的實現方式和對子應用入口文件以及 webpack 配置的修改是必須的嗎?
其實並不是,微前端的核心在於資源加載與渲染,iframe 的渲染方式就是一個典型,只要能夠實現一種元素隔離的功能並且路由符合要求,子應用理論上不需要修改代碼就可以嵌入另外一個頁面渲染,我們試圖從這個角度中找到不一樣的實現思路。
0****2
微前端的組件化
要想簡化微前端的實現步驟,必須摒棄舊的實現思路,探索出不同的道路。
我們借鑑了 WebComponent 的思想,以此爲基礎推出另一種更加組件化的實現方式:類 WebComponent + HTML Entry。
**HTML Entry:**是指設置 html 作爲資源入口,通過加載遠程 html,解析其 DOM 結構從而獲取 js、css 等靜態資源來實現微前端的渲染,這也是 qiankun 目前採用的渲染方案。
**WebComponent:**web 原生組件,它有兩個核心組成部分:CustomElement 和 ShadowDom。CustomElement 用於創建自定義標籤,ShadowDom 用於創建陰影 DOM,陰影 DOM 具有天然的樣式隔離和元素隔離屬性。由於 WebComponent 是原生組件,它可以在任何框架中使用,理論上是實現微前端最優的方案。但 WebComponent 有一個無法解決的問題 - ShadowDom 的兼容性非常不好,一些前端框架在 ShadowDom 環境下無法正常運行,尤其是 react 框架。
**類 WebComponent:**就是使用 CustomElement 結合自定義的 ShadowDom 實現
WebComponent 基本一致的功能。
由於 ShadowDom 存在的問題,我們採用自定義的樣式隔離和元素隔離實現 ShadowDom 類似的功能,然後將微前端應用封裝在一個 CustomElement 中,從而模擬實現了一個類 WebComponent 組件,它的使用方式和兼容性與 WebComponent 一致,同時也避開了 ShadowDom 的問題。並且由於自定義 ShadowDom 的隔離特性,Micro App 不需要像 single-spa 和 qiankun 一樣要求子應用修改渲染邏輯並暴露出方法,也不需要修改 webpack 配置。
我們通過上述方案封裝了一個自定義標籤 micro-app ,它的渲染機制和功能與 WebComponent 類似,開發者可以像使用 web 組件一樣接入微前端。它可以兼容任何框架,在使用方式和數據通信上也更加組件化,這顯著降低了基座應用的接入成本,並且由於元素隔離的屬性,子應用的改動量也大大降低。
使用方式
接下來我們將分別介紹主應用和子應用的接入方式。本篇文章都以 react 代碼進行舉例。
0****1
主應用
每個⾃定義標籤 micro-app 渲染後就是⼀個微前端的⼦應⽤,它的使⽤⽅式類似於 iframe 標籤。
我們需要給標籤傳遞三個基礎屬性:
-
name:名稱
-
url:⼦應⽤⻚⾯地址
-
baseurl:baseurl 是基座應⽤分配給⼦應⽤的路由前綴
使⽤⽅式如下:
0****2
⼦應⽤
如果⼦應⽤只有⼀個⻚⾯,沒有路由配置,則不需要做任何修改。
如果⼦應⽤是多⻚⾯,只需要修改路由配置,添加路由前綴。
如下:
window.baseurl 是由基座應⽤傳⼊的路由前綴,在⾮微前端環境下,這個值爲 undefined。
完成以上配置即可實現微前端的渲染,對源碼的改動量很少。當然 Micro App 還提供了其它⼀些能⼒,如插件系統、數據通信,我們接下來做詳細介紹。
核⼼原理
Micro App 的核⼼功能在 CustomElement 基礎上進⾏構建,CustomElement ⽤於創建⾃定義標籤,並提供了元素的渲染、卸載、屬性修改等鉤⼦函數,我們通過鉤⼦函數獲知微應⽤的渲染時機,並將⾃定義標籤作爲容器,微應⽤的所有元素和樣式作⽤域都⽆法逃離容器邊界,從⽽形成⼀個封閉的環境。
01
概念圖
02
渲染流程
通過⾃定義元素 micro-app 的⽣命週期函 connectedCallback 監聽元素被渲染,加載⼦應⽤的 html 並轉換爲 DOM 結構,遞歸查詢所有 js 和 css 等靜態資源並加載,設置元素隔離,攔截所有動態創建的 script、link 等標籤,提取標籤內容。將加載的 js 經過插件系統處理後放⼊沙箱中運⾏,對 css 資源進⾏樣式隔離,最後將格式化後的元素放⼊ micro-app 中,最終 micro-app 元素渲染爲⼀個微前端的⼦應⽤。在渲染的過程中,會執⾏開發者綁定的⽣命週期函數,⽤於進⼀步操作。
流程圖
03
元素隔離
元素隔離源於 ShadowDom 的概念,即 ShadowDom 中的元素可以和外部的元素重複但不會衝突,ShadowDom 只能對⾃⼰內部的元素進⾏操作。
Micro App 模擬實現了類似的功能,我們攔截了底層原型鏈上元素的⽅法,保證⼦應⽤只能對⾃⼰內部的元素進⾏操作,每個⼦應⽤都有⾃⼰的元素作⽤域。
元素隔離可以有效的防⽌⼦應⽤對基座應⽤和其它⼦應⽤元素的誤操作,常⻅的場景是多個應⽤的根元素都使⽤相同的 id,元素隔離可以保證⼦應⽤的渲染框架能夠正確找到⾃⼰的根元素。
概念圖
實際效果
如上圖所示, micro-app 元素內部渲染的就是⼀個⼦應⽤,它還有兩個⾃定義元素 micro-app-head 、micro-app-body ,這兩個元素的作⽤分別對應 html 中的 head 和 body 元素。⼦應⽤在原 head 元素中的內容和⼀些動態創建並插⼊ head 的 link、script 元素都會移動到 micro-app-head 中,在原 body 元素中的內容和⼀些動態創建並插⼊ body 的元素都會移動到 micro-app-body 中。這樣可以防⽌⼦應⽤的元素泄漏到全局,在進⾏元素查詢、刪除等操作時,只需要在 micro-app 內部進⾏處理,是實現元素隔離的重要基礎。
可以將 micro-app 理解爲⼀個內嵌的 html ⻚⾯,它的結構和功能都和 html ⻚⾯類似。
04
插件系統
微前端的使⽤場景⾮常複雜,即便有沙箱機制也⽆法避免所有的問題,所以我們提供了⼀套插件系統⽤於解決⼀些⽆法預知的問題。
插件可以理解爲符合特定規則的對象,對象中提供⼀個函數⽤於對資源進⾏處理,插件通常由開發者⾃定義。
插件系統的作⽤是對傳⼊的靜態資源進⾏初步處理,並依次調⽤符合條件的插件,將初步處理後的靜態資源作爲參數傳⼊插件,由插件對資源內容進⼀步的修改,並將修改後的內容返回。插件系統賦予開發者靈活處理靜態資源的能⼒,對有問題的資源⽂件進⾏修改。
插件系統本身是純淨的,不會對資源內容造成影響,它的作⽤是統籌各個插件如何執⾏,當開發者沒有設置插件時,則傳⼊和傳出的內容是⼀致的。
05
js 沙箱和樣式隔離
js 沙箱通過 Proxy 代理⼦應⽤的全局對象,防⽌應⽤之間全局變量的衝突,記錄或清空⼦應⽤的全局副作⽤函數,也可以向⼦應⽤注⼊全局變量⽤於定製化處理。
樣式隔離是指對⼦應⽤的 link 和 style 元素的 css 內容進⾏格式化處理,確保⼦應⽤的樣式只作⽤域⾃身,⽆法影響外部。
Micro App 借鑑了 qiankun 的 js 沙箱和樣式隔離⽅案,這也是⽬前應⽤⼴泛且成熟的⽅案。
06
預加載
Micro App 提供了預加載⼦應⽤的功能,它是基於 requestIdleCallback 實現的,預加載不會對基座應⽤和其它⼦應⽤的渲染速度造成影響,它會在瀏覽器空閒時間加載應⽤的靜態資源,在應⽤真正被渲染時直接從緩存中獲取資源並渲染。
07
資源地址補全
微前端中經常出現資源丟失的現象,原因是基座應⽤將⼦應⽤的資源加載到⾃⼰的⻚⾯渲染,如果⼦應⽤的靜態資源地址是相對地址,瀏覽器會以基座應⽤所在域名地址補全靜態資源,從⽽導致資源丟失。
資源地址補全就是將⼦應⽤靜態資源的相對地址補全爲絕對地址,保證地址指向正確的資源路徑,這種操作類似於 webpack 在運⾏時設置 publicPath。
08
生命週期
在微應⽤渲染時, micro-app 在不同渲染階段會發送不同的⽣命週期事件,基座應⽤可以通過監聽事件來進⾏相應的操作。
⽣命週期列表:
-
created:當 micro-app 標籤被創建後,加載資源之前執⾏。
-
beforemount:資源加載完成,正式渲染之前執⾏。
-
mounted:⼦應⽤已經渲染完成後執⾏
-
unmount:⼦應⽤卸載時執⾏。
-
error:當出現破壞性錯誤,⽆法繼續渲染時執⾏。
在卸載時,⼦應⽤也會接收到⼀個卸載的事件,⽤於執⾏卸載相關操作。
09
數據通信
數據通信是微前端中⾮常重要的功能,實現數據通信的技術⽅案很多,優秀的⽅案可以提升開發效率,減少試錯成本。我們也研究了 qiankun 等微前端框架數據通信的⽅式,但他們的實現⽅式並不適合我們,我們嘗試直接通過元素屬性傳遞複雜數據的形式實現數據通信。
對於前端研發⼈員最熟悉的是組件化的數據交互的⽅式,⽽⾃定義元素 micro-app 作爲類 WebComponent,通過組件屬性進⾏數據交互必然是最優的⽅式。但 MicroApp 在數據通信中遇到的最⼤的問題是⾃定義元素⽆法⽀持設置對象類型屬性,例如 <micro-app data={x: 1}> 會轉換爲 ,想要以組件化形式進⾏數據通信必須讓元素⽀持對象屬性。
爲了解決這個問題,我們重寫了 micro-app 元素原型鏈上屬性設置的⽅法,在 micro-app 元素設置對象屬性時將傳遞的值保存到數據中⼼,通過數據中⼼將值分發給⼦應⽤。
Micro App 中數據是綁定通信的,即每個 micro-app 元素只能與⾃⼰指向的⼦應⽤進⾏通信,這樣每個應⽤都有着清晰的數據鏈,可以避免數據的混亂,同時 Micro App 也⽀持全局通信,以便跨應⽤傳遞數據。
數據通信概念圖
框架對⽐
爲了更直觀的感受 Micro App 和其它框架的區別,我們使⽤⼀張圖進⾏對⽐。
從對⽐圖可以看出,⽬前開源的微前端框架中有的功能 Micro App 都有,並提供了⼀些它們不具備的功能,⽐如靜態資源地址補全,元素隔離,插件系統等。
業務實踐
Micro App 已經在京東內部的多個項⽬中使⽤,尤其是將⼀些⽼項⽬改造成微前端,在項⽬不受影響的情況下,即降低接⼊成本,⼜可以保證項⽬平穩運⾏,減⼩耦合。
下圖是在營銷活動中⼼使⽤的案例(圖中爲測試數據):
未來規劃
Micro App 以及⽬前市⾯上開源的微前端框架都是解決渲染的問題,即如何將多個應⽤融合爲微前端。但微前端的使⽤場景⾮常複雜,沒有完美的框架,雖然我們提供了插件系統⽤於解決這些不可預知的問題,但依然有⼀些問題有待解決。尤其是開發環境下,如何提⾼不同業務、不同團隊之間連調的效率,如何降低開發時可能遇到的隱形問題,這是我們未來要發展的⽅向。所以我們計劃在 Micro App 的基礎上定義⼀套微前端標準,作爲 B 端項⽬微前端場景下整體的解決⽅案。這其中包括 CLI 腳⼿架、前端框架、代碼結構、構建⼯具、代碼檢測、路由規範、發版部署、跨域請求等⼀系列規範,⽤來降低開發⻔檻,規避常⻅的問題,提升開發效率和代碼健壯性,讓開發者能夠專注於項⽬本身,⽽不⽤花費時間解決微前端可能出現的問題。
微前端架構圖
如果你對微前端也有興趣,歡迎加⼊我們:maguohua@jd.com,⼀起共建微前端標準。
導航
Micro App 官⽹地址:https://zeroing.jd.com/micro-app
iPaaS 官⽹地址:http://ipaas.jd.com/wiki
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6A6TqQpWgN1_KoxUMx3FFw