🐛 compatible with overwritten appendChild by apps themselves (#2449)

This commit is contained in:
Kuitos 2023-03-29 20:37:35 +08:00 committed by GitHub
parent 601696ad7f
commit 6e5b145046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 23 deletions

View File

@ -9,18 +9,12 @@ import { qiankunHeadTagName } from '../../../utils';
import { cachedGlobals } from '../../proxySandbox'; import { cachedGlobals } from '../../proxySandbox';
import * as css from '../css'; import * as css from '../css';
export const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
const rawHeadRemoveChild = HTMLHeadElement.prototype.removeChild;
const rawBodyAppendChild = HTMLBodyElement.prototype.appendChild;
const rawBodyRemoveChild = HTMLBodyElement.prototype.removeChild;
const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore;
const rawRemoveChild = HTMLElement.prototype.removeChild;
const SCRIPT_TAG_NAME = 'SCRIPT'; const SCRIPT_TAG_NAME = 'SCRIPT';
const LINK_TAG_NAME = 'LINK'; const LINK_TAG_NAME = 'LINK';
const STYLE_TAG_NAME = 'STYLE'; const STYLE_TAG_NAME = 'STYLE';
export const styleElementTargetSymbol = Symbol('target'); export const styleElementTargetSymbol = Symbol('target');
const overwrittenSymbol = Symbol('qiankun-overwritten');
type DynamicDomMutationTarget = 'head' | 'body'; type DynamicDomMutationTarget = 'head' | 'body';
@ -32,6 +26,10 @@ declare global {
interface HTMLStyleElement { interface HTMLStyleElement {
[styleElementTargetSymbol]: DynamicDomMutationTarget; [styleElementTargetSymbol]: DynamicDomMutationTarget;
} }
interface Function {
[overwrittenSymbol]: boolean;
}
} }
export const getAppWrapperHeadElement = (appWrapper: Element | ShadowRoot): Element => { export const getAppWrapperHeadElement = (appWrapper: Element | ShadowRoot): Element => {
@ -69,6 +67,7 @@ export function isStyledComponentsLike(element: HTMLStyleElement) {
} }
const appsCounterMap = new Map<string, { bootstrappingPatchCount: number; mountingPatchCount: number }>(); const appsCounterMap = new Map<string, { bootstrappingPatchCount: number; mountingPatchCount: number }>();
export function calcAppCount( export function calcAppCount(
appName: string, appName: string,
calcType: 'increase' | 'decrease', calcType: 'increase' | 'decrease',
@ -88,6 +87,7 @@ export function calcAppCount(
} }
appsCounterMap.set(appName, appCount); appsCounterMap.set(appName, appCount);
} }
export function isAllAppsUnmounted(): boolean { export function isAllAppsUnmounted(): boolean {
return Array.from(appsCounterMap.entries()).every( return Array.from(appsCounterMap.entries()).every(
([, { bootstrappingPatchCount: bpc, mountingPatchCount: mpc }]) => bpc === 0 && mpc === 0, ([, { bootstrappingPatchCount: bpc, mountingPatchCount: mpc }]) => bpc === 0 && mpc === 0,
@ -197,7 +197,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
containerConfigGetter: (element: HTMLElement) => ContainerConfig; containerConfigGetter: (element: HTMLElement) => ContainerConfig;
target: DynamicDomMutationTarget; target: DynamicDomMutationTarget;
}) { }) {
return function appendChildOrInsertBefore<T extends Node>( function appendChildOrInsertBefore<T extends Node>(
this: HTMLHeadElement | HTMLBodyElement, this: HTMLHeadElement | HTMLBodyElement,
newChild: T, newChild: T,
refChild: Node | null = null, refChild: Node | null = null,
@ -339,19 +339,22 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
} }
return rawDOMAppendOrInsertBefore.call(this, element, refChild); return rawDOMAppendOrInsertBefore.call(this, element, refChild);
}; }
appendChildOrInsertBefore[overwrittenSymbol] = true;
return appendChildOrInsertBefore;
} }
function getNewRemoveChild( function getNewRemoveChild(
headOrBodyRemoveChild: typeof HTMLElement.prototype.removeChild, rawRemoveChild: typeof HTMLElement.prototype.removeChild,
containerConfigGetter: (element: HTMLElement) => ContainerConfig, containerConfigGetter: (element: HTMLElement) => ContainerConfig,
target: DynamicDomMutationTarget, target: DynamicDomMutationTarget,
isInvokedByMicroApp: (element: HTMLElement) => boolean, isInvokedByMicroApp: (element: HTMLElement) => boolean,
) { ) {
return function removeChild<T extends Node>(this: HTMLHeadElement | HTMLBodyElement, child: T) { function removeChild<T extends Node>(this: HTMLHeadElement | HTMLBodyElement, child: T) {
const { tagName } = child as any; const { tagName } = child as any;
if (!isHijackingTag(tagName) || !isInvokedByMicroApp(child as any)) if (!isHijackingTag(tagName) || !isInvokedByMicroApp(child as any)) return rawRemoveChild.call(this, child) as T;
return headOrBodyRemoveChild.call(this, child) as T;
try { try {
let attachedElement: Node; let attachedElement: Node;
@ -391,19 +394,26 @@ function getNewRemoveChild(
console.warn(e); console.warn(e);
} }
return headOrBodyRemoveChild.call(this, child) as T; return rawRemoveChild.call(this, child) as T;
}; }
removeChild[overwrittenSymbol] = true;
return removeChild;
} }
export function patchHTMLDynamicAppendPrototypeFunctions( export function patchHTMLDynamicAppendPrototypeFunctions(
isInvokedByMicroApp: (element: HTMLElement) => boolean, isInvokedByMicroApp: (element: HTMLElement) => boolean,
containerConfigGetter: (element: HTMLElement) => ContainerConfig, containerConfigGetter: (element: HTMLElement) => ContainerConfig,
) { ) {
const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
const rawBodyAppendChild = HTMLBodyElement.prototype.appendChild;
const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore;
// Just overwrite it while it have not been overwritten // Just overwrite it while it have not been overwritten
if ( if (
HTMLHeadElement.prototype.appendChild === rawHeadAppendChild && rawHeadAppendChild[overwrittenSymbol] !== true &&
HTMLBodyElement.prototype.appendChild === rawBodyAppendChild && rawBodyAppendChild[overwrittenSymbol] !== true &&
HTMLHeadElement.prototype.insertBefore === rawHeadInsertBefore rawHeadInsertBefore[overwrittenSymbol] !== true
) { ) {
HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({ HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
rawDOMAppendOrInsertBefore: rawHeadAppendChild, rawDOMAppendOrInsertBefore: rawHeadAppendChild,
@ -426,11 +436,10 @@ export function patchHTMLDynamicAppendPrototypeFunctions(
}) as typeof rawHeadInsertBefore; }) as typeof rawHeadInsertBefore;
} }
const rawHeadRemoveChild = HTMLHeadElement.prototype.removeChild;
const rawBodyRemoveChild = HTMLBodyElement.prototype.removeChild;
// Just overwrite it while it have not been overwritten // Just overwrite it while it have not been overwritten
if ( if (rawHeadRemoveChild[overwrittenSymbol] !== true && rawBodyRemoveChild[overwrittenSymbol] !== true) {
HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild &&
HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild
) {
HTMLHeadElement.prototype.removeChild = getNewRemoveChild( HTMLHeadElement.prototype.removeChild = getNewRemoveChild(
rawHeadRemoveChild, rawHeadRemoveChild,
containerConfigGetter, containerConfigGetter,

View File

@ -13,7 +13,6 @@ import {
isAllAppsUnmounted, isAllAppsUnmounted,
isHijackingTag, isHijackingTag,
patchHTMLDynamicAppendPrototypeFunctions, patchHTMLDynamicAppendPrototypeFunctions,
rawHeadAppendChild,
rebuildCSSRules, rebuildCSSRules,
recordStyledComponentsCSSRules, recordStyledComponentsCSSRules,
styleElementTargetSymbol, styleElementTargetSymbol,
@ -22,6 +21,8 @@ import {
// Get native global window with a sandbox disgusted way, thus we could share it between qiankun instances🤪 // Get native global window with a sandbox disgusted way, thus we could share it between qiankun instances🤪
Object.defineProperty(nativeGlobal, '__proxyAttachContainerConfigMap__', { enumerable: false, writable: true }); Object.defineProperty(nativeGlobal, '__proxyAttachContainerConfigMap__', { enumerable: false, writable: true });
const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
// Share proxyAttachContainerConfigMap between multiple qiankun instance, thus they could access the same record // Share proxyAttachContainerConfigMap between multiple qiankun instance, thus they could access the same record
nativeGlobal.__proxyAttachContainerConfigMap__ = nativeGlobal.__proxyAttachContainerConfigMap__ =
nativeGlobal.__proxyAttachContainerConfigMap__ || new WeakMap<WindowProxy, ContainerConfig>(); nativeGlobal.__proxyAttachContainerConfigMap__ || new WeakMap<WindowProxy, ContainerConfig>();