🐛 keep the lifecycle execution order while multiple apps mounting on the same container parallelly (#1625)

This commit is contained in:
Kuitos 2021-08-03 21:42:14 +08:00 committed by GitHub
parent 44ccad92de
commit 9e4274e3f8
2 changed files with 70 additions and 11 deletions

View File

@ -52,6 +52,7 @@ export function registerMicroApps<T extends ObjectType>(
}
const appConfigPromiseGetterMap = new Map<string, Promise<ParcelConfigObjectGetter>>();
const containerMicroAppsMap = new Map<string, MicroApp[]>();
export function loadMicroApp<T extends ObjectType>(
app: LoadableApp<T>,
@ -69,9 +70,39 @@ export function loadMicroApp<T extends ObjectType>(
return undefined;
};
let microApp: MicroApp;
const wrapParcelConfigForRemount = (config: ParcelConfigObject): ParcelConfigObject => {
const container = 'container' in app ? app.container : undefined;
let microAppConfig = config;
if (container) {
const xpath = getContainerXpath(container);
if (xpath) {
const containerMicroApps = containerMicroAppsMap.get(`${name}-${xpath}`);
if (containerMicroApps?.length) {
const mount = [
async () => {
// While there are multiple micro apps mounted on the same container, we must wait until the prev instances all had unmounted
// Otherwise it will lead some concurrent issues
const prevLoadMicroApps = containerMicroApps.slice(0, containerMicroApps.indexOf(microApp));
const prevLoadMicroAppsWhichNotBroken = prevLoadMicroApps.filter(
(v) => v.getStatus() !== 'LOAD_ERROR' && v.getStatus() !== 'SKIP_BECAUSE_BROKEN',
);
await Promise.all(prevLoadMicroAppsWhichNotBroken.map((v) => v.unmountPromise));
},
...toArray(microAppConfig.mount),
];
microAppConfig = {
...config,
mount,
};
}
}
}
return {
...config,
...microAppConfig,
// empty bootstrap hook which should not run twice while it calling from cached micro app
bootstrap: () => Promise.resolve(),
};
@ -123,7 +154,30 @@ export function loadMicroApp<T extends ObjectType>(
startSingleSpa({ urlRerouteOnly: frameworkConfiguration.urlRerouteOnly ?? defaultUrlRerouteOnly });
}
return mountRootParcel(memorizedLoadingFn, { domElement: document.createElement('div'), ...props });
microApp = mountRootParcel(memorizedLoadingFn, { domElement: document.createElement('div'), ...props });
// Store the microApps which they mounted on the same container
const container = 'container' in app ? app.container : undefined;
if (container) {
const xpath = getContainerXpath(container);
if (xpath) {
const key = `${name}-${xpath}`;
const microAppsRef = containerMicroAppsMap.get(key) || [];
microAppsRef.push(microApp);
containerMicroAppsMap.set(key, microAppsRef);
// gc after unmount
microApp.unmountPromise.finally(() => {
const index = microAppsRef.indexOf(microApp);
microAppsRef.splice(index, 1);
// @ts-ignore
microApp = null;
});
}
}
return microApp;
}
export function start(opts: FrameworkConfiguration = {}) {

View File

@ -341,15 +341,8 @@ export async function loadApp<T extends ObjectType>(
const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer = initialContainer) => {
let appWrapperElement: HTMLElement | null = initialAppWrapperElement;
const appWrapperGetter = getAppWrapperGetter(
appName,
appInstanceId,
!!legacyRender,
strictStyleIsolation,
scopedCSS,
() => appWrapperElement,
);
let appWrapperElement: HTMLElement | null;
let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;
const parcelConfig: ParcelConfigObject = {
name: appInstanceId,
@ -371,6 +364,18 @@ export async function loadApp<T extends ObjectType>(
return undefined;
},
// initial wrapper element before app mount/remount
async () => {
appWrapperElement = initialAppWrapperElement;
appWrapperGetter = getAppWrapperGetter(
appName,
appInstanceId,
!!legacyRender,
strictStyleIsolation,
scopedCSS,
() => appWrapperElement,
);
},
// 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕
async () => {
const useNewContainer = remountContainer !== initialContainer;