<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入淺出解析阿里成熟的微前端框架 qiankun 源碼【圖文并茂】

          共 13050字,需瀏覽 27分鐘

           ·

          2021-10-18 11:53

          來源:leaf(a1029563229?)

          https://github.com/a1029563229/blogs/blob/master/Source-Code/qiankun/1.md


          本文將針對(duì)微前端框架 qiankun 的源碼進(jìn)行深入解析,在源碼講解之前,我們先來了解一下什么是 微前端

          微前端 是一種類似于微服務(wù)的架構(gòu),它將微服務(wù)的理念應(yīng)用于瀏覽器端,即將單頁面前端應(yīng)用由單一的單體應(yīng)用轉(zhuǎn)變?yōu)槎鄠€(gè)小型前端應(yīng)用聚合為一的應(yīng)用。各個(gè)前端應(yīng)用還可以獨(dú)立開發(fā)、獨(dú)立部署。同時(shí),它們也可以在共享組件的同時(shí)進(jìn)行并行開發(fā)——這些組件可以通過 NPM 或者 Git Tag、Git Submodule 來管理。

          qiankun(乾坤) 就是一款由螞蟻金服推出的比較成熟的微前端框架,基于 single-spa 進(jìn)行二次開發(fā),用于將 Web 應(yīng)用由單一的單體應(yīng)用轉(zhuǎn)變?yōu)槎鄠€(gè)小型前端應(yīng)用聚合為一的應(yīng)用。(見下圖)

          qiankun

          那么,話不多說,我們的源碼解析正式開始。

          初始化全局配置 - start(opts)

          我們從兩個(gè)基礎(chǔ) API - registerMicroApps(apps, lifeCycles?) - 注冊(cè)子應(yīng)用start(opts?) - 啟動(dòng)主應(yīng)用 開始,由于 registerMicroApps 函數(shù)中設(shè)置的回調(diào)函數(shù)較多,并且讀取了 start 函數(shù)中設(shè)置的初始配置項(xiàng),所以我們從 start 函數(shù)開始解析。

          我們從 start 函數(shù)開始解析(見下圖):

          qiankun

          我們對(duì) start 函數(shù)進(jìn)行逐行解析:

          • 第 196 行:設(shè)置 window__POWERED_BY_QIANKUN__ 屬性為 true,在子應(yīng)用中使用 window.__POWERED_BY_QIANKUN__ 值判斷是否運(yùn)行在主應(yīng)用容器中。
          • 第 198~199 行:設(shè)置配置參數(shù)(有默認(rèn)值),將配置參數(shù)存儲(chǔ)在 importLoaderConfiguration 對(duì)象中;
          • 第 201~203 行:檢查 prefetch 屬性,如果需要預(yù)加載,則添加全局事件 single-spa:first-mount 監(jiān)聽,在第一個(gè)子應(yīng)用掛載后預(yù)加載其他子應(yīng)用資源,優(yōu)化后續(xù)其他子應(yīng)用的加載速度。
          • 第 205 行:根據(jù) singularMode 參數(shù)設(shè)置是否為單實(shí)例模式。
          • 第 209~217 行:根據(jù) jsSandbox 參數(shù)設(shè)置是否啟用沙箱運(yùn)行環(huán)境,舊版本需要關(guān)閉該選項(xiàng)以兼容 IE。(新版本在單實(shí)例模式下默認(rèn)支持 IE,多實(shí)例模式依然不支持 IE)。
          • 第 222 行:調(diào)用了 single-spastartSingleSpa 方法啟動(dòng)應(yīng)用,這個(gè)在 single-spa 篇我們會(huì)單獨(dú)剖析,這里可以簡單理解為啟動(dòng)主應(yīng)用。

          從上面可以看出,start 函數(shù)負(fù)責(zé)初始化一些全局設(shè)置,然后啟動(dòng)應(yīng)用。這些初始化的配置參數(shù)有一部分將在 registerMicroApps 注冊(cè)子應(yīng)用的回調(diào)函數(shù)中使用,我們繼續(xù)往下看。

          注冊(cè)子應(yīng)用 - registerMicroApps(apps, lifeCycles?)

          registerMicroApps 函數(shù)的作用是注冊(cè)子應(yīng)用,并且在子應(yīng)用激活時(shí),創(chuàng)建運(yùn)行沙箱,在不同階段調(diào)用不同的生命周期鉤子函數(shù)。(見下圖)

          qiankun

          從上面可以看出,在 第 70~71 行registerMicroApps 函數(shù)做了個(gè)處理,防止重復(fù)注冊(cè)相同的子應(yīng)用。

          第 74 行 調(diào)用了 single-sparegisterApplication 方法注冊(cè)了子應(yīng)用。

          我們直接來看 registerApplication 方法,registerApplication 方法是 single-spa 中注冊(cè)子應(yīng)用的核心函數(shù)。該函數(shù)有四個(gè)參數(shù),分別是

          • name(子應(yīng)用的名稱)
          • 回調(diào)函數(shù)(activeRule 激活時(shí)調(diào)用)
          • activeRule(子應(yīng)用的激活規(guī)則)
          • props(主應(yīng)用需要傳遞給子應(yīng)用的數(shù)據(jù))

          這些參數(shù)都是由 single-spa 直接實(shí)現(xiàn),這里可以先簡單理解為注冊(cè)子應(yīng)用(這個(gè)我們會(huì)在 single-spa 篇展開說)。在符合 activeRule 激活規(guī)則時(shí)將會(huì)激活子應(yīng)用,執(zhí)行回調(diào)函數(shù),返回一些生命周期鉤子函數(shù)(見下圖)。

          注意,這些生命周期鉤子函數(shù)屬于 single-spa,由 single-spa 決定在何時(shí)調(diào)用,這里我們從函數(shù)名來簡單理解。(bootstrap - 初始化子應(yīng)用,mount - 掛載子應(yīng)用,unmount - 卸載子應(yīng)用)

          qiankun

          如果你還是覺得有點(diǎn)懵,沒關(guān)系,我們通過一張圖來幫助理解。(見下圖)

          qiankun

          獲取子應(yīng)用資源 - import-html-entry

          我們從上面分析可以看出,qiankunregisterMicroApps 方法中第一個(gè)入?yún)?apps - Array> 有三個(gè)參數(shù) name、activeRule、props 都是交給 single-spa 使用,還有 entryrender 參數(shù)還沒有用到。

          我們這里需要關(guān)注 entry(子應(yīng)用的 entry 地址)render(子應(yīng)用被激活時(shí)觸發(fā)的渲染規(guī)則) 這兩個(gè)還沒有用到的參數(shù),這兩個(gè)參數(shù)延遲到 single-spa 子應(yīng)用激活后的回調(diào)函數(shù)中執(zhí)行。

          那我們假設(shè)此時(shí)我們的子應(yīng)用已激活,我們來看看這里做了什么。(見下圖)

          qiankun

          從上圖可以看出,在子應(yīng)用激活后,首先在 第 81~84 行 處使用了 import-html-entry 庫從 entry 進(jìn)入加載子應(yīng)用,加載完成后將返回一個(gè)對(duì)象(見下圖)

          qiankun

          我們來解釋一下這幾個(gè)字段

          字段解釋
          template將腳本文件內(nèi)容注釋后的 html 模板文件
          assetPublicPath資源地址根路徑,可用于加載子應(yīng)用資源
          getExternalScripts方法:獲取外部引入的腳本文件
          getExternalStyleSheets方法:獲取外部引入的樣式表文件
          execScripts方法:執(zhí)行該模板文件中所有的 JS 腳本文件,并且可以指定腳本的作用域 - proxy 對(duì)象

          我們先將 template 模板getExternalScriptsgetExternalStyleSheets 函數(shù)的執(zhí)行結(jié)果打印出來,效果如下(見下圖):

          qiankun

          從上圖我們可以看到我們外部引入的三個(gè) js 腳本文件,這個(gè)模板文件沒有外部 css 樣式表,對(duì)應(yīng)的樣式表數(shù)組也為空。

          然后我們?cè)賮矸治?execScripts 方法,該方法的作用就是指定一個(gè) proxy(默認(rèn)是 window)對(duì)象,然后執(zhí)行該模板文件中所有的 JS,并返回 JS 執(zhí)行后 proxy 對(duì)象的最后一個(gè)屬性(見下圖 1)。在微前端架構(gòu)中,這個(gè)對(duì)象一般會(huì)包含一些子應(yīng)用的生命周期鉤子函數(shù)(見下圖 2),主應(yīng)用可以通過在特定階段調(diào)用這些生命周期鉤子函數(shù),進(jìn)行掛載和銷毀子應(yīng)用的操作。

          qiankun
          qiankun

          qiankunimportEntry 函數(shù)中還傳入了配置項(xiàng) getTemplate,這個(gè)其實(shí)是對(duì) html 目標(biāo)文件的二次處理,這里就不作展開了,有興趣的可以自行去了解一下。

          主應(yīng)用掛載子應(yīng)用 HTML 模板

          我們回到 qiankun 源碼部分繼續(xù)看(見下圖)

          qiankun

          從上圖看出,在 第 85~87 行 處,先對(duì)單實(shí)例進(jìn)行檢測(cè)。在單實(shí)例模式下,新的子應(yīng)用掛載行為會(huì)在舊的子應(yīng)用卸載之后才開始。

          第 88 行 中,執(zhí)行注冊(cè)子應(yīng)用時(shí)傳入的 render 函數(shù),將 HTML Templateloading 作為入?yún)ⅲ?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">render 函數(shù)的內(nèi)容一般是將 HTML 掛載在指定容器中(見下圖)。

          qiankun

          在這個(gè)階段,主應(yīng)用已經(jīng)將子應(yīng)用基礎(chǔ)的 HTML 結(jié)構(gòu)掛載在了主應(yīng)用的某個(gè)容器內(nèi),接下來還需要執(zhí)行子應(yīng)用對(duì)應(yīng)的 mount 方法(如 Vue.$mount)對(duì)子應(yīng)用狀態(tài)進(jìn)行掛載。

          此時(shí)頁面還可以根據(jù) loading 參數(shù)開啟一個(gè)類似加載的效果,直至子應(yīng)用全部內(nèi)容加載完成。

          沙箱運(yùn)行環(huán)境 - genSandbox

          我們回到 qiankun 源碼部分繼續(xù)看,此時(shí)還是子應(yīng)用激活時(shí)的回調(diào)函數(shù)部分(見下圖)

          qiankun

          第 90~98 行qiankun 比較核心的部分,也是幾個(gè)子應(yīng)用之間狀態(tài)獨(dú)立的關(guān)鍵,那就是 js 的沙箱運(yùn)行環(huán)境。如果關(guān)閉了 useJsSandbox 選項(xiàng),那么所有子應(yīng)用的沙箱環(huán)境都是 window,就很容易對(duì)全局狀態(tài)產(chǎn)生污染。

          我們進(jìn)入到 genSandbox 內(nèi)部,看看 qiankun 是如何創(chuàng)建的 (JS)沙箱運(yùn)行環(huán)境。(見下圖)

          qiankun

          從上圖可以看出 genSandbox 內(nèi)部的沙箱主要是通過是否支持 window.Proxy 分為 LegacySandboxSnapshotSandbox 兩種。

          擴(kuò)展閱讀:多實(shí)例還有一種 ProxySandbox 沙箱,這種沙箱模式目前看來是最優(yōu)方案。由于其表現(xiàn)與舊版本略有不同,所以暫時(shí)只用于多實(shí)例模式。

          ProxySandbox 沙箱穩(wěn)定之后可能會(huì)作為單實(shí)例沙箱使用。

          LegacySandbox

          我們先來看看 LegacySandbox 沙箱是怎么進(jìn)行狀態(tài)隔離的(見下圖)

          qiankun

          我們來分析一下 LegacySandbox 類的幾個(gè)屬性:

          字段解釋
          addedPropsMapInSandbox記錄沙箱運(yùn)行期間新增的全局變量
          modifiedPropsOriginalValueMapInSandbox記錄沙箱運(yùn)行期間更新的全局變量
          currentUpdatedPropsValueMap記錄沙箱運(yùn)行期間操作過的全局變量。上面兩個(gè) Map 用于 關(guān)閉沙箱 時(shí)還原全局狀態(tài),而 currentUpdatedPropsValueMap 是在 激活沙箱 時(shí)還原沙箱的獨(dú)立狀態(tài)
          name沙箱名稱
          proxy代理對(duì)象,可以理解為子應(yīng)用的 global/window 對(duì)象
          sandboxRunning當(dāng)前沙箱是否在運(yùn)行中
          active激活沙箱,在子應(yīng)用掛載時(shí)啟動(dòng)
          inactive關(guān)閉沙箱,在子應(yīng)用卸載時(shí)啟動(dòng)
          constructor構(gòu)造函數(shù),創(chuàng)建沙箱環(huán)境

          我們現(xiàn)在從 window.Proxysetget 屬性來詳細(xì)講解 LegacySandbox 是如何實(shí)現(xiàn)沙箱運(yùn)行環(huán)境的。(見下圖)

          qiankun

          注意:子應(yīng)用沙箱中的 proxy 對(duì)象(第 62 行)可以簡單理解為子應(yīng)用的 window 全局對(duì)象(代碼如下),子應(yīng)用對(duì)全局屬性的操作就是對(duì)該 proxy 對(duì)象屬性的操作,帶著這份理解繼續(xù)往下看吧。

          //?子應(yīng)用腳本文件的執(zhí)行過程:
          eval(
          ??//?這里將?proxy?作為?window?參數(shù)傳入
          ??//?子應(yīng)用的全局對(duì)象就是該子應(yīng)用沙箱的?proxy?對(duì)象
          ??(function(window)?{
          ????/*?子應(yīng)用腳本文件內(nèi)容?*/
          ??})(proxy)
          );

          第 65~72 行中,當(dāng)調(diào)用 set 向子應(yīng)用 proxy/window 對(duì)象設(shè)置屬性時(shí),所有的屬性設(shè)置和更新都會(huì)先記錄在 addedPropsMapInSandboxmodifiedPropsOriginalValueMapInSandbox 中,然后統(tǒng)一記錄到currentUpdatedPropsValueMap 中。

          第 73 行 中修改全局 window 的屬性,完成值的設(shè)置。

          當(dāng)調(diào)用 get 從子應(yīng)用 proxy/window 對(duì)象取值時(shí),會(huì)直接從 window 對(duì)象中取值。對(duì)于非構(gòu)造函數(shù)的取值將會(huì)對(duì) this 指針綁定到 window 對(duì)象后,再返回函數(shù)。

          LegacySandbox 的沙箱隔離是通過激活沙箱時(shí)還原子應(yīng)用狀態(tài),卸載時(shí)還原主應(yīng)用狀態(tài)(子應(yīng)用掛載前的全局狀態(tài))實(shí)現(xiàn)的,具體實(shí)現(xiàn)如下(見下圖)。

          qiankun

          從上圖可以看出:

          • 第 37 行:在激活沙箱時(shí),沙箱會(huì)通過 currentUpdatedPropsValueMap 查詢到子應(yīng)用的獨(dú)立狀態(tài)池(沙箱可能會(huì)激活多次,這里是沙箱曾經(jīng)激活期間被修改的全局變量),然后還原子應(yīng)用狀態(tài)。
          • 第 44~45 行:在關(guān)閉沙箱時(shí),通過 addedPropsMapInSandbox 刪除在沙箱運(yùn)行期間新增的全局變量,通過 modifiedPropsOriginalValueMapInSandbox 還原沙箱運(yùn)行期間被修改的全局變量,從而還原到子應(yīng)用掛載前的狀態(tài)。

          從上面的分析可以得知,LegacySandbox 的沙箱隔離機(jī)制利用快照模式實(shí)現(xiàn),我們畫一張圖來幫助理解(見下圖)

          qiankun

          多實(shí)例沙箱 - ProxySandbox

          ProxySandbox 是一種新的沙箱模式,目前用于多實(shí)例模式的狀態(tài)隔離。在穩(wěn)定后以后可能會(huì)成為 單實(shí)例沙箱,我們來看看 ProxySandbox 沙箱是怎么進(jìn)行狀態(tài)隔離的(見下圖)

          qiankun

          我們來分析一下 ProxySandbox 類的幾個(gè)屬性:

          字段解釋
          updateValueMap記錄沙箱中更新的值,也就是每個(gè)子應(yīng)用中獨(dú)立的狀態(tài)池
          name沙箱名稱
          proxy代理對(duì)象,可以理解為子應(yīng)用的 global/window 對(duì)象
          sandboxRunning當(dāng)前沙箱是否在運(yùn)行中
          active激活沙箱,在子應(yīng)用掛載時(shí)啟動(dòng)
          inactive關(guān)閉沙箱,在子應(yīng)用卸載時(shí)啟動(dòng)
          constructor構(gòu)造函數(shù),創(chuàng)建沙箱環(huán)境

          我們現(xiàn)在從 window.Proxysetget 屬性來詳細(xì)講解 ProxySandbox 是如何實(shí)現(xiàn)沙箱運(yùn)行環(huán)境的。(見下圖)

          qiankun

          注意:子應(yīng)用沙箱中的 proxy 對(duì)象可以簡單理解為子應(yīng)用的 window 全局對(duì)象(代碼如下),子應(yīng)用對(duì)全局屬性的操作就是對(duì)該 proxy 對(duì)象屬性的操作,帶著這份理解繼續(xù)往下看吧。

          //?子應(yīng)用腳本文件的執(zhí)行過程:
          eval(
          ??//?這里將?proxy?作為?window?參數(shù)傳入
          ??//?子應(yīng)用的全局對(duì)象就是該子應(yīng)用沙箱的?proxy?對(duì)象
          ??(function(window)?{
          ????/*?子應(yīng)用腳本文件內(nèi)容?*/
          ??})(proxy)
          );

          當(dāng)調(diào)用 set 向子應(yīng)用 proxy/window 對(duì)象設(shè)置屬性時(shí),所有的屬性設(shè)置和更新都會(huì)命中 updateValueMap,存儲(chǔ)在 updateValueMap 集合中(第 38 行),從而避免對(duì) window 對(duì)象產(chǎn)生影響(舊版本則是通過 diff 算法還原 window 對(duì)象狀態(tài)快照,子應(yīng)用之間的狀態(tài)是隔離的,而父子應(yīng)用之間 window 對(duì)象會(huì)有污染)。

          當(dāng)調(diào)用 get 從子應(yīng)用 proxy/window 對(duì)象取值時(shí),會(huì)優(yōu)先從子應(yīng)用的沙箱狀態(tài)池 updateValueMap 中取值,如果沒有命中才從主應(yīng)用的 window 對(duì)象中取值(第 49 行)。對(duì)于非構(gòu)造函數(shù)的取值將會(huì)對(duì) this 指針綁定到 window 對(duì)象后,再返回函數(shù)。

          如此一來,ProxySandbox 沙箱應(yīng)用之間的隔離就完成了,所有子應(yīng)用對(duì) proxy/window 對(duì)象值的存取都受到了控制。設(shè)置值只會(huì)作用在沙箱內(nèi)部的 updateValueMap 集合上,取值也是優(yōu)先取子應(yīng)用獨(dú)立狀態(tài)池(updateValueMap)中的值,沒有找到的話,再從 proxy/window 對(duì)象中取值。

          相比較而言,ProxySandbox 是最完備的沙箱模式,完全隔離了對(duì) window 對(duì)象的操作,也解決了快照模式中子應(yīng)用運(yùn)行期間仍然會(huì)對(duì) window 造成污染的問題。

          我們對(duì) ProxySandbox 沙箱畫一張圖來加深理解(見下圖)

          qiankun

          SnapshotSandbox

          在不支持 window.Proxy 屬性時(shí),將會(huì)使用 SnapshotSandbox 沙箱,我們來看看其內(nèi)部實(shí)現(xiàn)(見下圖)

          qiankun

          我們來分析一下 SnapshotSandbox 類的幾個(gè)屬性:

          字段解釋
          name沙箱名稱
          proxy代理對(duì)象,此處為 window 對(duì)象
          sandboxRunning當(dāng)前沙箱是否激活
          windowSnapshotwindow 狀態(tài)快照
          modifyPropsMap沙箱運(yùn)行期間被修改過的 window 屬性
          constructor構(gòu)造函數(shù),激活沙箱
          active激活沙箱,在子應(yīng)用掛載時(shí)啟動(dòng)
          inactive關(guān)閉沙箱,在子應(yīng)用卸載時(shí)啟動(dòng)

          SnapshotSandbox 的沙箱環(huán)境主要是通過激活時(shí)記錄 window 狀態(tài)快照,在關(guān)閉時(shí)通過快照還原 window 對(duì)象來實(shí)現(xiàn)的。(見下圖)

          qiankun

          我們先看 active 函數(shù),在沙箱激活時(shí),會(huì)先給當(dāng)前 window 對(duì)象打一個(gè)快照,記錄沙箱激活前的狀態(tài)(第 38~40 行)。打完快照后,函數(shù)內(nèi)部將 window 狀態(tài)通過 modifyPropsMap 記錄還原到上次的沙箱運(yùn)行環(huán)境,也就是還原沙箱激活期間(歷史記錄)修改過的 window 屬性。

          在沙箱關(guān)閉時(shí),調(diào)用 inactive 函數(shù),在沙箱關(guān)閉前通過遍歷比較每一個(gè)屬性,將被改變的 window 對(duì)象屬性值(第 54 行)記錄在 modifyPropsMap 集合中。在記錄了 modifyPropsMap 后,將 window 對(duì)象通過快照 windowSnapshot 還原到被沙箱激活前的狀態(tài)(第 55 行),相當(dāng)于是將子應(yīng)用運(yùn)行期間對(duì) window 造成的污染全部清除。

          SnapshotSandbox 沙箱就是利用快照實(shí)現(xiàn)了對(duì) window 對(duì)象狀態(tài)隔離的管理。相比較 ProxySandbox 而言,在子應(yīng)用激活期間,SnapshotSandbox 將會(huì)對(duì) window 對(duì)象造成污染,屬于一個(gè)對(duì)不支持 Proxy 屬性的瀏覽器的向下兼容方案。

          我們對(duì) SnapshotSandbox 沙箱畫一張圖來加深理解(見下圖)

          qiankun

          掛載沙箱 - mountSandbox

          qiankun

          我們繼續(xù)回到這張圖,genSandbox 函數(shù)不僅返回了一個(gè) sandbox 沙箱,還返回了一個(gè) mountunmount 方法,分別在子應(yīng)用掛載時(shí)和卸載時(shí)的時(shí)候調(diào)用。

          我們先看看 mount 函數(shù)內(nèi)部(見下圖)

          qiankun

          首先,在 mount 內(nèi)部先激活了子應(yīng)用沙箱(第 26 行),在沙箱啟動(dòng)后開始劫持各類全局監(jiān)聽(第 27 行),我們這里重點(diǎn)看看 patchAtMounting 內(nèi)部是怎么實(shí)現(xiàn)的。(見下圖)

          qiankun

          patchAtMounting 內(nèi)部調(diào)用了下面四個(gè)函數(shù):

          • patchTimer(計(jì)時(shí)器劫持)
          • patchWindowListener(window 事件監(jiān)聽劫持)
          • patchHistoryListener(window.history 事件監(jiān)聽劫持)
          • patchDynamicAppend(動(dòng)態(tài)添加 Head 元素事件劫持)

          上面四個(gè)函數(shù)實(shí)現(xiàn)了對(duì) window 指定對(duì)象的統(tǒng)一劫持,我們可以挑一些解析看看其內(nèi)部實(shí)現(xiàn)。

          計(jì)時(shí)器劫持 - patchTimer

          我們先來看看 patchTimer 對(duì)計(jì)時(shí)器的劫持(見下圖)

          qiankun

          從上圖可以看出,patchTimer 內(nèi)部將 setInterval 進(jìn)行重載,將每個(gè)啟用的定時(shí)器的 intervalId 都收集起來(第 23~24 行),以便在子應(yīng)用卸載時(shí)調(diào)用 free 函數(shù)將計(jì)時(shí)器全部清除(見下圖)。

          qiankun

          我們來看看在子應(yīng)用加載時(shí)的 setInterval 函數(shù)驗(yàn)證即可(見下圖)

          qiankun

          從上圖可以看出,在進(jìn)入子應(yīng)用時(shí),setInterval 已經(jīng)被替換成了劫持后的函數(shù),防止全局計(jì)時(shí)器泄露污染。

          動(dòng)態(tài)添加樣式表和腳本文件劫持 - patchDynamicAppend

          patchWindowListenerpatchHistoryListener 的實(shí)現(xiàn)都與 patchTimer 實(shí)現(xiàn)類似,這里就不作復(fù)述了。

          我們需要重點(diǎn)對(duì) patchDynamicAppend 函數(shù)進(jìn)行解析,這個(gè)函數(shù)的作用是劫持對(duì) head 元素的操作(見下圖)

          qiankun

          從上圖可以看出,patchDynamicAppend 主要是對(duì)動(dòng)態(tài)添加的 style 樣式表和 script 標(biāo)簽做了處理。

          我們先看看對(duì) style 樣式表的處理(見下圖)

          qiankun

          從上圖可以看出,主要的處理邏輯在 第 68~74 行,如果當(dāng)前子應(yīng)用處于激活狀態(tài)(判斷子應(yīng)用的激活狀態(tài)主要是因?yàn)椋寒?dāng)主應(yīng)用切換路由時(shí)可能會(huì)自動(dòng)添加動(dòng)態(tài)樣式表,此時(shí)需要避免主應(yīng)用的樣式表被添加到子應(yīng)用head節(jié)點(diǎn)中導(dǎo)致出錯(cuò)),那么動(dòng)態(tài) style 樣式表就會(huì)被添加到子應(yīng)用容器內(nèi)(見下圖),在子應(yīng)用卸載時(shí)樣式表也可以和子應(yīng)用一起被卸載,從而避免樣式污染。同時(shí),動(dòng)態(tài)樣式表也會(huì)存儲(chǔ)在 dynamicStyleSheetElements 數(shù)組中,在后面還會(huì)提到其用處。

          qiankun

          我們?cè)賮砜纯磳?duì) script 腳本文件的處理(見下圖)

          qiankun

          對(duì)動(dòng)態(tài) script 腳本文件的處理較為復(fù)雜一些,我們也來解析一波:

          第 83~101 行 處對(duì)外部引入的 script 腳本文件使用 fetch 獲取,然后使用 execScripts 指定 proxy 對(duì)象(作為 window 對(duì)象)后執(zhí)行腳本文件內(nèi)容,同時(shí)也觸發(fā)了 loaderror 兩個(gè)事件。

          第 103~106 行 處將注釋后的腳本文件內(nèi)容以注釋的形式添加到子應(yīng)用容器內(nèi)。

          第 109~113 行 是對(duì)內(nèi)嵌腳本文件的執(zhí)行過程,就不作復(fù)述了。

          我們可以看出,對(duì)動(dòng)態(tài)添加的腳本進(jìn)行劫持的主要目的就是為了將動(dòng)態(tài)腳本運(yùn)行時(shí)的 window 對(duì)象替換成 proxy 代理對(duì)象,使子應(yīng)用動(dòng)態(tài)添加的腳本文件的運(yùn)行上下文也替換成子應(yīng)用自身。

          HTMLHeadElement.prototype.removeChild 的邏輯就是多加了個(gè)子應(yīng)用容器判斷,其他無異,就不展開說了。

          最后我們來看看 free 函數(shù)(見下圖)

          qiankun

          這個(gè) free 函數(shù)與其他的 patches(劫持函數(shù)) 實(shí)現(xiàn)不太一樣,這里緩存了一份 cssRules,在重新掛載的時(shí)候會(huì)執(zhí)行 rebuild 函數(shù)將其還原。這是因?yàn)闃邮皆?DOM 從文檔中刪除后,瀏覽器會(huì)自動(dòng)清除樣式元素表。如果不這么做的話,在重新掛載時(shí)會(huì)出現(xiàn)存在 style 標(biāo)簽,但是沒有渲染樣式的問題。

          卸載沙箱 - unmountSandbox

          我們?cè)倩氐?mount 函數(shù)本身(見下圖)

          qiankun

          從上圖可以看出,在 patchAtMounting 函數(shù)中劫持了各類全局監(jiān)聽,并返回了解除劫持的 free 函數(shù)。在卸載應(yīng)用時(shí)調(diào)用 free 函數(shù)解除這些全局監(jiān)聽的劫持行為(見下圖)

          qiankun

          從上圖可以看到 sideEffectsRebuildersfree 后被返回,在 mount 的時(shí)候又將被調(diào)用 rebuild 重建動(dòng)態(tài)樣式表。這塊環(huán)環(huán)相扣,是稍微有點(diǎn)繞,沒太看明白的同學(xué)可以翻上去再看一遍。

          到這里,qiankun 的最核心部分-沙箱機(jī)制,我們就已經(jīng)解析完畢了,接下來我們繼續(xù)剖析別的部分。

          在這里我們畫一張圖,對(duì)沙箱的創(chuàng)建過程進(jìn)行一個(gè)總梳理(見下圖)

          qiankun

          注冊(cè)內(nèi)部生命周期函數(shù)

          在創(chuàng)建好了沙箱環(huán)境后,在 第 100~106 行 注冊(cè)了一些內(nèi)部生命周期函數(shù)(見下圖)

          qiankun

          在上圖中,第 106 行mergeWith 方法的作用是將內(nèi)置的生命周期函數(shù)與傳入的 lifeCycles 生命周期函數(shù)。

          這里的 lifeCycles 生命周期函數(shù)指的是全子應(yīng)用共享的生命周期函數(shù),可用于執(zhí)行多個(gè)子應(yīng)用間相同的邏輯操作,例如 加載效果 之類的。(見下圖)

          qiankun

          除了外部傳入的生命周期函數(shù)外,我們還需要關(guān)注 qiankun 內(nèi)置的生命周期函數(shù)做了些什么(見下圖)

          qiankun

          我們對(duì)上圖的代碼進(jìn)行逐一解析:

          • 第 13~15 行:在加載子應(yīng)用前 beforeLoad(只會(huì)執(zhí)行一次)時(shí)注入一個(gè)環(huán)境變量,指示了子應(yīng)用的 public 路徑。
          • 第 17~19 行:在掛載子應(yīng)用前 beforeMount(可能會(huì)多次執(zhí)行)時(shí)可能也會(huì)注入該環(huán)境變量。
          • 第 23~30 行:在卸載子應(yīng)用前 beforeUnmount 時(shí)將環(huán)境變量還原到原始狀態(tài)。

          通過上面的分析我們可以得出一個(gè)結(jié)論,我們可以在子應(yīng)用中獲取該環(huán)境變量,將其設(shè)置為 __webpack_public_path__ 的值,從而使子應(yīng)用在主應(yīng)用中運(yùn)行時(shí),可以匹配正確的資源路徑。(見下圖)

          qiankun

          觸發(fā) beforeLoad 生命周期鉤子函數(shù)

          在注冊(cè)完了生命周期函數(shù)后,立即觸發(fā)了 beforeLoad 生命周期鉤子函數(shù)(見下圖)

          qiankun

          從上圖可以看出,在 第 108 行 中,觸發(fā)了 beforeLoad 生命周期鉤子函數(shù)。

          隨后,在 第 110 行 執(zhí)行了 import-html-entryexecScripts 方法。指定了腳本文件的運(yùn)行沙箱(jsSandbox),執(zhí)行完子應(yīng)用的腳本文件后,返回了一個(gè)對(duì)象,對(duì)象包含了子應(yīng)用的生命周期鉤子函數(shù)(見下圖)。

          qiankun

          第 112~121 行 對(duì)子應(yīng)用的生命周期鉤子函數(shù)做了個(gè)檢測(cè),如果在子應(yīng)用的導(dǎo)出對(duì)象中沒有發(fā)現(xiàn)生命周期鉤子函數(shù),會(huì)在沙箱對(duì)象中繼續(xù)查找生命周期鉤子函數(shù)。如果最后沒有找到生命周期鉤子函數(shù)則會(huì)拋出一個(gè)錯(cuò)誤,所以我們的子應(yīng)用一定要有 bootstrap, mount, unmount 這三個(gè)生命周期鉤子函數(shù)才能被 qiankun 正確嵌入到主應(yīng)用中。

          這里我們畫一張圖,對(duì)子應(yīng)用掛載前的初始化過程做一個(gè)總梳理(見下圖)

          qiankun

          進(jìn)入到 mount 掛載流程

          在一些初始化配置(如 子應(yīng)用資源、運(yùn)行沙箱環(huán)境、生命周期鉤子函數(shù)等等)準(zhǔn)備就緒后,qiankun 內(nèi)部將其組裝在一起,返回了三個(gè)函數(shù)作為 single-spa 內(nèi)部的生命周期函數(shù)(見下圖)

          qiankun

          single-spa 內(nèi)部的邏輯我們后面再展開說,這里我們可以簡單理解為 single-spa 內(nèi)部的三個(gè)生命周期鉤子函數(shù):

          • bootstrap:子應(yīng)用初始化時(shí)調(diào)用,只會(huì)調(diào)用一次;
          • mount:子應(yīng)用掛載時(shí)調(diào)用,可能會(huì)調(diào)用多次;
          • unmount:子應(yīng)用卸載時(shí)調(diào)用,可能會(huì)調(diào)用多次;

          我們可以看出,在 bootstrap 階段調(diào)用了子應(yīng)用暴露的 bootstrap 生命周期函數(shù)。

          我們這里對(duì) mount 階段進(jìn)行展開,看看在子應(yīng)用 mount 階段執(zhí)行了哪些函數(shù)(見下圖)

          qiankun

          我們進(jìn)行逐行解析:

          • 第 127~133 行:對(duì)單實(shí)例模式進(jìn)行檢測(cè)。在單實(shí)例模式下,新的子應(yīng)用掛載行為會(huì)在舊的子應(yīng)用卸載之后才開始。(由于這里是串行順序執(zhí)行,所以如果某一處發(fā)生阻塞的話,會(huì)阻塞所有后續(xù)的函數(shù)執(zhí)行)
          • 第 134 行:執(zhí)行注冊(cè)子應(yīng)用時(shí)傳入的 render 函數(shù),將 HTML Templateloading 作為入?yún)ⅰ_@里一般是在發(fā)生了一次 unmount 后,再次進(jìn)行 mount 掛載行為時(shí)將 HTML 掛載在指定容器中(見下圖)

            由于初始化的時(shí)候已經(jīng)調(diào)用過一次 render,所以在首次調(diào)用 mount 時(shí)可能已經(jīng)執(zhí)行過一次 render 方法。

            在下面的代碼中也有對(duì)重復(fù)掛載的情況進(jìn)行判斷的語句 - if (frame.querySelector("div") === null,防止重復(fù)掛載子應(yīng)用。

          qiankun
          • 第 135 行:觸發(fā)了 beforeMount 全局生命周期鉤子函數(shù);
          • 第 136 行:掛載沙箱,這一步中激活了對(duì)應(yīng)的子應(yīng)用沙箱,劫持了部分全局監(jiān)聽(如 setInterval)。此時(shí)開始子應(yīng)用的代碼將在沙箱中運(yùn)行。(反推可知,在 beforeMount 前的部分全局操作將會(huì)對(duì)主應(yīng)用造成污染,如 setInterval
          • 第 137 行:觸發(fā)子應(yīng)用的 mount 生命周期鉤子函數(shù),在這一步通常是執(zhí)行對(duì)應(yīng)的子應(yīng)用的掛載操作(如 ReactDOM.render、Vue.$mount。(見下圖)
          qiankun
          • 第 138 行:再次調(diào)用 render 函數(shù),此時(shí) loading 參數(shù)為 false,代表子應(yīng)用已經(jīng)加載完成。
          • 第 139 行:觸發(fā)了 afterMount 全局生命周期鉤子函數(shù);
          • 第 140~144 行:在單實(shí)例模式下設(shè)置 prevAppUnmountedDeferred 的值,這個(gè)值是一個(gè) promise,在當(dāng)前子應(yīng)用卸載時(shí)才會(huì)被 resolve,在該子應(yīng)用運(yùn)行期間會(huì)阻塞其他子應(yīng)用的掛載動(dòng)作(第 134 行);

          我們?cè)谏厦婧茉敿?xì)的剖析了整個(gè)子應(yīng)用的 mount 掛載流程,如果你還沒有搞懂的話,沒關(guān)系,我們?cè)佼嬕粋€(gè)流程圖來幫助理解。(見下圖)

          qiankun

          進(jìn)入到 unmount 卸載流程

          我們剛才梳理了子應(yīng)用的 mount 掛載流程,我們現(xiàn)在就進(jìn)入到子應(yīng)用的 unmount 卸載流程。在子應(yīng)用激活階段, activeRule 未命中時(shí)將會(huì)觸發(fā) unmount 卸載行為,具體的行為如下(見下圖)

          qiankun

          從上圖我們可以看出,unmount 卸載流程要比 mount 簡單很多,我們直接來梳理一下:

          • 第 148 行:觸發(fā)了 beforeUnmount 全局生命周期鉤子函數(shù);
          • 第 149 行:這里與 mount 流程的順序稍微有點(diǎn)不同,這里先執(zhí)行了子應(yīng)用的 unmount 生命周期鉤子函數(shù),保證子應(yīng)用仍然是運(yùn)行在沙箱內(nèi),避免造成狀態(tài)污染。在這里一般是對(duì)子應(yīng)用的一些狀態(tài)進(jìn)行清理和卸載操作。(如下圖,銷毀了剛才創(chuàng)建的 vue 實(shí)例)
          qiankun
          • 第 150 行:卸載沙箱,關(guān)閉了沙箱的激活狀態(tài)。
          • 第 151 行:觸發(fā)了 afterUnmount 全局生命周期鉤子函數(shù);
          • 第 152 行:觸發(fā) render 方法,并且傳入的 appContent 為空字符串,此處可以清空主應(yīng)用容器內(nèi)的內(nèi)容。
          • 第 153~156 行:當(dāng)前子應(yīng)用卸載完成后,在單實(shí)例模式下觸發(fā) prevAppUnmountedDeferred.resolve(),使其他子應(yīng)用的掛載行為得以繼續(xù)進(jìn)行,不再阻塞。

          我們對(duì) unmount 卸載流程也畫一張圖,幫助大家理解(見下圖)。

          qiankun

          總結(jié)

          到這里,我們對(duì) qiankun 框架的總流程梳理就差不多了。這里應(yīng)該做個(gè)總結(jié),大家看了這么多文字,估計(jì)大家也看累了,最后用一張圖對(duì) qiankun 的總流程進(jìn)行總結(jié)吧。

          qiankun

          彩蛋

          qiankun

          展望

          傳統(tǒng)的云控制臺(tái)應(yīng)用,幾乎都會(huì)面臨業(yè)務(wù)快速發(fā)展之后,單體應(yīng)用進(jìn)化成巨石應(yīng)用的問題。我們要如何維護(hù)一個(gè)巨無霸中臺(tái)應(yīng)用?

          上面這個(gè)問題引出了微前端架構(gòu)理念,所以微前端的概念也越來越火,我們團(tuán)隊(duì)最近也在嘗試轉(zhuǎn)型微前端架構(gòu)。

          工欲善其事必先利其器,所以本文針對(duì) qiankun 的源碼進(jìn)行解讀,在分享知識(shí)的同時(shí)也是幫助自己理解。

          這是我們團(tuán)隊(duì)對(duì)微前端架構(gòu)的最佳實(shí)踐(見下圖),如果有需求的話,可以在評(píng)論區(qū)留言,我們會(huì)考慮出一篇《微前端框架 qiankun 最佳實(shí)踐》來幫助大家搭建一套微前端架構(gòu)。

          架構(gòu)圖

          最后一件事

          這篇文章我花了大約半個(gè)月的時(shí)間來進(jìn)行排版、梳理、畫圖,堅(jiān)持下來一路寫完確實(shí)很不容易。

          如果您已經(jīng)看到這里了,希望您還是點(diǎn)個(gè)贊再走吧~

          如果本文對(duì)您有幫助的話,請(qǐng)點(diǎn)個(gè)贊和收藏吧!

          您的點(diǎn)贊是對(duì)作者的最大鼓勵(lì),也可以讓更多人看到本篇文章!

          如果對(duì) 《微前端框架 qiankun 最佳實(shí)踐》 有興趣的話,還請(qǐng)?jiān)谠u(píng)論區(qū)留言告訴作者吧!

          github 地址: https://github.com/a1029563229/Blogs/tree/master/Source-Code/qiankun/1.md)

          瀏覽 82
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美精品久久久又大又粗 | 欧美黄色免费在线观看 | 亚洲AⅤ永久无码毛片牛牛影视 | 超碰在线免费大屁股导航 | 高清无码第一页 |