From 9e4274e3f89191f90bb4651531914dc487ce9208 Mon Sep 17 00:00:00 2001 From: Kuitos Date: Tue, 3 Aug 2021 21:42:14 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20keep=20the=20lifecycle=20executi?= =?UTF-8?q?on=20order=20while=20multiple=20apps=20mounting=20on=20the=20sa?= =?UTF-8?q?me=20container=20parallelly=20(#1625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/loader.ts | 23 ++++++++++++-------- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/apis.ts b/src/apis.ts index 7d617fc..a9bf179 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -52,6 +52,7 @@ export function registerMicroApps( } const appConfigPromiseGetterMap = new Map>(); +const containerMicroAppsMap = new Map(); export function loadMicroApp( app: LoadableApp, @@ -69,9 +70,39 @@ export function loadMicroApp( 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( 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 = {}) { diff --git a/src/loader.ts b/src/loader.ts index 37ac667..b0c126f 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -341,15 +341,8 @@ export async function loadApp( 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; const parcelConfig: ParcelConfigObject = { name: appInstanceId, @@ -371,6 +364,18 @@ export async function loadApp( 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;