本文將針對微前端框架 qiankun
的源碼進行深入解析,在源碼講解之前,我們先來了解一下什麼是 微前端
。
微前端
是一種類似於微服務的架構,它將微服務的理念應用於瀏覽器端,即將單頁面前端應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。各個前端應用還可以獨立開發、獨立部署。同時,它們也可以在共享組件的同時進行並行開發——這些組件可以通過 NPM
或者 Git Tag、Git Submodule
來管理。
qiankun(乾坤)
就是一款由螞蟻金服推出的比較成熟的微前端框架,基於 single-spa
進行二次開發,用於將 Web 應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。(見下圖)
那麼,話不多說,我們的源碼解析正式開始。
初始化全局配置 - start(opts)
我們從兩個基礎 API - registerMicroApps(apps, lifeCycles?) - 註冊子應用
和 start(opts?) - 啓動主應用
開始,由於 registerMicroApps
函數中設置的回調函數較多,並且讀取了 start
函數中設置的初始配置項,所以我們從 start
函數開始解析。
我們從 start
函數開始解析(見下圖):
我們對 start
函數進行逐行解析:
第 196 行
:設置window
的__POWERED_BY_QIANKUN__
屬性爲true
,在子應用中使用window.__POWERED_BY_QIANKUN__
值判斷是否運行在主應用容器中。第 198~199 行
:設置配置參數(有默認值),將配置參數存儲在importLoaderConfiguration
對象中;第 201~203 行
:檢查prefetch
屬性,如果需要預加載,則添加全局事件single-spa:first-mount
監聽,在第一個子應用掛載後預加載其他子應用資源,優化後續其他子應用的加載速度。第 205 行
:根據singularMode
參數設置是否爲單實例模式。第 209~217 行
:根據jsSandbox
參數設置是否啓用沙箱運行環境,舊版本需要關閉該選項以兼容 IE。(新版本在單實例模式下默認支持 IE,多實例模式依然不支持 IE)。第 222 行
:調用了single-spa
的startSingleSpa
方法啓動應用,這個在single-spa
篇我們會單獨剖析,這裏可以簡單理解爲啓動主應用。
從上面可以看出,start
函數負責初始化一些全局設置,然後啓動應用。這些初始化的配置參數有一部分將在 registerMicroApps
註冊子應用的回調函數中使用,我們繼續往下看。
註冊子應用 - registerMicroApps(apps, lifeCycles?)
registerMicroApps
函數的作用是註冊子應用,並且在子應用激活時,創建運行沙箱,在不同階段調用不同的生命週期鉤子函數。(見下圖)
從上面可以看出,在 第 70~71 行
處 registerMicroApps
函數做了個處理,防止重複註冊相同的子應用。
在 第 74 行
調用了 single-spa
的 registerApplication
方法註冊了子應用。
我們直接來看 registerApplication
方法,registerApplication
方法是 single-spa
中註冊子應用的核心函數。該函數有四個參數,分別是
name(子應用的名稱)
回調函數(activeRule 激活時調用)
activeRule(子應用的激活規則)
props(主應用需要傳遞給子應用的數據)
這些參數都是由 single-spa
直接實現,這裏可以先簡單理解爲註冊子應用(這個我們會在 single-spa
篇展開說)。在符合 activeRule
激活規則時將會激活子應用,執行回調函數,返回一些生命週期鉤子函數(見下圖)。
注意,這些生命週期鉤子函數屬於
single-spa
,由single-spa
決定在何時調用,這裏我們從函數名來簡單理解。(bootstrap
- 初始化子應用,mount
- 掛載子應用,unmount
- 卸載子應用)
如果你還是覺得有點懵,沒關係,我們通過一張圖來幫助理解。(見下圖)
獲取子應用資源 - import-html-entry
我們從上面分析可以看出,qiankun
的 registerMicroApps
方法中第一個入參 apps - Array<RegistrableApp<T>>
有三個參數 name、activeRule、props
都是交給 single-spa
使用,還有 entry
和 render
參數還沒有用到。
我們這裏需要關注 entry(子應用的 entry 地址)
和 render(子應用被激活時觸發的渲染規則)
這兩個還沒有用到的參數,這兩個參數延遲到 single-spa
子應用激活後的回調函數中執行。
那我們假設此時我們的子應用已激活,我們來看看這裏做了什麼。(見下圖)
從上圖可以看出,在子應用激活後,首先在 第 81~84 行
處使用了 import-html-entry
庫從 entry
進入加載子應用,加載完成後將返回一個對象(見下圖)
我們來解釋一下這幾個字段
字段 | 解釋 |
---|---|
template |
將腳本文件內容註釋後的 html 模板文件 |
assetPublicPath |
資源地址根路徑,可用於加載子應用資源 |
getExternalScripts |
方法:獲取外部引入的腳本文件 |
getExternalStyleSheets |
方法:獲取外部引入的樣式表文件 |
execScripts |
方法:執行該模板文件中所有的 JS 腳本文件,並且可以指定腳本的作用域 - proxy 對象 |
我們先將 template 模板
、getExternalScripts
和 getExternalStyleSheets
函數的執行結果打印出來,效果如下(見下圖):
從上圖我們可以看到我們外部引入的三個 js
腳本文件,這個模板文件沒有外部 css
樣式表,對應的樣式表數組也爲空。
然後我們再來分析 execScripts
方法,該方法的作用就是指定一個 proxy
(默認是 window
)對象,然後執行該模板文件中所有的 JS
,並返回 JS
執行後 proxy
對象的最後一個屬性(見下圖 1)。在微前端架構中,這個對象一般會包含一些子應用的生命週期鉤子函數(見下圖 2),主應用可以通過在特定階段調用這些生命週期鉤子函數,進行掛載和銷燬子應用的操作。
在 qiankun
的 importEntry
函數中還傳入了配置項 getTemplate
,這個其實是對 html
目標文件的二次處理,這裏就不作展開了,有興趣的可以自行去了解一下。
主應用掛載子應用 HTML 模板
我們回到 qiankun
源碼部分繼續看(見下圖)
從上圖看出,在 第 85~87 行
處,先對單實例進行檢測。在單實例模式下,新的子應用掛載行爲會在舊的子應用卸載之後纔開始。
在 第 88 行
中,執行註冊子應用時傳入的 render
函數,將 HTML Template
和 loading
作爲入參,render
函數的內容一般是將 HTML
掛載在指定容器中(見下圖)。
在這個階段,主應用已經將子應用基礎的 HTML
結構掛載在了主應用的某個容器內,接下來還需要執行子應用對應的 mount
方法(如 Vue.$mount
)對子應用狀態進行掛載。
此時頁面還可以根據 loading
參數開啓一個類似加載的效果,直至子應用全部內容加載完成。
沙箱運行環境 - genSandbox
我們回到 qiankun
源碼部分繼續看,此時還是子應用激活時的回調函數部分(見下圖)
在 第 90~98 行
是 qiankun
比較核心的部分,也是幾個子應用之間狀態獨立的關鍵,那就是 js
的沙箱運行環境。如果關閉了 useJsSandbox
選項,那麼所有子應用的沙箱環境都是 window
,就很容易對全局狀態產生污染。
我們進入到 genSandbox
內部,看看 qiankun
是如何創建的 (JS)沙箱運行環境
。(見下圖)
從上圖可以看出 genSandbox
內部的沙箱主要是通過是否支持 window.Proxy
分爲 LegacySandbox
和 SnapshotSandbox
兩種。
擴展閱讀:多實例還有一種
ProxySandbox
沙箱,這種沙箱模式目前看來是最優方案。由於其表現與舊版本略有不同,所以暫時只用於多實例模式。
ProxySandbox
沙箱穩定之後可能會作爲單實例沙箱使用。
LegacySandbox
我們先來看看 LegacySandbox
沙箱是怎麼進行狀態隔離的(見下圖)
我們來分析一下 LegacySandbox
類的幾個屬性:
字段 | 解釋 |
---|---|
addedPropsMapInSandbox |
記錄沙箱運行期間新增的全局變量 |
modifiedPropsOriginalValueMapInSandbox |
記錄沙箱運行期間更新的全局變量 |
currentUpdatedPropsValueMap |
記錄沙箱運行期間操作過的全局變量。上面兩個 Map 用於 關閉沙箱 時還原全局狀態,而 currentUpdatedPropsValueMap 是在 激活沙箱 時還原沙箱的獨立狀態 |
name |
沙箱名稱 |
proxy |
代理對象,可以理解爲子應用的 global/window 對象 |
sandboxRunning |
當前沙箱是否在運行中 |
active |
激活沙箱,在子應用掛載時啓動 |
inactive |
關閉沙箱,在子應用卸載時啓動 |
constructor |
構造函數,創建沙箱環境 |
我們現在從 window.Proxy
的 set
和 get
屬性來詳細講解 LegacySandbox
是如何實現沙箱運行環境的。(見下圖)
注意:子應用沙箱中的
proxy
對象(第 62 行
)可以簡單理解爲子應用的window
全局對象(代碼如下),子應用對全局屬性的操作就是對該proxy
對象屬性的操作,帶着這份理解繼續往下看吧。
// 子應用腳本文件的執行過程:
eval(
// 這裏將 proxy 作爲 window 參數傳入
// 子應用的全局對象就是該子應用沙箱的 proxy 對象
(function(window) {
/* 子應用腳本文件內容 */
})(proxy)
);
在 第 65~72 行中
,當調用 set
向子應用 proxy/window
對象設置屬性時,所有的屬性設置和更新都會先記錄在 addedPropsMapInSandbox
或 modifiedPropsOriginalValueMapInSandbox
中,然後統一記錄到
currentUpdatedPropsValueMap
中。
在 第 73 行
中修改全局 window
的屬性,完成值的設置。
當調用 get
從子應用 proxy/window
對象取值時,會直接從 window
對象中取值。對於非構造函數的取值將會對 this
指針綁定到 window
對象後,再返回函數。
LegacySandbox
的沙箱隔離是通過激活沙箱時還原子應用狀態,卸載時還原主應用狀態(子應用掛載前的全局狀態)實現的,具體實現如下(見下圖)。
從上圖可以看出:
第 37 行
:在激活沙箱時,沙箱會通過currentUpdatedPropsValueMap
查詢到子應用的獨立狀態池(沙箱可能會激活多次,這裏是沙箱曾經激活期間被修改的全局變量),然後還原子應用狀態。第 44~45 行
:在關閉沙箱時,通過addedPropsMapInSandbox
刪除在沙箱運行期間新增的全局變量,通過modifiedPropsOriginalValueMapInSandbox
還原沙箱運行期間被修改的全局變量,從而還原到子應用掛載前的狀態。
從上面的分析可以得知,LegacySandbox
的沙箱隔離機制利用快照模式實現,我們畫一張圖來幫助理解(見下圖)
多實例沙箱 - ProxySandbox
ProxySandbox
是一種新的沙箱模式,目前用於多實例模式的狀態隔離。在穩定後以後可能會成爲 單實例沙箱
,我們來看看 ProxySandbox
沙箱是怎麼進行狀態隔離的(見下圖)
我們來分析一下 ProxySandbox
類的幾個屬性:
字段 | 解釋 |
---|---|
updateValueMap |
記錄沙箱中更新的值,也就是每個子應用中獨立的狀態池 |
name |
沙箱名稱 |
proxy |
代理對象,可以理解爲子應用的 global/window 對象 |
sandboxRunning |
當前沙箱是否在運行中 |
active |
激活沙箱,在子應用掛載時啓動 |
inactive |
關閉沙箱,在子應用卸載時啓動 |
constructor |
構造函數,創建沙箱環境 |
我們現在從 window.Proxy
的 set
和 get
屬性來詳細講解 ProxySandbox
是如何實現沙箱運行環境的。(見下圖)
注意:子應用沙箱中的
proxy
對象可以簡單理解爲子應用的window
全局對象(代碼如下),子應用對全局屬性的操作就是對該proxy
對象屬性的操作,帶着這份理解繼續往下看吧。
// 子應用腳本文件的執行過程:
eval(
// 這裏將 proxy 作爲 window 參數傳入
// 子應用的全局對象就是該子應用沙箱的 proxy 對象
(function(window) {
/* 子應用腳本文件內容 */
})(proxy)
);
當調用 set
向子應用 proxy/window
對象設置屬性時,所有的屬性設置和更新都會命中 updateValueMap
,存儲在 updateValueMap
集合中(第 38 行
),從而避免對 window
對象產生影響(舊版本則是通過 diff
算法還原 window
對象狀態快照,子應用之間的狀態是隔離的,而父子應用之間 window
對象會有污染)。
當調用 get
從子應用 proxy/window
對象取值時,會優先從子應用的沙箱狀態池 updateValueMap
中取值,如果沒有命中才從主應用的 window
對象中取值(第 49 行
)。對於非構造函數的取值將會對 this
指針綁定到 window
對象後,再返回函數。
如此一來,ProxySandbox
沙箱應用之間的隔離就完成了,所有子應用對 proxy/window
對象值的存取都受到了控制。設置值只會作用在沙箱內部的 updateValueMap
集合上,取值也是優先取子應用獨立狀態池(updateValueMap
)中的值,沒有找到的話,再從 proxy/window
對象中取值。
相比較而言,ProxySandbox
是最完備的沙箱模式,完全隔離了對 window
對象的操作,也解決了快照模式中子應用運行期間仍然會對 window
造成污染的問題。
我們對 ProxySandbox
沙箱畫一張圖來加深理解(見下圖)
SnapshotSandbox
在不支持 window.Proxy
屬性時,將會使用 SnapshotSandbox
沙箱,我們來看看其內部實現(見下圖)
我們來分析一下 SnapshotSandbox
類的幾個屬性:
字段 | 解釋 |
---|---|
name |
沙箱名稱 |
proxy |
代理對象,此處爲 window 對象 |
sandboxRunning |
當前沙箱是否激活 |
windowSnapshot |
window 狀態快照 |
modifyPropsMap |
沙箱運行期間被修改過的 window 屬性 |
constructor |
構造函數,激活沙箱 |
active |
激活沙箱,在子應用掛載時啓動 |
inactive |
關閉沙箱,在子應用卸載時啓動 |
SnapshotSandbox
的沙箱環境主要是通過激活時記錄 window
狀態快照,在關閉時通過快照還原 window
對象來實現的。(見下圖)
我們先看 active
函數,在沙箱激活時,會先給當前 window
對象打一個快照,記錄沙箱激活前的狀態(第 38~40 行
)。打完快照後,函數內部將 window
狀態通過 modifyPropsMap
記錄還原到上次的沙箱運行環境,也就是還原沙箱激活期間(歷史記錄)修改過的 window
屬性。
在沙箱關閉時,調用 inactive
函數,在沙箱關閉前通過遍歷比較每一個屬性,將被改變的 window
對象屬性值(第 54 行
)記錄在 modifyPropsMap
集合中。在記錄了 modifyPropsMap
後,將 window
對象通過快照 windowSnapshot
還原到被沙箱激活前的狀態(第 55 行
),相當於是將子應用運行期間對 window
造成的污染全部清除。
SnapshotSandbox
沙箱就是利用快照實現了對 window
對象狀態隔離的管理。相比較 ProxySandbox
而言,在子應用激活期間,SnapshotSandbox
將會對 window
對象造成污染,屬於一個對不支持 Proxy
屬性的瀏覽器的向下兼容方案。
我們對 SnapshotSandbox
沙箱畫一張圖來加深理解(見下圖)
掛載沙箱 - mountSandbox
我們繼續回到這張圖,genSandbox
函數不僅返回了一個 sandbox
沙箱,還返回了一個 mount
和 unmount
方法,分別在子應用掛載時和卸載時的時候調用。
我們先看看 mount
函數內部(見下圖)
首先,在 mount
內部先激活了子應用沙箱(第 26 行
),在沙箱啓動後開始劫持各類全局監聽(第 27 行
),我們這裏重點看看 patchAtMounting
內部是怎麼實現的。(見下圖)
patchAtMounting
內部調用了下面四個函數:
patchTimer(計時器劫持)
patchWindowListener(window 事件監聽劫持)
patchHistoryListener(window.history 事件監聽劫持)
patchDynamicAppend(動態添加 Head 元素事件劫持)
上面四個函數實現了對 window
指定對象的統一劫持,我們可以挑一些解析看看其內部實現。
計時器劫持 - patchTimer
我們先來看看 patchTimer
對計時器的劫持(見下圖)
從上圖可以看出,patchTimer
內部將 setInterval
進行重載,將每個啓用的定時器的 intervalId
都收集起來(第 23~24 行
),以便在子應用卸載時調用 free
函數將計時器全部清除(見下圖)。
我們來看看在子應用加載時的 setInterval
函數驗證即可(見下圖)
從上圖可以看出,在進入子應用時,setInterval
已經被替換成了劫持後的函數,防止全局計時器泄露污染。
動態添加樣式表和腳本文件劫持 - patchDynamicAppend
patchWindowListener
和 patchHistoryListener
的實現都與 patchTimer
實現類似,這裏就不作複述了。
我們需要重點對 patchDynamicAppend
函數進行解析,這個函數的作用是劫持對 head
元素的操作(見下圖)
從上圖可以看出,patchDynamicAppend
主要是對動態添加的 style
樣式表和 script
標籤做了處理。
我們先看看對 style
樣式表的處理(見下圖)
從上圖可以看出,主要的處理邏輯在 第 68~74 行
,如果當前子應用處於激活狀態(判斷子應用的激活狀態主要是因爲:當主應用切換路由時可能會自動添加動態樣式表,此時需要避免主應用的樣式表被添加到子應用
head節點中導致出錯
),那麼動態 style
樣式表就會被添加到子應用容器內(見下圖),在子應用卸載時樣式表也可以和子應用一起被卸載,從而避免樣式污染。同時,動態樣式表也會存儲在 dynamicStyleSheetElements
數組中,在後面還會提到其用處。
我們再來看看對 script
腳本文件的處理(見下圖)
對動態 script
腳本文件的處理較爲複雜一些,我們也來解析一波:
在 第 83~101 行
處對外部引入的 script
腳本文件使用 fetch
獲取,然後使用 execScripts
指定 proxy
對象(作爲 window
對象)後執行腳本文件內容,同時也觸發了 load
和 error
兩個事件。
在 第 103~106 行
處將註釋後的腳本文件內容以註釋的形式添加到子應用容器內。
在 第 109~113 行
是對內嵌腳本文件的執行過程,就不作複述了。
我們可以看出,對動態添加的腳本進行劫持的主要目的就是爲了將動態腳本運行時的 window
對象替換成 proxy
代理對象,使子應用動態添加的腳本文件的運行上下文也替換成子應用自身。
HTMLHeadElement.prototype.removeChild
的邏輯就是多加了個子應用容器判斷,其他無異,就不展開說了。
最後我們來看看 free
函數(見下圖)
這個 free
函數與其他的 patches(劫持函數)
實現不太一樣,這裏緩存了一份 cssRules
,在重新掛載的時候會執行 rebuild
函數將其還原。這是因爲樣式元素 DOM
從文檔中刪除後,瀏覽器會自動清除樣式元素表。如果不這麼做的話,在重新掛載時會出現存在 style
標籤,但是沒有渲染樣式的問題。
卸載沙箱 - unmountSandbox
我們再回到 mount
函數本身(見下圖)
從上圖可以看出,在 patchAtMounting
函數中劫持了各類全局監聽,並返回瞭解除劫持的 free
函數。在卸載應用時調用 free
函數解除這些全局監聽的劫持行爲(見下圖)
從上圖可以看到 sideEffectsRebuilders
在 free
後被返回,在 mount
的時候又將被調用 rebuild
重建動態樣式表。這塊環環相扣,是稍微有點繞,沒太看明白的同學可以翻上去再看一遍。
到這裏,qiankun
的最核心部分-沙箱機制,我們就已經解析完畢了,接下來我們繼續剖析別的部分。
在這裏我們畫一張圖,對沙箱的創建過程進行一個總梳理(見下圖)
註冊內部生命週期函數
在創建好了沙箱環境後,在 第 100~106 行
註冊了一些內部生命週期函數(見下圖)
在上圖中,第 106 行
的 mergeWith
方法的作用是將內置的生命週期函數與傳入的 lifeCycles
生命週期函數。
這裏的
lifeCycles
生命週期函數指的是全子應用共享的生命週期函數,可用於執行多個子應用間相同的邏輯操作,例如加載效果
之類的。(見下圖)
除了外部傳入的生命週期函數外,我們還需要關注 qiankun
內置的生命週期函數做了些什麼(見下圖)
我們對上圖的代碼進行逐一解析:
第 13~15 行
:在加載子應用前beforeLoad
(只會執行一次)時注入一個環境變量,指示了子應用的public
路徑。第 17~19 行
:在掛載子應用前beforeMount
(可能會多次執行)時可能也會注入該環境變量。第 23~30 行
:在卸載子應用前beforeUnmount
時將環境變量還原到原始狀態。
通過上面的分析我們可以得出一個結論,我們可以在子應用中獲取該環境變量,將其設置爲 __webpack_public_path__
的值,從而使子應用在主應用中運行時,可以匹配正確的資源路徑。(見下圖)
觸發 beforeLoad
生命週期鉤子函數
在註冊完了生命週期函數後,立即觸發了 beforeLoad
生命週期鉤子函數(見下圖)
從上圖可以看出,在 第 108 行
中,觸發了 beforeLoad
生命週期鉤子函數。
隨後,在 第 110 行
執行了 import-html-entry
的 execScripts
方法。指定了腳本文件的運行沙箱(jsSandbox
),執行完子應用的腳本文件後,返回了一個對象,對象包含了子應用的生命週期鉤子函數(見下圖)。
在 第 112~121 行
對子應用的生命週期鉤子函數做了個檢測,如果在子應用的導出對象中沒有發現生命週期鉤子函數,會在沙箱對象中繼續查找生命週期鉤子函數。如果最後沒有找到生命週期鉤子函數則會拋出一個錯誤,所以我們的子應用一定要有 bootstrap, mount, unmount
這三個生命週期鉤子函數才能被 qiankun
正確嵌入到主應用中。
這裏我們畫一張圖,對子應用掛載前的初始化過程做一個總梳理(見下圖)
進入到 mount
掛載流程
在一些初始化配置(如 子應用資源、運行沙箱環境、生命週期鉤子函數等等
)準備就緒後,qiankun
內部將其組裝在一起,返回了三個函數作爲 single-spa
內部的生命週期函數(見下圖)
single-spa
內部的邏輯我們後面再展開說,這裏我們可以簡單理解爲 single-spa
內部的三個生命週期鉤子函數:
bootstrap
:子應用初始化時調用,只會調用一次;mount
:子應用掛載時調用,可能會調用多次;unmount
:子應用卸載時調用,可能會調用多次;
我們可以看出,在 bootstrap
階段調用了子應用暴露的 bootstrap
生命週期函數。
我們這裏對 mount
階段進行展開,看看在子應用 mount
階段執行了哪些函數(見下圖)
我們進行逐行解析:
第 127~133 行
:對單實例模式進行檢測。在單實例模式下,新的子應用掛載行爲會在舊的子應用卸載之後纔開始。(由於這裏是串行順序執行,所以如果某一處發生阻塞的話,會阻塞所有後續的函數執行)第 134 行
:執行註冊子應用時傳入的render
函數,將HTML Template
和loading
作爲入參。這裏一般是在發生了一次unmount
後,再次進行mount
掛載行爲時將HTML
掛載在指定容器中(見下圖)由於初始化的時候已經調用過一次
render
,所以在首次調用mount
時可能已經執行過一次render
方法。在下面的代碼中也有對重複掛載的情況進行判斷的語句 -
if (frame.querySelector("div") === null
,防止重複掛載子應用。
第 135 行
:觸發了beforeMount
全局生命週期鉤子函數;第 136 行
:掛載沙箱,這一步中激活了對應的子應用沙箱,劫持了部分全局監聽(如setInterval
)。此時開始子應用的代碼將在沙箱中運行。(反推可知,在beforeMount
前的部分全局操作將會對主應用造成污染,如setInterval
)第 137 行
:觸發子應用的mount
生命週期鉤子函數,在這一步通常是執行對應的子應用的掛載操作(如ReactDOM.render、Vue.$mount
。(見下圖)
第 138 行
:再次調用render
函數,此時loading
參數爲false
,代表子應用已經加載完成。第 139 行
:觸發了afterMount
全局生命週期鉤子函數;第 140~144 行
:在單實例模式下設置prevAppUnmountedDeferred
的值,這個值是一個promise
,在當前子應用卸載時纔會被resolve
,在該子應用運行期間會阻塞其他子應用的掛載動作(第 134 行
);
我們在上面很詳細的剖析了整個子應用的 mount
掛載流程,如果你還沒有搞懂的話,沒關係,我們再畫一個流程圖來幫助理解。(見下圖)
進入到 unmount
卸載流程
我們剛纔梳理了子應用的 mount
掛載流程,我們現在就進入到子應用的 unmount
卸載流程。在子應用激活階段, activeRule
未命中時將會觸發 unmount
卸載行爲,具體的行爲如下(見下圖)
從上圖我們可以看出,unmount
卸載流程要比 mount
簡單很多,我們直接來梳理一下:
第 148 行
:觸發了beforeUnmount
全局生命週期鉤子函數;第 149 行
:這裏與mount
流程的順序稍微有點不同,這裏先執行了子應用的unmount
生命週期鉤子函數,保證子應用仍然是運行在沙箱內,避免造成狀態污染。在這裏一般是對子應用的一些狀態進行清理和卸載操作。(如下圖,銷燬了剛纔創建的vue
實例)
第 150 行
:卸載沙箱,關閉了沙箱的激活狀態。第 151 行
:觸發了afterUnmount
全局生命週期鉤子函數;第 152 行
:觸發render
方法,並且傳入的appContent
爲空字符串,此處可以清空主應用容器內的內容。第 153~156 行
:當前子應用卸載完成後,在單實例模式下觸發prevAppUnmountedDeferred.resolve()
,使其他子應用的掛載行爲得以繼續進行,不再阻塞。
我們對 unmount
卸載流程也畫一張圖,幫助大家理解(見下圖)。
總結
到這裏,我們對 qiankun
框架的總流程梳理就差不多了。這裏應該做個總結,大家看了這麼多文字,估計大家也看累了,最後用一張圖對 qiankun
的總流程進行總結吧。
彩蛋
展望
傳統的雲控制檯應用,幾乎都會面臨業務快速發展之後,單體應用進化成巨石應用的問題。我們要如何維護一個巨無霸中臺應用?
上面這個問題引出了微前端架構理念,所以微前端的概念也越來越火,我們團隊最近也在嘗試轉型微前端架構。
工慾善其事必先利其器,所以本文針對 qiankun
的源碼進行解讀,在分享知識的同時也是幫助自己理解。
這是我們團隊對微前端架構的最佳實踐(見下圖),如果有需求的話,可以在評論區留言,我們會考慮出一篇《微前端框架 qiankun
最佳實踐》來幫助大家搭建一套微前端架構。
最後一件事
這篇文章我花了大約半個月的時間來進行排版、梳理、畫圖,堅持下來一路寫完確實很不容易。
如果您已經看到這裏了,希望您還是點個贊再走吧~
如果本文對您有幫助的話,請點個贊和收藏吧!
您的點贊是對作者的最大鼓勵,也可以讓更多人看到本篇文章!
如果對 《微前端框架
qiankun最佳實踐》
有興趣的話,還請在評論區留言告訴作者吧!