Skip to content

Commit

Permalink
🐛 app instance id generator compatible with nested sandbox (umijs#1866)
Browse files Browse the repository at this point in the history
  • Loading branch information
kuitos authored Dec 10, 2021
1 parent e19c06d commit b103078
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 41 deletions.
12 changes: 12 additions & 0 deletions src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Deferred,
genAppInstanceIdByName,
getDefaultTplWrapper,
getWrapperId,
getXPathForElement,
Expand Down Expand Up @@ -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');
});
56 changes: 28 additions & 28 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
import { createSandboxContainer, css } from './sandbox';
import {
Deferred,
getAppInstanceName,
genAppInstanceIdByName,
getContainer,
getDefaultTplWrapper,
getWrapperId,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
}

Expand All @@ -111,7 +111,7 @@ function createElement(

/** generate app wrapper dom getter */
function getAppWrapperGetter(
appName: string,
appInstanceId: string,
useLegacyRender: boolean,
strictStyleIsolation: boolean,
scopedCSS: boolean,
Expand All @@ -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!;
Expand All @@ -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') {
Expand All @@ -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);
Expand Down Expand Up @@ -246,10 +246,10 @@ export async function loadApp<T extends ObjectType>(
configuration: FrameworkConfiguration = {},
lifeCycles?: FrameworkLifeCycles<T>,
): Promise<ParcelConfigObjectGetter> {
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);
}
Expand All @@ -272,7 +272,7 @@ export async function loadApp<T extends ObjectType>(
await (prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise);
}

const appContent = getDefaultTplWrapper(appInstanceName)(template);
const appContent = getDefaultTplWrapper(appInstanceId)(template);

const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;

Expand All @@ -287,20 +287,20 @@ export async function loadApp<T extends ObjectType>(
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,
Expand All @@ -314,7 +314,7 @@ export async function loadApp<T extends ObjectType>(
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,
Expand Down Expand Up @@ -342,13 +342,13 @@ export async function loadApp<T extends ObjectType>(
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<string, CallableFunction> =
getMicroAppStateActions(appInstanceName);
getMicroAppStateActions(appInstanceId);

// FIXME temporary way
const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
Expand All @@ -358,7 +358,7 @@ export async function loadApp<T extends ObjectType>(
let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;

const parcelConfig: ParcelConfigObject = {
name: appInstanceName,
name: appInstanceId,
bootstrap,
mount: [
async () => {
Expand All @@ -381,7 +381,7 @@ export async function loadApp<T extends ObjectType>(
async () => {
appWrapperElement = initialAppWrapperElement;
appWrapperGetter = getAppWrapperGetter(
appInstanceName,
appInstanceId,
!!legacyRender,
strictStyleIsolation,
scopedCSS,
Expand All @@ -394,7 +394,7 @@ export async function loadApp<T extends ObjectType>(
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);
}

Expand All @@ -415,7 +415,7 @@ export async function loadApp<T extends ObjectType>(
},
async () => {
if (process.env.NODE_ENV === 'development') {
const measureName = `[qiankun] App ${appInstanceName} Loading Consuming`;
const measureName = `[qiankun] App ${appInstanceId} Loading Consuming`;
performanceMeasure(measureName, markName);
}
},
Expand All @@ -427,7 +427,7 @@ export async function loadApp<T extends ObjectType>(
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);
Expand Down
32 changes: 19 additions & 13 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string, number>>(() => {
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]}`;
};

/** 校验子应用导出的 生命周期 对象是否正确 */
Expand All @@ -136,7 +144,7 @@ export function validateExportLifecycle(exports: any) {
return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount);
}

class Deferred<T> {
export class Deferred<T> {
promise: Promise<T>;

resolve!: (value: T | PromiseLike<T>) => void;
Expand All @@ -151,8 +159,6 @@ class Deferred<T> {
}
}

export { Deferred };

const supportsUserTiming =
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
Expand Down

0 comments on commit b103078

Please sign in to comment.