此篇文章只簡(jiǎn)單梳理思路拇舀,不會(huì)源碼逐行分析,看此文章前請(qǐng)熟悉qiankun和single-spa的使用最好
暴露出的API, 從index.ts文件可以看出下面暴露的API
export { loadMicroApp, registerMicroApps, start } from './apis';
export { initGlobalState } from './globalState';
export * from './errorHandler';
export * from './effects';
export * from './interfaces';
export { prefetchApps } from './prefetch';
1. 注冊(cè)子應(yīng)用 registerMicroApps
注冊(cè)子應(yīng)用的接口樱拴, 基本原理是內(nèi)部有一個(gè)子應(yīng)用數(shù)組爹土,可以注冊(cè)多個(gè)子應(yīng)用纺且,遍歷數(shù)組調(diào)用single-spa
的registerApplication
函數(shù)進(jìn)行注冊(cè), 此API用法如下, 可以參考 https://single-spa.js.org/docs/api
注意此函數(shù)的第二個(gè)參數(shù)applicationOrLoadingFn
,返回一個(gè)resolved的應(yīng)用或者一個(gè)promise,注意這里的 the resolved application
其實(shí)是single-spa
定義的一個(gè)概念妻往,一個(gè)應(yīng)用的概念,具體參考https://single-spa.js.org/docs/building-applications
說(shuō)白了就是要返回一個(gè)如下的對(duì)象试和,帶有名字讯泣,聲明周期方法, 這就是qiankun要求你子應(yīng)用必須聲明這些聲明周期函數(shù)的目的,返回給single-spa
{
name: "applicationName",
bootstrap: [ bootstrap function array],
mount: [ mount function array],
unmount: [ unmount function array]
}
registerMicroApps里面關(guān)鍵的代碼如下阅悍,注意這個(gè)loadApp
才是真正封裝的返回應(yīng)用的地方
unregisteredApps.forEach(app => {
const { name, activeRule, props, ...appConfig } = app;
registerApplication({
name,
app: async () => {
await frameworkStartedDefer.promise;
return loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles);
},
activeWhen: activeRule,
customProps: props,
});
});
加載子應(yīng)用內(nèi)部函數(shù) loadApp
下面著重看下這個(gè)加載子應(yīng)用函數(shù)好渠,分幾個(gè)小步驟看下
-
加載入口文件 importEntry: 加載指定的html文本,和腳本用來(lái)執(zhí)行节视,這就是你在qiankun中傳入
entry: '//localhost:7100'
類似參數(shù)的用途拳锚,用此路勁來(lái)加載你子應(yīng)用的入口文件,和手動(dòng)執(zhí)行html中的腳本 - 創(chuàng)建子應(yīng)用DOM render: 如果你注冊(cè)的時(shí)候加入了render方法,此處會(huì)調(diào)用執(zhí)行寻行,否則找到container元素霍掺,把渲染的內(nèi)容插入節(jié)點(diǎn)中
- 創(chuàng)建沙箱 sandbox: 此處是qiankun的核心,創(chuàng)建了js的沙箱進(jìn)行子應(yīng)用之間和主應(yīng)用的隔離, 此處不展開(kāi)分析拌蜘,原理就是window對(duì)象的屬性劫持杆烁,內(nèi)部用一個(gè)對(duì)象緩存子應(yīng)用window下面屬性方法,達(dá)到隔離的目的简卧,下面代碼可以看出兔魂,支持proxy的就用proxy代理的沙箱隔離,否則用SnapshotSandbox举娩,簡(jiǎn)單來(lái)說(shuō)就是內(nèi)部備份析校,diff 方式記錄, SingularProxySandbox是老沙箱方式實(shí)現(xiàn)铜涉,說(shuō)是穩(wěn)定了再用統(tǒng)一的ProxySandbox
if (window.Proxy) {
sandbox = singular ? new LegacySandbox(appName) : new ProxySandbox(appName);
} else {
sandbox = new SnapshotSandbox(appName);
}
- 執(zhí)行加載前事件 beforeLoad: 利用execHooksChain函數(shù)歸并執(zhí)行函數(shù)智玻,簡(jiǎn)單來(lái)說(shuō)就是類似redux中的compose洋蔥模型,上一次函數(shù)的返回值作為下一個(gè)函數(shù)的參數(shù)骄噪,包裹執(zhí)行尚困,也是中間件原理,后面的一些加載聲明周期函數(shù)與此類似
- 執(zhí)行入口文件的腳本 execScripts: 此步驟就是獲取子應(yīng)用指定的主腳本链蕊,執(zhí)行并且獲取子應(yīng)用的bootstrap,mount, unmount等聲明周期函數(shù)
- 返回應(yīng)用并執(zhí)行內(nèi)部的一些鉤子周期函數(shù): 此時(shí)就可以返回一個(gè)滿足single-spa的應(yīng)用了事甜,同時(shí)在mount和unmount之前會(huì)執(zhí)行一些內(nèi)部定義的生命周期鉤子
到此為止,基本整個(gè)注冊(cè)滔韵,加載流程清楚了
2. 動(dòng)態(tài)加載應(yīng)用 loadMicroApp
qiankun提供了動(dòng)態(tài)加載子應(yīng)用的方式逻谦,簡(jiǎn)單代碼如下, 動(dòng)態(tài)調(diào)用了single-spa
的mountRootParcel
方法和qiankun內(nèi)部loadApp來(lái)實(shí)現(xiàn)應(yīng)用加載
export function loadMicroApp<T extends object = {}>(
app: LoadableApp<T>,
configuration = frameworkConfiguration,
): MicroApp {
const { props, ...appConfig } = app;
return mountRootParcel(() => loadApp(appConfig, configuration), {
domElement: document.createElement('div'),
...props,
});
}
3. 啟動(dòng)應(yīng)用 start
start的代碼也沒(méi)有幾行,如下主要就是調(diào)用single-spa
內(nèi)部的start
來(lái)啟動(dòng)應(yīng)用陪蜻,對(duì)應(yīng)的為startSingleSpa此函數(shù)
export function start(opts: FrameworkConfiguration = {}) {
frameworkConfiguration = opts;
const {
prefetch = true,
sandbox = true,
singular = true,
urlRerouteOnly,
...importEntryOpts
} = frameworkConfiguration;
if (prefetch) {
prefetchApps(microApps, prefetch, importEntryOpts);
}
if (sandbox) {
if (!window.Proxy) {
console.warn('[qiankun] Miss window.Proxy, proxySandbox will degenerate into snapshotSandbox');
// 快照沙箱不支持非 singular 模式
if (!singular) {
console.error('[qiankun] singular is forced to be true when sandbox enable but proxySandbox unavailable');
frameworkConfiguration.singular = true;
}
}
}
startSingleSpa({ urlRerouteOnly });
frameworkStartedDefer.resolve();
}