極致簡潔的微前端框架-京東MicroApp開源了

cangdu發表於2021-07-28

前言

MicroApp是一款基於類WebComponent進行渲染的微前端框架,不同於目前流行的開源框架,它從元件化的思維實現微前端,旨在降低上手難度、提升工作效率。它是目前市面上接入微前端成本最低的框架,並且提供了JS沙箱、樣式隔離、元素隔離、預載入、資源地址補全、外掛系統、資料通訊等一系列完善的功能。MicroApp與技術棧無關,也不和業務繫結,可以用於任何前端框架和業務。

本篇文章中我們會從業務背景、實現思路介紹MicroApp,也會詳細介紹它的使用方式和技術原理。

背景

隨著這些年網際網路的飛速發展,很多企業的web應用在持續迭代中功能越來越複雜,參與的人員、團隊不斷增多,導致專案出現難以維護的問題,這種情況PC端尤其常見,許多研發團隊也在找尋一種高效管理複雜應用的方案,於是微前端被提及的越來越頻繁。

微前端並不是一項新的技術,而是一種架構理念,它將單一的web應用拆解成多個可以獨立開發、獨立執行、獨立部署的小型應用,並將它們整合為一個應用。

在實際業務中,我們也遇到同樣的問題,並且在不同的業務場景下嘗試了各種解決方案,如iframe、npm包、微前端框架,並對各種方案的優劣進行了對比。

iframe:在所有微前端方案中,iframe是最穩定的、上手難度最低的,但它有一些無法解決的問題,例如效能低、通訊複雜、雙滾動條、彈窗無法全域性覆蓋,它的成長性不高,只適合簡單的頁面渲染。

npm包:將子應用封裝成npm包,通過元件的方式引入,在效能和相容性上是最優的方案,但卻有一個致命的問題就是版本更新,每次版本釋出需要通知接入方同步更新,管理非常困難。

微前端框架:流行的微前端框架有single-spa和qiankun,它們將維護成本和功能上達到一種平衡,是目前實現微前端備受推崇的方案。

由於iframe和npm包存在問題理論上無法解決,在最初我們採用qiankun作為解決方案,qiankun是在single-spa基礎上進行了封裝,提供了js沙箱、樣式隔離、預載入等功能,並且與技術棧無關,可以相容不同的框架。

業務訴求

qiankun雖然優秀,但依然無法滿足我們的預期。第一個問題是在我們實際使用場景中,每個接入微前端的專案執行已久,且每個專案由不同的人員和團隊負責,如何降低對原始碼的侵入性,減少程式碼修改和溝通成本,這是我們非常關心的點,所以我們需要一種比qiankun接入成本更小的方案。第二個問題是在多方應用接入的情況下,沙箱並不能完美規避所有問題,但qiankun處理此類不可預料的問題的能力並不是非常高效。在不停的摸索中,我們找到一種極致簡潔的實現思路,它像使用元件一樣簡單,只修改一點點程式碼就可以接入微前端,並且還提供外掛系統,賦予開發者靈活處理問題的能力。

image

實現思路

微前端分為主應用和子應用,主應用也稱為基座應用,是其它應用的容器載體,子應用則是被嵌入方。我們分別從主應用和子應用的角度出發,探尋一種更簡潔和有效的接入微前端的方式。

關於qinkun和single-spa的思考

在single-spa和qiankun中都是通過監聽url change事件,在路由變化時匹配到渲染的子應用並進行渲染。這種基於路由監聽渲染是single-spa最早實現的,作為出現最早、最有影響力的微前端框架,single-spa被很多框架和公司借鑑,也導致目前實現的微前端的方式大多是基於路由監聽。

同時single-spa要求子應用修改渲染邏輯並暴露出三個方法:bootstrap、mount、unmount,分別對應初始化、渲染和解除安裝,這也導致子應用需要對入口檔案進行修改。這個特點也被qiankun繼承下來,並且需要對webpack配置進行一些修改。

image

基於路由監聽的實現方式和對子應用入口檔案以及webpack配置的修改是必須的嗎?

其實並不是,微前端的核心在於資源載入與渲染,iframe的渲染方式就是一個典型,只要能夠實現一種元素隔離的功能並且路由符合要求,子應用理論上不需要修改程式碼就可以嵌入另外一個頁面渲染,我們試圖從這個角度中找到不一樣的實現思路。

微前端的元件化

要想簡化微前端的實現步驟,必須摒棄舊的實現思路,探索出不同的道路。

我們借鑑了WebComponent的思想,以此為基礎推出另一種更加元件化的實現方式:類WebComponent + HTML Entry。

image

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程式碼舉例

主應用

每個自定義標籤micro-app渲染後就是一個微前端的子應用,它的使用方式類似於iframe標籤。

我們需要給標籤傳遞三個基礎屬性:

  • name:名稱
  • url:子應用頁面地址
  • baseurl:baseurl是基座應用分配給子應用的路由字首

使用方式如下:

image

子應用

如果子應用只有一個頁面,沒有路由配置,則不需要做任何修改。

如果子應用是多頁面,只需要修改路由配置,新增路由字首。

如下:

window.__MICRO_APP_BASE_URL__是由基座應用下發的路由字首,在非微前端環境下,這個值為undefined

image

完成以上配置即可實現微前端的渲染,對原始碼的改動量很少。當然MicroApp還提供了其它一些能力,如外掛系統、資料通訊,我們接下來做詳細介紹。

核心原理

MicroApp 的核心功能在CustomElement基礎上進行構建,CustomElement用於建立自定義標籤,並提供了元素的渲染、解除安裝、屬性修改等鉤子函式,我們通過鉤子函式獲知微應用的渲染時機,並將自定義標籤作為容器,微應用的所有元素和樣式作用域都無法逃離容器邊界,從而形成一個封閉的環境。

概念圖

image

渲染流程

通過自定義元素micro-app的生命週期函式connectedCallback監聽元素被渲染,載入子應用的html並轉換為DOM結構,遞迴查詢所有js和css等靜態資源並載入,設定元素隔離,攔截所有動態建立的script、link等標籤,提取標籤內容。將載入的js經過外掛系統處理後放入沙箱中執行,對css資源進行樣式隔離,最後將格式化後的元素放入micro-app中,最終將micro-app元素渲染為一個微前端的子應用。在渲染的過程中,會執行開發者繫結的生命週期函式,用於進一步操作。

流程圖

image

元素隔離

元素隔離源於ShadowDom的概念,即ShadowDom中的元素可以和外部的元素重複但不會衝突,ShadowDom只能對自己內部的元素進行操作。

MicroApp模擬實現了類似的功能,我們攔截了底層原型鏈上元素的方法,保證子應用只能對自己內部的元素進行操作,每個子應用都有自己的元素作用域。

元素隔離可以有效的防止子應用對基座應用和其它子應用元素的誤操作,常見的場景是多個應用的根元素都使用相同的id,元素隔離可以保證子應用的渲染框架能夠正確找到自己的根元素。

概念圖

image

實際效果

image

如上圖所示,micro-app元素內部渲染的就是一個子應用,它還有兩個自定義元素 micro-app-headmicro-app-body,這兩個元素的作用分別對應html中的head和body元素。子應用在原head元素中的內容和一些動態建立並插入head的link、script元素都會移動到micro-app-head中,在原body元素中的內容和一些動態建立並插入body的元素都會移動到micro-app-body中。這樣可以防止子應用的元素洩漏到全域性,在進行元素查詢、刪除等操作時,只需要在micro-app內部進行處理,是實現元素隔離的重要基礎。

可以將micro-app理解為一個內嵌的html頁面,它的結構和功能都和html頁面類似。

外掛系統

微前端的使用場景非常複雜,即便有沙箱機制也無法避免所有的問題,所以我們提供了一套外掛系統用於解決一些無法預知的問題。

外掛可以理解為符合特定規則的物件,物件中提供一個函式用於對資源進行處理,外掛通常由開發者自定義。

外掛系統的作用是對傳入的靜態資源進行初步處理,並依次呼叫符合條件的外掛,將初步處理後的靜態資源作為引數傳入外掛,由外掛對資源內容進一步的修改,並將修改後的內容返回。外掛系統賦予開發者靈活處理靜態資源的能力,對有問題的資原始檔進行修改。

外掛系統本身是純淨的,不會對資源內容造成影響,它的作用是統籌各個外掛如何執行,當開發者沒有設定外掛時,則傳入和傳出的內容是一致的。

image

js沙箱和樣式隔離

js沙箱通過Proxy代理子應用的全域性物件,防止應用之間全域性變數的衝突,記錄或清空子應用的全域性副作用函式,也可以向子應用注入全域性變數用於定製化處理。

樣式隔離是指對子應用的link和style元素的css內容進行格式化處理,確保子應用的樣式只作用域自身,無法影響外部。

MicroApp借鑑了qiankun的js沙箱和樣式隔離方案,這也是目前應用廣泛且成熟的方案。

預載入

MicroApp 提供了預載入子應用的功能,它是基於requestIdleCallback實現的,預載入不會對基座應用和其它子應用的渲染速度造成影響,它會在瀏覽器空閒時間載入應用的靜態資源,在應用真正被渲染時直接從快取中獲取資源並渲染。

image

資源地址補全

微前端中經常出現資源丟失的現象,原因是基座應用將子應用的資源載入到自己的頁面渲染,如果子應用的靜態資源地址是相對地址,瀏覽器會以基座應用所在域名地址補全靜態資源,從而導致資源丟失。

資源地址補全就是將子應用靜態資源的相對地址補全為絕對地址,保證地址指向正確的資源路徑,這種操作類似於webpack在執行時設定publicPath。

image

生命週期

在微應用渲染時,micro-app元素在不同渲染階段會觸發相應的生命週期事件,基座應用可以通過監聽事件來進行相應的操作。
image

生命週期列表:

  • created:當micro-app標籤被建立後,載入資源之前執行。
  • beforemount:資源載入完成,正式渲染之前執行。
  • mounted:子應用已經渲染完成後執行
  • unmount:子應用解除安裝時執行。
  • error:當出現破壞性錯誤,無法繼續渲染時執行。

在解除安裝時,子應用也會接收到一個解除安裝的事件,用於執行解除安裝相關操作。

資料通訊

資料通訊是微前端中非常重要的功能,實現資料通訊的技術方案很多,優秀的方案可以提升開發效率,減少試錯成本。我們也研究了qiankun等微前端框架資料通訊的方式,但他們的實現方式並不適合我們,我們嘗試直接通過元素屬性傳遞複雜資料的形式實現資料通訊。

對於前端研發人員最熟悉的是元件化的資料互動的方式,而自定義元素micro-app作為類WebComponent,通過元件屬性進行資料互動必然是最優的方式。但MicroApp在資料通訊中遇到的最大的問題是自定義元素無法支援設定物件型別屬性,例如<micro-app data={x: 1}></micro-app> 會轉換為 <micro-app data='[object Object]'></micro-app>,想要以元件化形式進行資料通訊必須讓元素支援物件屬性。

為了解決這個問題,我們重寫了micro-app元素原型鏈上屬性設定的方法,在micro-app元素設定物件屬性時將傳遞的值儲存到資料中心,通過資料中心將值分發給子應用。

MicroApp中資料是繫結通訊的,即每個micro-app元素只能與自己指向的子應用進行通訊,這樣每個應用都有著清晰的資料鏈,可以避免資料的混亂,同時MicroApp也支援全域性通訊,以便跨應用傳遞資料。

資料通訊概念圖

image

框架對比

為了更直觀的感受Micro App和其它框架的區別,我們使用一張圖進行對比。
image
從對比圖可以看出,目前開源的微前端框架中有的功能 MicroApp都有,並提供了一些它們不具備的功能,比如靜態資源地址補全,元素隔離,外掛系統等。

業務實踐

MicroApp已經在公司內部多個專案中使用,表現良好,尤其是將一些老專案改造成微前端,在專案不受影響的情況下,即降低接入成本,又可以保證專案平穩執行,減小耦合。

為什麼開源?

當初我們團隊打算使用微前端時,調研了市面上實現微前端的框架,可供選擇的只有sigle-spa和qiankun。single-spa太過於基礎,對原有專案的改造過多,成本太高。剩下的只有qiankun,但因為接入很多老專案,在實際使用中出了很多問題,我們不得不對qiankun的原始碼進行大量的魔改。在此過程中,我們對微前端的實現方式產生了一些自己的想法,並將這些想法付諸實踐,於是有了MicroApp。

目前像qiankun類似提供完善功能的微前端框架太少了,當接入qiankun失敗時,沒有其他方案可供選擇,這是我們當初經歷過的痛。所以我們選擇將MicroApp開源,一是因為MicroApp有諸多創新點,可以更簡單的接入微前端,功能更加豐富,二是可以讓大家多一種選擇,沒有完美的微前端框架,只有選擇多了,才知道哪一個更適合自己。

如果你對這個專案感興趣,可以通過加入組織或提pull requests的方式參與共建,非常歡迎與期待你的加入。

導航

GitHub地址:https://github.com/micro-zoe/micro-app

官網地址:https://cangdu.org/micro-app

特別鳴謝:qiankun

相關文章