阿里妹導讀
我們對微前端框架的內容做了一個詳細的介紹,并從零開始用Typescript實現了微前端的基本功能。
本文我們首先實現一個可進行子應用注冊和資源加載的微前端框架,實現在一個vue3主應用中加載3個不同技術棧(vue2、react15、react16)的子應用,并且頁面上渲染出各個子應用的內容;?
然后,我們對該微前端框架實現擴展,實現
運行環境隔離(沙箱)
css樣式隔離
應用間通訊(含父子通信、子應用間通信)
全局狀態管理(全局store的簡單使用)
利用應用緩存和預加載子應用提高加載性能
一、前置準備
再開發我們自己的微前端框架之前,我們需要做一定的架構設計準備。我們考慮微前端架構設計時的整體思路,并畫出項目架構圖。
1.1 微前端框架實現思路
采用路由分發式(本文使用的是hash模式)
主應用控制路由匹配和子應用加載,共享依賴加載
子應用做功能,并接入主應用實現主子控制和聯動
1.2 分析框架& 項目架構圖
首先分析需求:
1.主應用功能:
a.注冊子應用;
b.加載、渲染子應用
c.路由匹配(activeWhen, rules- 由框架判斷)
d.獲取數據(公共依賴,通過數據做鑒權處理)
e.通信(父子通信、子父通信)
2.子應用功能:
a.渲染
b.監聽通信(主應用傳遞過來的數據)
3.微前端框架功能
a.子應用的注冊
b.有開始內容(應用加載完成)
c.路由更新判斷
d.匹配對應的子應用
e.加載子應用的內容
f.完成所有依賴項的執行
g.將子應用渲染在固定的容器內
h.公共事件的管理
i.異常的捕獲和報錯
j.全局的狀態管理的內容
k.沙箱的隔離
l.通信機制
4.服務端的功能:提供數據服務?
整體項目架構圖如下:
二、開發微前端框架
本節我們開始開發主子應用并且實現微前端框架的基礎功能
* 首先,我們實現主子應用的開發:按照我們的實際項目需求進行子應用的搭建和改造工作,每種子應用改造的方式大同小異;并且開發主應用,主應用起著整體的調度工作,按照對應的路由匹配規則渲染對應的子應用
* 然后我們實現微前端框架的基礎功能,包括:應用注冊、路由攔截、主應用生命周期添加、微前端生命周期添加、加載和解析html及js、渲染、執行腳本文件等內容。
2.1 準備子應用
技術選型
本文采用Vue3技術棧開發主應用,并準備了3個不同技術棧的子應用:
子應用:
vue2子應用(實現home主頁)
React15子應用(博客頁)
React16子應用(照片頁)
項目目錄結構
注:在開發微前端框架前,我們首先需要準備3個子應用,子應用的具體實現并非重點,完整代碼可見:blog-website-mircroFE-demo:https://code.alibaba-inc.com/sunxiaochun.sxc/blog-website-mircroFE-demo,項目目錄結構如下:
子應用改造接入微前端
當我們有了幾個子應用后,需要對其進行一些改造,從而使其能接入微前端。
設置啟動腳本
當新建好3個子應用后,我們的目錄結構是這樣的
以啟動react16子應用為例,cd react16 &&yarn start 只能啟動單個react子應用,我們需要配置一個命令來一次性啟動所有子項目:
這樣,在根目錄下執行yarn start即可一次性啟動3個子應用
2.2 主應用開發
主應用負責所有子應用的卸載、更新和加載整個流程,主應用是鏈接子應用和微前端框架的工具。
構建主應用基本頁面
主應用需要負責整體頁面布局,使用vue3開發主應用mAIn,其主體框架如下:
可以看到,主應用在頁面中設置了一個子應用的容器
子應用內容
,在這個區域中,我們展示不同的子應用內容。
在主應用中進行子應用注冊
有了主應用之后,首先需求在主應用中注冊子應用的信息。需要存儲的子應用信息:
name:子應用唯一id
activeRule:激活狀態(即在哪些路由下渲染該子應用)
container: 渲染容器(子應用要放在哪個容器里顯示)
entry 子應用的資源入口:(從哪里獲取子應用的文件)
通過主應用注冊子應用:設置一個subNavList存儲子應用信息的注冊信息,本文的3個子應用的注冊信息如下
然后編寫微前端框架中注冊子應用的方法,將子應用注冊進微前端框架(這里為了簡單直接掛載到window)里
最后在主應用中注冊子應用
2.3 實現路由攔截
有了子應用列表后,我們需要啟動微前端,以便來渲染對應的子應用,本節實現的是:主應用控制路由匹配和子應用加載
路由攔截方法
下面我們編寫微前端中路由攔截方法,實現思路:
監聽路由切換 & 重寫路由切換事件pushState和replaceState
并且監聽瀏覽器的前進/后退按鈕window.onpopstate
在啟動微前端框架時調用我們重寫的路由監聽方法
根據路由查找子應用
子應用注冊時保存了子應用列表的信息,對于當前路由,需要找到其對應的子應用。如下。我們編寫一個根據當前路由獲取子應用的通用方法
然后,我們編寫啟動微前端框架的路由查找方法,實現當進入項目,對于首個展示的子應用,先驗證當前子應用列表是否為空,不為空根據route匹配找到當前對應的子應用,如果子應用存在,則跳轉到子應用對應的url。
2.4 生命周期
實現路由攔截后,對于如何掛載和卸載該路由下的子應用,就需要去實現一套生命周期。
子應用生命周期
子應用通常有下面三個生命周期:
1.bootstrap:開始加載應用,用于配置子應用的全局信息等
2.mount:應用進行掛載,用來渲染子應用
3.unmount:應用進行卸載,用于銷毀子應用
這是一個協議接入,只要子應用實現了boostrap、mount和unmount這三個生命周期狗子,有這三個函數導出,我們的框架就可以知道如何加載這個子應用。
主應用生命周期
主應用的生命周期主要有三個:
1.beforeLoad:掛載子應用前,開始加載
2.mounted 掛載子應用后,渲染完成,
3.destoryed 卸載子應用卸載完成,
我們改寫微前端框架提供的方法,加入生命周期的內容
在主應用中利用微前端提供的注冊微應用的方法registerMicroApps中設置主應用的生命周期,并通過主應用生命周期去控制子應用的內容顯示
微前端框架生命周期
微前端的生命周期:如果路由變化時,對子應用進行對應的銷毀和加載操作
首先,編寫微前端框架監聽子應用是否做了切換的方法isTurnChild、以及根據路由獲取子應用的方法findAppByRoute
其次,編寫微前端框架的生命周期lifeCycle方法
實現思路是:微前端掛載到主應用,執行注冊函數時,傳入的就是主應用的生命周期,遍歷執行這個主應用的所有生命周期,微前端框架就可以實現將子應用注冊到主應用。
這樣,當微前端框架監聽到對應的子應用切換時,就執行微前端的生命周期
最終效果:當我們切換路由時(例如從vue2子應用切換到react15子應用時),控制臺打印如下:
2.5 獲取需要展示的頁面
為了展示子應用頁面,首先需要做的:主應用獲取子應用的生命周期,結構,方法,文件等。這樣主應用才能控制子應用的渲染和加載。
主應用的生命周期有三個:beforeLoad開始加載、mounted 渲染完成、destoryed 卸載完成。實際上,我們在主應用生命周期beforeLoad中去獲取頁面內容(加載資源):
子應用顯示頁面本質上是通過get請求?.NETwork中的doc)去獲取頁面信息,由此我們就可以在微前端框架中通過get請求去獲取到子應用對應的頁面,根據url通過fetch拿到頁面內容,最后再將內容賦值給對應的容器就可以顯示子應用對應的頁面了,
但是直接賦值給容器,容器是沒法解析html中的標簽,對于link和script(src,js代碼)元素,需要專門提取出來這些元素進行處理
加載和解析html
因為所有的網站都是以HTML作為入口文件的,這份HTML中實際已經包含了子應用的所有信息(網頁結構、js與css資源等),在微前端框架中,可以通過找到HTML中的靜態資源并加載,從而渲染出對應的子應用。
有了加載和解析子應用的HTML的方法,就需要在加載子應用的生命周期中進行調用
加載和執行js
上面我們只加載了HTML資源,但實際上子應用除了dom資源外,還有script資源也需加載。
實現思路:在getResource方法中我們遞歸尋找元素,將 link、script元素找出來并做對應的解析處理即可。對于dom資源,直接進行渲染,對于script資源有兩種情況:
1.內部script腳本
2.外部scriptUrl鏈接資源
我們分別進行處理
結果如下:?
congratulations!
到現在我們就實現了在一個vue3主應用中加載3個不同技術棧(vue2、react15、react16)的子應用,并且成功在頁面上展示了出來!
三、微前端框架-輔助功能
上文我們已經解決了微前端中應用的加載和切換,本節我們給微前端添加其他輔助功能,例如:預加載、應用通訊、全局store等功能
3.1 運行環境隔離 - 沙箱
為了避免應用間發生沖突,不同子應用之間的運行環境應該進行隔離,防止全局變量的互相污染。沙箱有兩種實現:快照沙箱 & 代理沙箱
快照沙箱
快照沙箱就是在應用沙箱掛載和卸載的時候記錄快照,在應用切換時依據快照來恢復環境。
具體實現思路是:記錄當前執行的子應用的變量,當子應用切換的時候,將變量置為初始值。
代理沙箱
代理沙箱是利用ES6的proxy去代理window,監聽window的改變,是更現代化的沙箱方法。
具體實現思路:設置一個空對象defaultValue去儲存子應用的變量
當window改變時,將改變值以key,value的形式存儲到defaultValue中;當需要獲取window屬性值的時候,也在代理的get中去返回defaultValue對應的值
沙箱銷毀的時候inactive也只需要將defaultValue置為{}
3.2 CSS樣式隔離
利用沙箱解決了JS之間的副作用沖突,接下來我們需要解決CSS之間的沖突,為CSS做樣式隔離。
常用樣式隔離方法有:
1.css modules:利用webpack配置打包機制進行css模塊化處理,通過編譯生成不沖突的選擇器名
2.shadow dom :創建個新的元素進行包裹,但語法較新,兼容性較差,設置shadow dom的步驟:
a.設置mode 利用attachShadow得到shadow
b.為shadow dom添加內容
3.minicss (本框架選用):一個webpack插件,該插件將css打包成單獨的文件,然后頁面通過link進行引用
4.csss in js:將應用的css樣式寫在JAVAScript文件里,最終生成不沖突的選擇器。
3.3 應用間通信
對于應用通訊,有兩種實現方式:props和customevent
基于Props的方式
這里有一個父子通信的實例場景:子應用動態控制主應用nav的顯示與隱藏。如下,在react16子應用中編寫了login登陸頁面,在該頁面中不應顯示導航條nav?
實現如下:首先,子應用需要一個控制主應用中nav顯示和隱藏的方法。所以我們在主應用的store中維護一個navStatus屬性,表示導航條nav是否顯示和隱藏至,暴露一個修改該navStatus屬性的方法changeNav
將該狀態綁定至主應用導航條中,
然后,將主應用的所有store內容(包含navStatus)用Props的方式傳遞給所有子應用。
修改子應用的mounted生命周期方法,注冊子應用時將含有navStatus屬性的appInfo傳遞給子應用
最后,以react16子應用為例,在react16子應用中拿到主應用傳遞來的信息,并在子應用的渲染中隱藏nav。
上述是一個主子應用通信的實例,對于子應用之間的通訊,也可以利用基于props的方式:子應用1 跟父應用交互,父應用再傳遞給子應用2。
基于CustomerEvent的方式
另一種應用間通訊的模型是掛一個事件總線,應用之間不直接相互交互,都去一個事件總線上注冊和監聽事件。實現思路:通過new CustomEvent對象去監聽事件(on)和觸發事件(emit),如下:
首先,利用customEvent創建Custom類
有了Custom類后, 我們在微前端框架中創建一個custom對象,為其添加事件監聽方法test,并將其掛載到全局window上
這樣,在子應用里,我們就可以通過window.custom來獲取custom對象。
3.4 全局狀態管理 - 全局store
建立微前端中的全局狀態管理基于發布訂閱模式,通過主應用監聽某個方法、子應用添加訂閱者來監聽到一些全局狀態的改變。
具體實現思路是:利用store保存數據,observer管理訂閱者(subscribeStore方法用于添加訂閱者),并提供獲取和更新store的getStore和updateStore的方法。
有了全局狀態,如何在主應用中引入即可。若要在子應用中也使用store,將store掛載到window上,子應用直接訪問window.store即可,使用全局變量也是應用通訊的一種方式。
3.5 提高加載性能
應用緩存
當前,當切換不同的子應用時,都會重新加載頁面,若頁面資源非常多,加載會比較緩慢,所以需要在微前端框架中對應用進行緩存,提升加載性能。
應用緩存的實現思路是:
定義一個cache對象,根據子應用的appName來做緩存
如果當前子應用的html已經解析并且加載過,就返回已經加載過的內容。如果沒有,則走正常加載和解析的流程
預加載子應用
提升加載性能的另一個思路是預加載子應用,即在啟動微前端框架時,先獲取當前要加載的子應用,再預加載剩下的所有的子應用。
這樣,在做子應用切換時,避免一定的延遲感~
四、寫在最后
在之前的內容中,我們對微前端框架的內容做了一個詳細的介紹,并從零開始用Typescript實現了微前端的基本功能,對這樣的一個簡易微前端框架,仍有可以擴展和繼續學習的地方。例如
如何實現微前端框架的自動發布?如何通過npm來發布我們編寫的框架?能否創建一個自動部署平臺來實現應用的自動化部署?
現有熱門的微前端框架(qiankun、single-spa、icestark)是如何實現微前端框架的應用注冊&加載、沙箱隔離、全局狀態管理、預加載這些功能?
參考資料:
?從零到一實現企業級微前端框架,保姆級教學?:https://juejin.cn/post/7004661323124441102
?微前端連載 6/7:微前端框架 - qiankun 大法好?:https://juejin.cn/post/6846687602439897101
?從零打造微前端框架:實戰“汽車資訊平臺”項目?:https://coding.imooc.com/class/chapter/520.html#Anchor