Single-SPA 源码分析
//src/application/apps.js
export function registerApplication(
appNameOrConfig,
appOrLoadApp,
activeWhen,
customProps
) {
const registration = sanitizeArguments(
appNameOrConfig,
appOrLoadApp,
activeWhen,
customProps
);
//把注册的spa放到apps数组
apps.push(
assign(
{...},
registration
)
);
if (isInBrowser) {
ensureJQuerySupport();
reroute();
}
}
//src/navigation/reroute.js
export function reroute(pendingPromises = [], eventArguments) {
...
const {
appsToUnload,
appsToUnmount,
appsToLoad,
appsToMount,
} = getAppChanges();
//是否已启动
if (isStarted()) {
appChangeUnderway = true;
appsThatChanged = appsToUnload.concat(
appsToLoad,
appsToUnmount,
appsToMount
);
return performAppChanges();
} else {
appsThatChanged = appsToLoad;
//未启动的话就加载app
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;
})
);
});
}
}
//src/lifecycles/load.js
export function toLoadPromise(app) {
return Promise.resolve().then(() => {
...
app.status = LOADING_SOURCE_CODE;
return (app.loadPromise = Promise.resolve()
.then(() => {
//调用每个spa的app方法,loadApp就是app方法
//registration.loadApp = appNameOrConfig.app;
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;
});
})
});
}
到现在为止toMountApp数组就准备好了,等待调用start方法。
//src/start.js
export function start(opts) {
//把全局状态改为started
started = true;
if (opts && opts.urlRerouteOnly) {
setUrlRerouteOnly(opts.urlRerouteOnly);
}
//又调用reroute,不过这次分支不同了
if (isInBrowser) {
reroute();
}
}
//src/navigation/reroute.js
export function reroute(pendingPromises = [], eventArguments) {
...
//是否已启动
if (isStarted()) {
appChangeUnderway = true;
appsThatChanged = appsToUnload.concat(
appsToLoad,
appsToUnmount,
appsToMount
);
return performAppChanges();
}
function performAppChanges() {
return Promise.resolve().then(() => {
//事件通知
window.dispatchEvent(
...
);
const unloadPromises = appsToUnload.map(toUnloadPromise);
const unmountUnloadPromises = appsToUnmount
.map(toUnmountPromise)
.map((unmountPromise) => unmountPromise.then(toUnloadPromise));
...
const loadThenMountPromises = appsToLoad.map((app) => {
return toLoadPromise(app).then((app) =>
//调用bootstrap事件
tryToBootstrapAndMount(app, unmountAllPromise)
);
});
const mountPromises = appsToMount
.filter((appToMount) => appsToLoad.indexOf(appToMount) < 0)
.map((appToMount) => {
return tryToBootstrapAndMount(appToMount, unmountAllPromise);
});
return unmountAllPromise
.catch((err) => {
callAllEventListeners();
throw err;
}).then(() => {...});
});
}
function tryToBootstrapAndMount(app, unmountAllPromise) {
if (shouldBeActive(app)) {
return toBootstrapPromise(app).then((app) =>
unmountAllPromise.then(() =>
shouldBeActive(app) ? toMountPromise(app) : app
)
);
} else {
return unmountAllPromise.then(() => app);
}
}
流程图如下:
Single-SPA MFE注册
singleSpa.registerApplication(
appName: string,
applicationOrLoadingFn: () => <Function | Promise>,
activityFn: (location) => boolean,
customProps?: Object =
)
//demo
singleSpa.registerApplication({
name:"app1",
app:() => import('./app1/app1.js');,
activeWhen:'/app1'
});
Single-SPA 能力分析
从原理和基础来看, Single-SPA提供了根据URL对微应用调度和生命周期钩子挂载通知的能力。
但同时,以下功能需要使用者自己考虑或实现:
- 微应用的加载
- 微应用的隔离
- 应用间通信
- CSS污染问题
所以SSPA不是一个可以直接落地使用的微前端框架,需要对其进行二次开发或封装。