🐛 should use the new element reference while in cache scenario (#1021)

This commit is contained in:
Kuitos 2020-10-23 17:44:29 +08:00 committed by GitHub
parent c7b0fb8c37
commit e0cc86019b

View File

@ -137,7 +137,7 @@ function getAppWrapperGetter(
const rawAppendChild = HTMLElement.prototype.appendChild; const rawAppendChild = HTMLElement.prototype.appendChild;
const rawRemoveChild = HTMLElement.prototype.removeChild; const rawRemoveChild = HTMLElement.prototype.removeChild;
type ElementRender = ( type ElementRender = (
props: { element: HTMLElement | null; loading: boolean; remountContainer?: string | HTMLElement }, props: { element: HTMLElement | null; loading: boolean; container?: string | HTMLElement },
phase: 'loading' | 'mounting' | 'mounted' | 'unmounted', phase: 'loading' | 'mounting' | 'mounted' | 'unmounted',
) => any; ) => any;
@ -146,16 +146,10 @@ type ElementRender = (
* 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 appName
* @param appContent * @param appContent
* @param container
* @param legacyRender * @param legacyRender
*/ */
function getRender( function getRender(appName: string, appContent: string, legacyRender?: HTMLContentRender) {
appName: string, const render: ElementRender = ({ element, loading, container }, phase) => {
appContent: string,
container?: string | HTMLElement,
legacyRender?: HTMLContentRender,
) {
const render: ElementRender = ({ element, loading, remountContainer }, phase) => {
if (legacyRender) { if (legacyRender) {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
console.warn( console.warn(
@ -166,7 +160,7 @@ function getRender(
return legacyRender({ loading, appContent: element ? appContent : '' }); return legacyRender({ loading, appContent: element ? appContent : '' });
} }
const containerElement = getContainer(remountContainer || container!); const containerElement = getContainer(container!);
// The container might have be removed after micro app unmounted. // The container might have be removed after micro app unmounted.
// Such as the micro app unmount lifecycle called by a react componentWillUnmount lifecycle, after micro app unmounted, the react component might also be removed // Such as the micro app unmount lifecycle called by a react componentWillUnmount lifecycle, after micro app unmounted, the react component might also be removed
@ -258,24 +252,29 @@ export async function loadApp<T extends object>(
const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation; const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;
const scopedCSS = isEnableScopedCSS(sandbox); const scopedCSS = isEnableScopedCSS(sandbox);
let appWrapperElement: HTMLElement | null = createElement(appContent, strictStyleIsolation, scopedCSS, appName); let initialAppWrapperElement: HTMLElement | null = createElement(
appContent,
strictStyleIsolation,
scopedCSS,
appName,
);
const container = '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(appName, appContent, container, legacyRender); const render = getRender(appName, appContent, legacyRender);
// 第一次加载设置应用可见区域 dom 结构 // 第一次加载设置应用可见区域 dom 结构
// 确保每次应用加载前容器 dom 结构已经设置完毕 // 确保每次应用加载前容器 dom 结构已经设置完毕
render({ element: appWrapperElement, loading: true }, 'loading'); render({ element: initialAppWrapperElement, loading: true, container: initialContainer }, 'loading');
const appWrapperGetter = getAppWrapperGetter( const initialAppWrapperGetter = getAppWrapperGetter(
appName, appName,
appInstanceId, appInstanceId,
!!legacyRender, !!legacyRender,
strictStyleIsolation, strictStyleIsolation,
scopedCSS, scopedCSS,
() => appWrapperElement, () => initialAppWrapperElement,
); );
let global = window; let global = window;
@ -283,7 +282,14 @@ export async function loadApp<T extends object>(
let unmountSandbox = () => Promise.resolve(); let unmountSandbox = () => Promise.resolve();
const useLooseSandbox = typeof sandbox === 'object' && !!sandbox.loose; const useLooseSandbox = typeof sandbox === 'object' && !!sandbox.loose;
if (sandbox) { if (sandbox) {
const sandboxInstance = createSandbox(appName, appWrapperGetter, scopedCSS, useLooseSandbox, excludeAssetFilter); const sandboxInstance = createSandbox(
appName,
// FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518
initialAppWrapperGetter,
scopedCSS,
useLooseSandbox,
excludeAssetFilter,
);
// 用沙箱的代理对象作为接下来使用的全局对象 // 用沙箱的代理对象作为接下来使用的全局对象
global = sandboxInstance.proxy as typeof window; global = sandboxInstance.proxy as typeof window;
mountSandbox = sandboxInstance.mount; mountSandbox = sandboxInstance.mount;
@ -309,7 +315,20 @@ export async function loadApp<T extends object>(
offGlobalStateChange, offGlobalStateChange,
}: Record<string, Function> = getMicroAppStateActions(appInstanceId); }: Record<string, Function> = getMicroAppStateActions(appInstanceId);
const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer) => { // FIXME temporary way
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,
);
const parcelConfig: ParcelConfigObject = { const parcelConfig: ParcelConfigObject = {
name: appInstanceId, name: appInstanceId,
bootstrap, bootstrap,
@ -332,16 +351,22 @@ export async function loadApp<T extends object>(
}, },
// 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕 // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕
async () => { async () => {
// element would be destroyed after unmounted, we need to recreate it if it not exist const useNewContainer = remountContainer !== initialContainer;
appWrapperElement = appWrapperElement || createElement(appContent, strictStyleIsolation, scopedCSS, appName); if (useNewContainer || !appWrapperElement) {
render({ element: appWrapperElement, loading: true, remountContainer }, 'mounting'); // 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, appName);
syncAppWrapperElement2Sandbox(appWrapperElement);
}
render({ element: appWrapperElement, loading: true, container: remountContainer }, 'mounting');
}, },
mountSandbox, mountSandbox,
// exec the chain after rendering to keep the behavior with beforeLoad // exec the chain after rendering to keep the behavior with beforeLoad
async () => execHooksChain(toArray(beforeMount), app, global), async () => execHooksChain(toArray(beforeMount), app, global),
async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }), async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }),
// finish loading after app mounted // finish loading after app mounted
async () => render({ element: appWrapperElement, loading: false, remountContainer }, 'mounted'), async () => render({ element: appWrapperElement, loading: false, container: remountContainer }, 'mounted'),
async () => execHooksChain(toArray(afterMount), app, global), async () => execHooksChain(toArray(afterMount), app, global),
// initialize the unmount defer after app mounted and resolve the defer after it unmounted // initialize the unmount defer after app mounted and resolve the defer after it unmounted
async () => { async () => {
@ -362,10 +387,11 @@ export async function loadApp<T extends object>(
unmountSandbox, unmountSandbox,
async () => execHooksChain(toArray(afterUnmount), app, global), async () => execHooksChain(toArray(afterUnmount), app, global),
async () => { async () => {
render({ element: null, loading: false, remountContainer }, 'unmounted'); render({ element: null, loading: false, container: remountContainer }, 'unmounted');
offGlobalStateChange(appInstanceId); offGlobalStateChange(appInstanceId);
// for gc // for gc
appWrapperElement = null; appWrapperElement = null;
syncAppWrapperElement2Sandbox(appWrapperElement);
}, },
async () => { async () => {
if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) { if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) {