From ff93c6fd10d9186bb04f3101ccd0ef0d62f80b14 Mon Sep 17 00:00:00 2001 From: Kuitos Date: Fri, 10 Dec 2021 16:22:45 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20app=20instance=20id=20generator?= =?UTF-8?q?=20compatible=20with=20nested=20sandbox=20(#1866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/utils.test.ts | 12 ++++++++ src/loader.ts | 56 ++++++++++++++++++------------------- src/utils.ts | 32 ++++++++++++--------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index c256055..bf08070 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,5 +1,6 @@ import { Deferred, + genAppInstanceIdByName, getDefaultTplWrapper, getWrapperId, getXPathForElement, @@ -137,3 +138,14 @@ it('should nextTick just executed once in one task context', async () => { await sleep(0); expect(counter).toBe(3); }); + +it('should genAppInstanceIdByName works well', () => { + const instanceId1 = genAppInstanceIdByName('hello'); + expect(instanceId1).toBe('hello'); + + const instanceId2 = genAppInstanceIdByName('hello'); + expect(instanceId2).toBe('hello_1'); + + const instanceId3 = genAppInstanceIdByName('hello'); + expect(instanceId3).toBe('hello_2'); +}); diff --git a/src/loader.ts b/src/loader.ts index c519c5f..d05ec61 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -20,7 +20,7 @@ import type { import { createSandboxContainer, css } from './sandbox'; import { Deferred, - getAppInstanceName, + genAppInstanceIdByName, getContainer, getDefaultTplWrapper, getWrapperId, @@ -68,7 +68,7 @@ function createElement( appContent: string, strictStyleIsolation: boolean, scopedCSS: boolean, - appName: string, + appInstanceId: string, ): HTMLElement { const containerElement = document.createElement('div'); containerElement.innerHTML = appContent; @@ -97,12 +97,12 @@ function createElement( if (scopedCSS) { const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr); if (!attr) { - appElement.setAttribute(css.QiankunCSSRewriteAttr, appName); + appElement.setAttribute(css.QiankunCSSRewriteAttr, appInstanceId); } const styleNodes = appElement.querySelectorAll('style') || []; forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => { - css.process(appElement!, stylesheetElement, appName); + css.process(appElement!, stylesheetElement, appInstanceId); }); } @@ -111,7 +111,7 @@ function createElement( /** generate app wrapper dom getter */ function getAppWrapperGetter( - appName: string, + appInstanceId: string, useLegacyRender: boolean, strictStyleIsolation: boolean, scopedCSS: boolean, @@ -122,13 +122,13 @@ function getAppWrapperGetter( if (strictStyleIsolation) throw new QiankunError('strictStyleIsolation can not be used with legacy render!'); if (scopedCSS) throw new QiankunError('experimentalStyleIsolation can not be used with legacy render!'); - const appWrapper = document.getElementById(getWrapperId(appName)); - assertElementExist(appWrapper, `Wrapper element for ${appName} is not existed!`); + const appWrapper = document.getElementById(getWrapperId(appInstanceId)); + assertElementExist(appWrapper, `Wrapper element for ${appInstanceId} is not existed!`); return appWrapper!; } const element = elementGetter(); - assertElementExist(element, `Wrapper element for ${appName} is not existed!`); + assertElementExist(element, `Wrapper element for ${appInstanceId} is not existed!`); if (strictStyleIsolation && supportShadowDOM) { return element!.shadowRoot!; @@ -148,11 +148,11 @@ type ElementRender = ( /** * Get the render function * If the legacy render function is provide, used as it, otherwise we will insert the app element to target container by qiankun - * @param appName + * @param appInstanceId * @param appContent * @param legacyRender */ -function getRender(appName: string, appContent: string, legacyRender?: HTMLContentRender) { +function getRender(appInstanceId: string, appContent: string, legacyRender?: HTMLContentRender) { const render: ElementRender = ({ element, loading, container }, phase) => { if (legacyRender) { if (process.env.NODE_ENV === 'development') { @@ -173,13 +173,13 @@ function getRender(appName: string, appContent: string, legacyRender?: HTMLConte switch (phase) { case 'loading': case 'mounting': - return `Target container with ${container} not existed while ${appName} ${phase}!`; + return `Target container with ${container} not existed while ${appInstanceId} ${phase}!`; case 'mounted': - return `Target container with ${container} not existed after ${appName} ${phase}!`; + return `Target container with ${container} not existed after ${appInstanceId} ${phase}!`; default: - return `Target container with ${container} not existed while ${appName} rendering!`; + return `Target container with ${container} not existed while ${appInstanceId} rendering!`; } })(); assertElementExist(containerElement, errorMsg); @@ -246,10 +246,10 @@ export async function loadApp( configuration: FrameworkConfiguration = {}, lifeCycles?: FrameworkLifeCycles, ): Promise { - const { entry } = app; - const appInstanceName = getAppInstanceName(app.name); + const { entry, name: appName } = app; + const appInstanceId = genAppInstanceIdByName(appName); - const markName = `[qiankun] App ${appInstanceName} Loading`; + const markName = `[qiankun] App ${appInstanceId} Loading`; if (process.env.NODE_ENV === 'development') { performanceMark(markName); } @@ -272,7 +272,7 @@ export async function loadApp( await (prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise); } - const appContent = getDefaultTplWrapper(appInstanceName)(template); + const appContent = getDefaultTplWrapper(appInstanceId)(template); const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation; @@ -287,20 +287,20 @@ export async function loadApp( appContent, strictStyleIsolation, scopedCSS, - appInstanceName, + appInstanceId, ); const initialContainer = 'container' in app ? app.container : undefined; const legacyRender = 'render' in app ? app.render : undefined; - const render = getRender(appInstanceName, appContent, legacyRender); + const render = getRender(appInstanceId, appContent, legacyRender); // 第一次加载设置应用可见区域 dom 结构 // 确保每次应用加载前容器 dom 结构已经设置完毕 render({ element: initialAppWrapperElement, loading: true, container: initialContainer }, 'loading'); const initialAppWrapperGetter = getAppWrapperGetter( - appInstanceName, + appInstanceId, !!legacyRender, strictStyleIsolation, scopedCSS, @@ -314,7 +314,7 @@ export async function loadApp( let sandboxContainer; if (sandbox) { sandboxContainer = createSandboxContainer( - appInstanceName, + appInstanceId, // FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518 initialAppWrapperGetter, scopedCSS, @@ -342,13 +342,13 @@ export async function loadApp( const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox); const { bootstrap, mount, unmount, update } = getLifecyclesFromExports( scriptExports, - appInstanceName, + appName, global, sandboxContainer?.instance?.latestSetProp, ); const { onGlobalStateChange, setGlobalState, offGlobalStateChange }: Record = - getMicroAppStateActions(appInstanceName); + getMicroAppStateActions(appInstanceId); // FIXME temporary way const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element); @@ -358,7 +358,7 @@ export async function loadApp( let appWrapperGetter: ReturnType; const parcelConfig: ParcelConfigObject = { - name: appInstanceName, + name: appInstanceId, bootstrap, mount: [ async () => { @@ -381,7 +381,7 @@ export async function loadApp( async () => { appWrapperElement = initialAppWrapperElement; appWrapperGetter = getAppWrapperGetter( - appInstanceName, + appInstanceId, !!legacyRender, strictStyleIsolation, scopedCSS, @@ -394,7 +394,7 @@ export async function loadApp( if (useNewContainer || !appWrapperElement) { // element will be destroyed after unmounted, we need to recreate it if it not exist // or we try to remount into a new container - appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceName); + appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceId); syncAppWrapperElement2Sandbox(appWrapperElement); } @@ -415,7 +415,7 @@ export async function loadApp( }, async () => { if (process.env.NODE_ENV === 'development') { - const measureName = `[qiankun] App ${appInstanceName} Loading Consuming`; + const measureName = `[qiankun] App ${appInstanceId} Loading Consuming`; performanceMeasure(measureName, markName); } }, @@ -427,7 +427,7 @@ export async function loadApp( async () => execHooksChain(toArray(afterUnmount), app, global), async () => { render({ element: null, loading: false, container: remountContainer }, 'unmounted'); - offGlobalStateChange(appInstanceName); + offGlobalStateChange(appInstanceId); // for gc appWrapperElement = null; syncAppWrapperElement2Sandbox(appWrapperElement); diff --git a/src/utils.ts b/src/utils.ts index 704d90d..dabbe5b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,7 +3,7 @@ * @since 2019-05-15 */ -import { isFunction, snakeCase } from 'lodash'; +import { isFunction, snakeCase, once } from 'lodash'; import { version } from './version'; import type { FrameworkConfiguration } from './interfaces'; @@ -111,23 +111,31 @@ export function getWrapperId(name: string) { export const nativeGlobal = new Function('return this')(); -Object.defineProperty(nativeGlobal, '__app_instance_name_map__', { - enumerable: false, - writable: true, - value: {}, +const getGlobalAppInstanceMap = once<() => Record>(() => { + if (!nativeGlobal.hasOwnProperty('__app_instance_name_map__')) { + Object.defineProperty(nativeGlobal, '__app_instance_name_map__', { + enumerable: false, + configurable: true, + writable: true, + value: {}, + }); + } + + return nativeGlobal.__app_instance_name_map__; }); /** - * get app instance name with the auto-increment approach + * Get app instance name with the auto-increment approach * @param appName */ -export const getAppInstanceName = (appName: string): string => { - if (!(appName in nativeGlobal.__app_instance_name_map__)) { +export const genAppInstanceIdByName = (appName: string): string => { + const globalAppInstanceMap = getGlobalAppInstanceMap(); + if (!(appName in globalAppInstanceMap)) { nativeGlobal.__app_instance_name_map__[appName] = 0; return appName; } - nativeGlobal.__app_instance_name_map__[appName]++; - return `${appName}_${nativeGlobal.__app_instance_name_map__[appName]}`; + globalAppInstanceMap[appName]++; + return `${appName}_${globalAppInstanceMap[appName]}`; }; /** 校验子应用导出的 生命周期 对象是否正确 */ @@ -136,7 +144,7 @@ export function validateExportLifecycle(exports: any) { return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount); } -class Deferred { +export class Deferred { promise: Promise; resolve!: (value: T | PromiseLike) => void; @@ -151,8 +159,6 @@ class Deferred { } } -export { Deferred }; - const supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' &&