微前端
微前端是一種多個(gè)團(tuán)隊(duì)通過獨(dú)立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略扁耐。將單頁(yè)面前端應(yīng)用由單一的單體應(yīng)用轉(zhuǎn)變?yōu)槎鄠€(gè)小型前端應(yīng)用聚合為一的應(yīng)用。各個(gè)前端應(yīng)用不限制技術(shù)棧,可以獨(dú)立開發(fā)寻馏、獨(dú)立部署兆龙、獨(dú)立運(yùn)行時(shí)狀態(tài)隔離。
初始化全局配置 start
第197~204行:設(shè)置配置參數(shù)(有默認(rèn)值)耻台,將配置參數(shù)存儲(chǔ)在 frameworkConfiguration 對(duì)象中空免;
第206~207行:配置預(yù)加載后,將會(huì)對(duì)配置的參數(shù)做預(yù)加載處理盆耽;
第210行:是qiankun根據(jù)環(huán)境支持的沙箱模式給出當(dāng)前使用哪種沙箱方案的提示信息蹋砚。
第212行:調(diào)用single-spa的 start方法啟動(dòng)應(yīng)用(startSingleSpa 是別名),而這里傳了一個(gè)urlRerouteOnly的作用是什么呢?
startSingleSpa({ urlRerouteOnly: true });
single-spa除了監(jiān)聽hashChange或popState兩個(gè)事件外摄杂,還劫持了原生的pushState和 replaceState兩個(gè)方法坝咐,而像scroll-restorer這樣的第三方組件可能會(huì)在頁(yè)面滾動(dòng)時(shí),通過調(diào)用pushState或replaceState匙姜,將滾動(dòng)位置記錄在state中畅厢,而頁(yè)面的url實(shí)際上沒有變化。這種情況下氮昧,single-spa理論上不應(yīng)該去重新加載應(yīng)用框杜,但是由于這種行為會(huì)觸發(fā)頁(yè)面的hashChange事件,所以根據(jù)上面的邏輯袖肥,single-spa會(huì)發(fā)生意外重載咪辱。
為了解決這個(gè)問題,single-spa允許開發(fā)者手動(dòng)設(shè)置是否只對(duì)url值的變化監(jiān)聽椎组,而不是只要發(fā)生hashChange或popState就去重新加載應(yīng)用油狂。
第213行:把started設(shè)為true,為了后面的手動(dòng)加載loadMicroApp的時(shí)候不用重復(fù)執(zhí)行start寸癌。
第215行:主要是為了防止在未執(zhí)行single-spa的start方法专筷,限制registerMicroApps注冊(cè)的子應(yīng)用加載必須在start之后,避免start傳遞的參數(shù)因?yàn)樽討?yīng)用提前執(zhí)行取不到蒸苇。
注冊(cè)子應(yīng)用 - registerMicroApps
第57行:遍歷未注冊(cè)的子應(yīng)用磷蛹,調(diào)用single-spa的registerApplication方法,注冊(cè)所有子應(yīng)用溪烤,這里registerApplication參數(shù)含義是
{
name: "app1", // 應(yīng)用名稱
app: function () {}, // 回調(diào)函數(shù)(activeRule 激活時(shí)調(diào)用)
activeRule: '/app1',(子應(yīng)用的激活規(guī)則)
props: {}(主應(yīng)用需要傳遞給子應(yīng)用的數(shù)據(jù))
}
此時(shí)味咳,微前端已全部初始化完成庇勃,等待子應(yīng)用的激活時(shí)機(jī)。
假如此時(shí)子應(yīng)用已激活槽驶,此時(shí)會(huì)執(zhí)行第59行的回調(diào)方法责嚷,接著就會(huì)執(zhí)行l(wèi)oadApp。
在應(yīng)用激活時(shí)候掂铐,我們可以看到第264行罕拂,通過調(diào)用import-html-entry的importEntry方法,返回了一個(gè)對(duì)象堡纬,如下圖:
字段 | 解釋 |
---|---|
template | 將腳本文件內(nèi)容注釋后的 html 模板文件 |
assetPublicPath | 資源地址根路徑聂受,可用于加載子應(yīng)用資源 |
getExternalScripts | 方法:獲取外部引入的腳本文件 |
getExternalStyleSheets | 方法:獲取外部引入的樣式表文件 |
execScripts | 方法:執(zhí)行該模板文件中所有的 JS 腳本文件,并且可以指定腳本的作用域 - proxy 對(duì)象 |
把getExternalScripts 和 getExternalStyleSheets 函數(shù)執(zhí)行的結(jié)果打印出來烤镐,可以看到樣式表和js腳本的代碼如下:
其中蛋济,execScripts 會(huì)先調(diào)用內(nèi)部方法getExternalScript,將外部script拿到和行內(nèi)script合并成一個(gè)隊(duì)列按順序執(zhí)行炮叶。
那在執(zhí)行過程中是如何給子應(yīng)用設(shè)置沙箱的碗旅?
首先看第173行,外部可以通過beforeExec對(duì)代碼進(jìn)行替換處理镜悉,進(jìn)行一些自定義的操作祟辟,然后通過 getExecutableScript對(duì)代碼進(jìn)行包裝侣肄。
// 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 拼接成函數(shù)聲明旧困,用于緩存全局變量稼锅,避免每次使用時(shí)都走一遍代理
const scopedGlobalVariableFnParameters = scopedGlobalVariables.length ? scopedGlobalVariables.join(',') : '';
// 通過這種方式獲取全局 window吼具,因?yàn)?script 也是在全局作用域下運(yùn)行的矩距,所以我們通過 window.proxy 綁定時(shí)也必須確保綁定到全局 window 上
// 否則在嵌套場(chǎng)景下拗盒, window.proxy 設(shè)置的是內(nèi)層應(yīng)用的 window,而代碼其實(shí)是在全局作用域運(yùn)行的锥债,會(huì)導(dǎo)致閉包里的 window.proxy 取的是最外層的微應(yīng)用的 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);
}
沙箱
簡(jiǎn)單來說是一種安全機(jī)制,為運(yùn)行中的程序提供隔離環(huán)境允趟。通常用于執(zhí)行未經(jīng)測(cè)試或不受信任的程序或代碼恼策,它會(huì)為待執(zhí)行的程序創(chuàng)建一個(gè)獨(dú)立的執(zhí)行環(huán)境,內(nèi)部程序的執(zhí)行不會(huì)影響到外部程序的運(yùn)行拼窥。
首先,可以看第44行鲁纠,根據(jù)window是否支持Proxy來使用LegacySandbox、ProxySandbox和SnapshotSandbox三種方式改含。
最后,我們來整體看一下qiankun主流程的流程圖捍壤。