微前端
微前端是一種多個團隊通過獨立發佈功能的方式來共同構建現代化 web 應用的技術手段及方法策略。將單頁面前端應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。各個前端應用不限制技術棧,可以獨立開發、獨立部署、獨立運行時狀態隔離。
初始化全局配置 start
第197~204行:設置配置參數(有默認值),將配置參數存儲在 frameworkConfiguration 對象中;
第206~207行:配置預加載後,將會對配置的參數做預加載處理;
第210行:是qiankun根據環境支持的沙箱模式給出當前使用哪種沙箱方案的提示信息。
第212行:調用single-spa的 start方法啓動應用(startSingleSpa 是別名),而這裏傳了一個urlRerouteOnly的作用是什麼呢?
startSingleSpa({ urlRerouteOnly: true });
single-spa除了監聽hashChange或popState兩個事件外,還劫持了原生的pushState和 replaceState兩個方法,而像scroll-restorer這樣的第三方組件可能會在頁面滾動時,通過調用pushState或replaceState,將滾動位置記錄在state中,而頁面的url實際上沒有變化。這種情況下,single-spa理論上不應該去重新加載應用,但是由於這種行爲會觸發頁面的hashChange事件,所以根據上面的邏輯,single-spa會發生意外重載。
爲了解決這個問題,single-spa允許開發者手動設置是否只對url值的變化監聽,而不是隻要發生hashChange或popState就去重新加載應用。
第213行:把started設爲true,爲了後面的手動加載loadMicroApp的時候不用重複執行start。
第215行:主要是爲了防止在未執行single-spa的start方法,限制registerMicroApps註冊的子應用加載必須在start之後,避免start傳遞的參數因爲子應用提前執行取不到。
註冊子應用 - registerMicroApps
第57行:遍歷未註冊的子應用,調用single-spa的registerApplication方法,註冊所有子應用,這裏registerApplication參數含義是
{
name: "app1", // 應用名稱
app: function () {}, // 回調函數(activeRule 激活時調用)
activeRule: '/app1',(子應用的激活規則)
props: {}(主應用需要傳遞給子應用的數據)
}
此時,微前端已全部初始化完成,等待子應用的激活時機。
假如此時子應用已激活,此時會執行第59行的回調方法,接着就會執行loadApp。
在應用激活時候,我們可以看到第264行,通過調用import-html-entry的importEntry方法,返回了一個對象,如下圖:
字段 | 解釋 |
---|---|
template | 將腳本文件內容註釋後的 html 模板文件 |
assetPublicPath | 資源地址根路徑,可用於加載子應用資源 |
getExternalScripts | 方法:獲取外部引入的腳本文件 |
getExternalStyleSheets | 方法:獲取外部引入的樣式表文件 |
execScripts | 方法:執行該模板文件中所有的 JS 腳本文件,並且可以指定腳本的作用域 - proxy 對象 |
把getExternalScripts 和 getExternalStyleSheets 函數執行的結果打印出來,可以看到樣式表和js腳本的代碼如下:
其中,execScripts 會先調用內部方法getExternalScript,將外部script拿到和行內script合併成一個隊列按順序執行。
那在執行過程中是如何給子應用設置沙箱的?
首先看第173行,外部可以通過beforeExec對代碼進行替換處理,進行一些自定義的操作,然後通過 getExecutableScript對代碼進行包裝。
// import-html-enrry /src/index.js 54行
function getExecutableScript(scriptSrc, scriptText, opts = {}) {
const { proxy, strictGlobal, scopedGlobalVariables = [] } = opts;
const sourceUrl = isInlineCode(scriptSrc) ? '' : `//# sourceURL=${scriptSrc}\n`;
// 將 scopedGlobalVariables 拼接成函數聲明,用於緩存全局變量,避免每次使用時都走一遍代理
const scopedGlobalVariableFnParameters = scopedGlobalVariables.length ? scopedGlobalVariables.join(',') : '';
// 通過這種方式獲取全局 window,因爲 script 也是在全局作用域下運行的,所以我們通過 window.proxy 綁定時也必須確保綁定到全局 window 上
// 否則在嵌套場景下, window.proxy 設置的是內層應用的 window,而代碼其實是在全局作用域運行的,會導致閉包裏的 window.proxy 取的是最外層的微應用的 proxy
const globalWindow = (0, eval)('window');
globalWindow.proxy = proxy;
// TODO 通過 strictGlobal 方式切換 with 閉包,待 with 方式坑趟平後再合併
return strictGlobal
? (
scopedGlobalVariableFnParameters
? `;(function(){with(window.proxy){(function(${scopedGlobalVariableFnParameters}){;${scriptText}\n${sourceUrl}}).bind(window.proxy)(${scopedGlobalVariableFnParameters})}})();`
: `;(function(window, self, globalThis){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`
)
: `;(function(window, self, globalThis){;${scriptText}\n${sourceUrl}}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`;
}
然後看evelCode做了什麼。
export function evalCode(scriptSrc, code) {
const key = scriptSrc;
if (!evalCache[key]) {
const functionWrappedCode = `(function(){${code}})`;
evalCache[key] = (0, eval)(functionWrappedCode);
}
const evalFunc = evalCache[key];
evalFunc.call(window);
}
沙箱
簡單來說是一種安全機制,爲運行中的程序提供隔離環境。通常用於執行未經測試或不受信任的程序或代碼,它會爲待執行的程序創建一個獨立的執行環境,內部程序的執行不會影響到外部程序的運行。
首先,可以看第44行,根據window是否支持Proxy來使用LegacySandbox、ProxySandbox和SnapshotSandbox三種方式。
最後,我們來整體看一下qiankun主流程的流程圖。