“在前面的5篇文章中,我们对乾坤进行了比较深入的介绍,但是无论怎么深入都是不全面的,甚至某种意义上来讲乾坤并不是一个微前端框架,
single-spa
才是,乾坤只是一个对single-spa
进行增强的一个方案。接下来的几篇文章主要对single-spa的一些核心机制和功能从源码层面对其进行分析。本文主要分析single-spa
的注册机制。 ”
我先来看single-spa
暴露的注册函数的主要逻辑:
// 代码片段1
export function registerApplication(
appNameOrConfig,
appOrLoadApp,
activeWhen,
customProps
) {
// 关键点1
const registration = sanitizeArguments(
appNameOrConfig,
appOrLoadApp,
activeWhen,
customProps
);
// 这里省去许多逻辑...
// 关键点2
apps.push(
assign(
{
loadErrorTime: null,
status: NOT_LOADED,
parcels: {},
devtools: {
overlays: {
options: {},
selectors: [],
},
},
},
registration
)
);
if (isInBrowser) {
// 关键点3
ensureJQuerySupport();
// 关键点4
reroute();
}
}
从整体上看,registerApplication
一共做了4件比较重要的事情。首先,是对参数进行处理,对应代码片段1中的关键点1,参数处理函数sanitizeArguments
有几十行代码,具体怎么处理的,逻辑相对简单,这里就不描述了。对参数的合理处理,给用户提供了更多的灵活性,可以通过不同形式来传递参数,然后将不同格式的参数处理成统一格式。同时,对参数进行了校验。这种写法很常见,在我们日常编程中可以借鉴。其次,是将微应用保存到数组apps
中,apps
是一个全局变量,会存放所有的注册过的微应用。这个数组很重要,微应用的各种状态都保存在这里,实际上single-spa
的核心工作就是对apps
中保存的微应用进行管理和控制。再次,是调用ensureJQuerySupport
函数对JQuery的某些监听事件进行拦截,下文中进行详述。最后,是调用reroute
函数,主要是加载微应用,下文中会进行详述。
我看先看ensureJQuerySupport
函数的逻辑:
// 代码片段2
export function ensureJQuerySupport(jQuery = window.jQuery) {
// 这里省略一些代码...
const originalJQueryOn = jQuery.fn.on;
const originalJQueryOff = jQuery.fn.off;
jQuery.fn.on = function (eventString, fn) {
return captureRoutingEvents.call(
this,
originalJQueryOn,
window.addEventListener,
eventString,
fn,
arguments
);
};
jQuery.fn.off = function (eventString, fn) {
// 这里省略许多代码... 与jQeury.fn.on类似,不在此赘述
};
// 这里省略一些代码...
}
代码片段2中省略了许多逻辑判断,但核心功能可以理解为做了两件事。一是保存了jQuery
的事件监听和事件取消函数。二是对jQuery
的事件监听函数进行了拦截,具体怎么拦截的,让我们进入captureRoutingEvents
函数中一探究竟。
// 代码片段3
function captureRoutingEvents(
originalJQueryFunction,
nativeFunctionToCall,
eventString,
fn,
originalArgs
) {
if (typeof eventString !== "string") {
return originalJQueryFunction.apply(this, originalArgs);
}
const eventNames = eventString.split(/\s+/);
eventNames.forEach((eventName) => {
if (routingEventsListeningTo.indexOf(eventName) >= 0) {
// 关键点1
nativeFunctionToCall(eventName, fn);
eventString = eventString.replace(eventName, "");
}
});
if (eventString.trim() === "") {
// 关键点2
return this;
} else {
return originalJQueryFunction.apply(this, originalArgs);
}
}
还记得我们上文保存的originalJQueryFunction
函数吗,在函数captureRoutingEvents
有了体现。可以概括为该函数在某些条件下执行jQuery.fn.on
未被重写前的逻辑。否则就返回this
。相对于代码片段2中jQuery.fn.on
中的调用,关键点1处的代码,相当于执行了window.addEventListener("hashchange"|"popstate",()=>{})
。当然里面利用了些条件逻辑,如果监听的事件不仅仅只有hashchange、popstate
两个事件,则继续调用jQuery.fn.on
未被重写前的逻辑进行事件的监听。由于我没研究过jQeury
,调用被重写前的jQuery.fn.on
函数会发生什么,并不太清楚,不过对于理解single-spa
而言,能理解上文呈现的逻辑就足够了,对于关键点2处返回this
有什么用途,后续遇见该逻辑再进行剖析。
聊完了ensureJQuerySupport
,是时候探索reroute
了。reroute
函数有将近300行代码,对其中次要逻辑进行删减,且只留下和注册相关的逻辑,如下所示:
// 代码片段4
export function reroute(pendingPromises = [], eventArguments) {
// 这里省略掉许多代码...
const {
appsToUnload,
appsToUnmount,
appsToLoad,
appsToMount,
} = getAppChanges();
// 这里省略掉许多代码...
return loadApps();
// 这里省略掉许多代码...
function loadApps() {
return Promise.resolve().then(() => {
const loadPromises = appsToLoad.map(toLoadPromise);
return (
Promise.all(loadPromises)
.then(callAllEventListeners)
.then(() => [])
.catch((err) => {
callAllEventListeners();
throw err;
})
);
});
}
// 这里省略许多代码...
}
代码片段4中留下了reroute
函数的核心逻辑,做了两件事情,一是获取处于各种状态的微应用。二是返回函数loadApps
的执行结果。而loadApps
中做了一件重要的事情,就是调用了这样一行代码const loadPromises = appsToLoad.map(toLoadPromise);
,我们不难知道appsToLoad
代表着需要加载的微应用,而toLoadPromise
主要完成什么功能呢?请看下文讲解。
// 代码片段5,为了精简逻辑,除了省略一些代码,还做了微调
export function toLoadPromise(app) {
return Promise.resolve().then(() => {
// 这里省略许多代码...
return (app.loadPromise = Promise.resolve()
.then(() => {
const loadPromise = app.loadApp(getProps(app));
// 这里省略许多代码...
return loadPromise.then((val) => {
// 这里省略许多代码...
app.status = NOT_BOOTSTRAPPED;
app.bootstrap = flattenFnArray(appOpts, "bootstrap");
app.mount = flattenFnArray(appOpts, "mount");
app.unmount = flattenFnArray(appOpts, "unmount");
app.unload = flattenFnArray(appOpts, "unload");
app.timeouts = ensureValidAppTimeouts(appOpts.timeouts);
delete app.loadPromise;
return app;
});
})
.catch((err) => {
// 这里省略许多代码...
}));
};
}
代码片段5中原本有100多行代码,对其进行精简,我们发现核心逻辑其实只做了三件事,一是调用子应用传入的加载微应用的方法。二是保存微应用的各种状态。三是规范化子应用暴露的方法。其实看源码就要抓住核心逻辑,其他疑惑都会迎刃而解,忌讳胡子眉毛一把抓,逐行阅读,最终会把自己陷在里面,不知道代码究竟想要表达什么,进而丧失了继续读下去的热情。好了,关于single-spa
的注册机制今天就分析到这里,请朋友们期待更多关于single-spa
的文章。