🐛 app instance id generator compatible with nested sandbox (#1866)
This commit is contained in:
parent
df11e0c8b2
commit
ff93c6fd10
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Deferred,
|
Deferred,
|
||||||
|
genAppInstanceIdByName,
|
||||||
getDefaultTplWrapper,
|
getDefaultTplWrapper,
|
||||||
getWrapperId,
|
getWrapperId,
|
||||||
getXPathForElement,
|
getXPathForElement,
|
||||||
|
|
@ -137,3 +138,14 @@ it('should nextTick just executed once in one task context', async () => {
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
expect(counter).toBe(3);
|
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');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import type {
|
||||||
import { createSandboxContainer, css } from './sandbox';
|
import { createSandboxContainer, css } from './sandbox';
|
||||||
import {
|
import {
|
||||||
Deferred,
|
Deferred,
|
||||||
getAppInstanceName,
|
genAppInstanceIdByName,
|
||||||
getContainer,
|
getContainer,
|
||||||
getDefaultTplWrapper,
|
getDefaultTplWrapper,
|
||||||
getWrapperId,
|
getWrapperId,
|
||||||
|
|
@ -68,7 +68,7 @@ function createElement(
|
||||||
appContent: string,
|
appContent: string,
|
||||||
strictStyleIsolation: boolean,
|
strictStyleIsolation: boolean,
|
||||||
scopedCSS: boolean,
|
scopedCSS: boolean,
|
||||||
appName: string,
|
appInstanceId: string,
|
||||||
): HTMLElement {
|
): HTMLElement {
|
||||||
const containerElement = document.createElement('div');
|
const containerElement = document.createElement('div');
|
||||||
containerElement.innerHTML = appContent;
|
containerElement.innerHTML = appContent;
|
||||||
|
|
@ -97,12 +97,12 @@ function createElement(
|
||||||
if (scopedCSS) {
|
if (scopedCSS) {
|
||||||
const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
|
const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
|
||||||
if (!attr) {
|
if (!attr) {
|
||||||
appElement.setAttribute(css.QiankunCSSRewriteAttr, appName);
|
appElement.setAttribute(css.QiankunCSSRewriteAttr, appInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleNodes = appElement.querySelectorAll('style') || [];
|
const styleNodes = appElement.querySelectorAll('style') || [];
|
||||||
forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
|
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 */
|
/** generate app wrapper dom getter */
|
||||||
function getAppWrapperGetter(
|
function getAppWrapperGetter(
|
||||||
appName: string,
|
appInstanceId: string,
|
||||||
useLegacyRender: boolean,
|
useLegacyRender: boolean,
|
||||||
strictStyleIsolation: boolean,
|
strictStyleIsolation: boolean,
|
||||||
scopedCSS: boolean,
|
scopedCSS: boolean,
|
||||||
|
|
@ -122,13 +122,13 @@ function getAppWrapperGetter(
|
||||||
if (strictStyleIsolation) throw new QiankunError('strictStyleIsolation can not be used with legacy render!');
|
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!');
|
if (scopedCSS) throw new QiankunError('experimentalStyleIsolation can not be used with legacy render!');
|
||||||
|
|
||||||
const appWrapper = document.getElementById(getWrapperId(appName));
|
const appWrapper = document.getElementById(getWrapperId(appInstanceId));
|
||||||
assertElementExist(appWrapper, `Wrapper element for ${appName} is not existed!`);
|
assertElementExist(appWrapper, `Wrapper element for ${appInstanceId} is not existed!`);
|
||||||
return appWrapper!;
|
return appWrapper!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = elementGetter();
|
const element = elementGetter();
|
||||||
assertElementExist(element, `Wrapper element for ${appName} is not existed!`);
|
assertElementExist(element, `Wrapper element for ${appInstanceId} is not existed!`);
|
||||||
|
|
||||||
if (strictStyleIsolation && supportShadowDOM) {
|
if (strictStyleIsolation && supportShadowDOM) {
|
||||||
return element!.shadowRoot!;
|
return element!.shadowRoot!;
|
||||||
|
|
@ -148,11 +148,11 @@ type ElementRender = (
|
||||||
/**
|
/**
|
||||||
* Get the render function
|
* 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
|
* 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 appContent
|
||||||
* @param legacyRender
|
* @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) => {
|
const render: ElementRender = ({ element, loading, container }, phase) => {
|
||||||
if (legacyRender) {
|
if (legacyRender) {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
|
@ -173,13 +173,13 @@ function getRender(appName: string, appContent: string, legacyRender?: HTMLConte
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case 'loading':
|
case 'loading':
|
||||||
case 'mounting':
|
case 'mounting':
|
||||||
return `Target container with ${container} not existed while ${appName} ${phase}!`;
|
return `Target container with ${container} not existed while ${appInstanceId} ${phase}!`;
|
||||||
|
|
||||||
case 'mounted':
|
case 'mounted':
|
||||||
return `Target container with ${container} not existed after ${appName} ${phase}!`;
|
return `Target container with ${container} not existed after ${appInstanceId} ${phase}!`;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `Target container with ${container} not existed while ${appName} rendering!`;
|
return `Target container with ${container} not existed while ${appInstanceId} rendering!`;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
assertElementExist(containerElement, errorMsg);
|
assertElementExist(containerElement, errorMsg);
|
||||||
|
|
@ -246,10 +246,10 @@ export async function loadApp<T extends ObjectType>(
|
||||||
configuration: FrameworkConfiguration = {},
|
configuration: FrameworkConfiguration = {},
|
||||||
lifeCycles?: FrameworkLifeCycles<T>,
|
lifeCycles?: FrameworkLifeCycles<T>,
|
||||||
): Promise<ParcelConfigObjectGetter> {
|
): Promise<ParcelConfigObjectGetter> {
|
||||||
const { entry } = app;
|
const { entry, name: appName } = app;
|
||||||
const appInstanceName = getAppInstanceName(app.name);
|
const appInstanceId = genAppInstanceIdByName(appName);
|
||||||
|
|
||||||
const markName = `[qiankun] App ${appInstanceName} Loading`;
|
const markName = `[qiankun] App ${appInstanceId} Loading`;
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
performanceMark(markName);
|
performanceMark(markName);
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +272,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
await (prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise);
|
await (prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appContent = getDefaultTplWrapper(appInstanceName)(template);
|
const appContent = getDefaultTplWrapper(appInstanceId)(template);
|
||||||
|
|
||||||
const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;
|
const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;
|
||||||
|
|
||||||
|
|
@ -287,20 +287,20 @@ export async function loadApp<T extends ObjectType>(
|
||||||
appContent,
|
appContent,
|
||||||
strictStyleIsolation,
|
strictStyleIsolation,
|
||||||
scopedCSS,
|
scopedCSS,
|
||||||
appInstanceName,
|
appInstanceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialContainer = 'container' in app ? app.container : undefined;
|
const initialContainer = 'container' in app ? app.container : undefined;
|
||||||
const legacyRender = 'render' in app ? app.render : undefined;
|
const legacyRender = 'render' in app ? app.render : undefined;
|
||||||
|
|
||||||
const render = getRender(appInstanceName, appContent, legacyRender);
|
const render = getRender(appInstanceId, appContent, legacyRender);
|
||||||
|
|
||||||
// 第一次加载设置应用可见区域 dom 结构
|
// 第一次加载设置应用可见区域 dom 结构
|
||||||
// 确保每次应用加载前容器 dom 结构已经设置完毕
|
// 确保每次应用加载前容器 dom 结构已经设置完毕
|
||||||
render({ element: initialAppWrapperElement, loading: true, container: initialContainer }, 'loading');
|
render({ element: initialAppWrapperElement, loading: true, container: initialContainer }, 'loading');
|
||||||
|
|
||||||
const initialAppWrapperGetter = getAppWrapperGetter(
|
const initialAppWrapperGetter = getAppWrapperGetter(
|
||||||
appInstanceName,
|
appInstanceId,
|
||||||
!!legacyRender,
|
!!legacyRender,
|
||||||
strictStyleIsolation,
|
strictStyleIsolation,
|
||||||
scopedCSS,
|
scopedCSS,
|
||||||
|
|
@ -314,7 +314,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
let sandboxContainer;
|
let sandboxContainer;
|
||||||
if (sandbox) {
|
if (sandbox) {
|
||||||
sandboxContainer = createSandboxContainer(
|
sandboxContainer = createSandboxContainer(
|
||||||
appInstanceName,
|
appInstanceId,
|
||||||
// FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518
|
// FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518
|
||||||
initialAppWrapperGetter,
|
initialAppWrapperGetter,
|
||||||
scopedCSS,
|
scopedCSS,
|
||||||
|
|
@ -342,13 +342,13 @@ export async function loadApp<T extends ObjectType>(
|
||||||
const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox);
|
const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox);
|
||||||
const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
|
const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
|
||||||
scriptExports,
|
scriptExports,
|
||||||
appInstanceName,
|
appName,
|
||||||
global,
|
global,
|
||||||
sandboxContainer?.instance?.latestSetProp,
|
sandboxContainer?.instance?.latestSetProp,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onGlobalStateChange, setGlobalState, offGlobalStateChange }: Record<string, CallableFunction> =
|
const { onGlobalStateChange, setGlobalState, offGlobalStateChange }: Record<string, CallableFunction> =
|
||||||
getMicroAppStateActions(appInstanceName);
|
getMicroAppStateActions(appInstanceId);
|
||||||
|
|
||||||
// FIXME temporary way
|
// FIXME temporary way
|
||||||
const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
|
const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
|
||||||
|
|
@ -358,7 +358,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;
|
let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;
|
||||||
|
|
||||||
const parcelConfig: ParcelConfigObject = {
|
const parcelConfig: ParcelConfigObject = {
|
||||||
name: appInstanceName,
|
name: appInstanceId,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
mount: [
|
mount: [
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -381,7 +381,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
async () => {
|
async () => {
|
||||||
appWrapperElement = initialAppWrapperElement;
|
appWrapperElement = initialAppWrapperElement;
|
||||||
appWrapperGetter = getAppWrapperGetter(
|
appWrapperGetter = getAppWrapperGetter(
|
||||||
appInstanceName,
|
appInstanceId,
|
||||||
!!legacyRender,
|
!!legacyRender,
|
||||||
strictStyleIsolation,
|
strictStyleIsolation,
|
||||||
scopedCSS,
|
scopedCSS,
|
||||||
|
|
@ -394,7 +394,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
if (useNewContainer || !appWrapperElement) {
|
if (useNewContainer || !appWrapperElement) {
|
||||||
// element will be destroyed after unmounted, we need to recreate it if it not exist
|
// element will be destroyed after unmounted, we need to recreate it if it not exist
|
||||||
// or we try to remount into a new container
|
// or we try to remount into a new container
|
||||||
appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceName);
|
appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceId);
|
||||||
syncAppWrapperElement2Sandbox(appWrapperElement);
|
syncAppWrapperElement2Sandbox(appWrapperElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,7 +415,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
const measureName = `[qiankun] App ${appInstanceName} Loading Consuming`;
|
const measureName = `[qiankun] App ${appInstanceId} Loading Consuming`;
|
||||||
performanceMeasure(measureName, markName);
|
performanceMeasure(measureName, markName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -427,7 +427,7 @@ export async function loadApp<T extends ObjectType>(
|
||||||
async () => execHooksChain(toArray(afterUnmount), app, global),
|
async () => execHooksChain(toArray(afterUnmount), app, global),
|
||||||
async () => {
|
async () => {
|
||||||
render({ element: null, loading: false, container: remountContainer }, 'unmounted');
|
render({ element: null, loading: false, container: remountContainer }, 'unmounted');
|
||||||
offGlobalStateChange(appInstanceName);
|
offGlobalStateChange(appInstanceId);
|
||||||
// for gc
|
// for gc
|
||||||
appWrapperElement = null;
|
appWrapperElement = null;
|
||||||
syncAppWrapperElement2Sandbox(appWrapperElement);
|
syncAppWrapperElement2Sandbox(appWrapperElement);
|
||||||
|
|
|
||||||
32
src/utils.ts
32
src/utils.ts
|
|
@ -3,7 +3,7 @@
|
||||||
* @since 2019-05-15
|
* @since 2019-05-15
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isFunction, snakeCase } from 'lodash';
|
import { isFunction, snakeCase, once } from 'lodash';
|
||||||
import { version } from './version';
|
import { version } from './version';
|
||||||
|
|
||||||
import type { FrameworkConfiguration } from './interfaces';
|
import type { FrameworkConfiguration } from './interfaces';
|
||||||
|
|
@ -111,23 +111,31 @@ export function getWrapperId(name: string) {
|
||||||
|
|
||||||
export const nativeGlobal = new Function('return this')();
|
export const nativeGlobal = new Function('return this')();
|
||||||
|
|
||||||
Object.defineProperty(nativeGlobal, '__app_instance_name_map__', {
|
const getGlobalAppInstanceMap = once<() => Record<string, number>>(() => {
|
||||||
enumerable: false,
|
if (!nativeGlobal.hasOwnProperty('__app_instance_name_map__')) {
|
||||||
writable: true,
|
Object.defineProperty(nativeGlobal, '__app_instance_name_map__', {
|
||||||
value: {},
|
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
|
* @param appName
|
||||||
*/
|
*/
|
||||||
export const getAppInstanceName = (appName: string): string => {
|
export const genAppInstanceIdByName = (appName: string): string => {
|
||||||
if (!(appName in nativeGlobal.__app_instance_name_map__)) {
|
const globalAppInstanceMap = getGlobalAppInstanceMap();
|
||||||
|
if (!(appName in globalAppInstanceMap)) {
|
||||||
nativeGlobal.__app_instance_name_map__[appName] = 0;
|
nativeGlobal.__app_instance_name_map__[appName] = 0;
|
||||||
return appName;
|
return appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeGlobal.__app_instance_name_map__[appName]++;
|
globalAppInstanceMap[appName]++;
|
||||||
return `${appName}_${nativeGlobal.__app_instance_name_map__[appName]}`;
|
return `${appName}_${globalAppInstanceMap[appName]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 校验子应用导出的 生命周期 对象是否正确 */
|
/** 校验子应用导出的 生命周期 对象是否正确 */
|
||||||
|
|
@ -136,7 +144,7 @@ export function validateExportLifecycle(exports: any) {
|
||||||
return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount);
|
return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Deferred<T> {
|
export class Deferred<T> {
|
||||||
promise: Promise<T>;
|
promise: Promise<T>;
|
||||||
|
|
||||||
resolve!: (value: T | PromiseLike<T>) => void;
|
resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
|
|
@ -151,8 +159,6 @@ class Deferred<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Deferred };
|
|
||||||
|
|
||||||
const supportsUserTiming =
|
const supportsUserTiming =
|
||||||
typeof performance !== 'undefined' &&
|
typeof performance !== 'undefined' &&
|
||||||
typeof performance.mark === 'function' &&
|
typeof performance.mark === 'function' &&
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user