From 9c7705553598f30555c58a28b5a978039068c6a9 Mon Sep 17 00:00:00 2001 From: Kuitos Date: Sun, 29 May 2022 22:41:10 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix=20the=20simulated=20head=20i?= =?UTF-8?q?ssues=20(#2121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/main/index.js | 12 ++--- examples/react15/index.js | 13 +++-- src/__tests__/utils.test.ts | 3 +- src/sandbox/patchers/dynamicAppend/common.ts | 50 +++++++++---------- .../dynamicAppend/forStrictSandbox.ts | 2 +- src/utils.ts | 16 ++++-- src/version.ts | 2 +- 7 files changed, 57 insertions(+), 41 deletions(-) diff --git a/examples/main/index.js b/examples/main/index.js index 8dcfe9d..d0c5458 100644 --- a/examples/main/index.js +++ b/examples/main/index.js @@ -1,12 +1,12 @@ import 'zone.js'; // for angular subapp -import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from '../../es'; +import { initGlobalState, registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from '../../es'; import './index.less'; - /** * 主应用 **可以使用任意技术栈** * 以下分别是 React 和 Vue 的示例,可切换尝试 */ import render from './render/ReactRender'; + // import render from './render/VueRender'; /** @@ -14,7 +14,7 @@ import render from './render/ReactRender'; */ render({ loading: true }); -const loader = loading => render({ loading }); +const loader = (loading) => render({ loading }); /** * Step2 注册子应用 @@ -67,17 +67,17 @@ registerMicroApps( ], { beforeLoad: [ - app => { + (app) => { console.log('[LifeCycle] before load %c%s', 'color: green;', app.name); }, ], beforeMount: [ - app => { + (app) => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name); }, ], afterUnmount: [ - app => { + (app) => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name); }, ], diff --git a/examples/react15/index.js b/examples/react15/index.js index 54d2dbf..750416e 100644 --- a/examples/react15/index.js +++ b/examples/react15/index.js @@ -2,13 +2,12 @@ * @author Kuitos * @since 2019-05-16 */ -import './public-path'; +import 'antd/dist/antd.min.css'; import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; - -import 'antd/dist/antd.min.css'; import './index.css'; +import './public-path'; export async function bootstrap() { console.log('[react15] react app bootstraped'); @@ -24,6 +23,14 @@ export async function mount(props = {}) { import('./dynamic.css').then(() => { console.log('[react15] dynamic style load'); }); + + const styleElement = document.createElement('style'); + styleElement.innerText = '.react15-icon { height: 400px }'; + document.head.appendChild(styleElement); + + setTimeout(() => { + document.head.removeChild(styleElement); + }, 2000); } export async function unmount(props) { diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 2fb1683..6eb0477 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -29,7 +29,8 @@ test('should wrap string with div', () => { const ret = factory(tpl); expect(ret).toBe( - `
${tpl}
`, + // eslint-disable-next-line max-len + `
${tpl}
`, ); }); diff --git a/src/sandbox/patchers/dynamicAppend/common.ts b/src/sandbox/patchers/dynamicAppend/common.ts index 3e62daf..c301e83 100644 --- a/src/sandbox/patchers/dynamicAppend/common.ts +++ b/src/sandbox/patchers/dynamicAppend/common.ts @@ -21,19 +21,19 @@ const STYLE_TAG_NAME = 'STYLE'; export const styleElementTargetSymbol = Symbol('target'); -type DynamicAppendTarget = 'head' | 'body'; +type DynamicDomMutationTarget = 'head' | 'body'; declare global { interface HTMLLinkElement { - [styleElementTargetSymbol]: DynamicAppendTarget; + [styleElementTargetSymbol]: DynamicDomMutationTarget; } interface HTMLStyleElement { - [styleElementTargetSymbol]: DynamicAppendTarget; + [styleElementTargetSymbol]: DynamicDomMutationTarget; } } -export const getAppWrapperHeadElement = (appWrapper: Element | ShadowRoot) => { +export const getAppWrapperHeadElement = (appWrapper: Element | ShadowRoot): Element => { const rootElement = 'host' in appWrapper ? appWrapper.host : appWrapper; return rootElement.getElementsByTagName(qiankunHeadTagName)[0]; }; @@ -158,7 +158,7 @@ export type ContainerConfig = { appName: string; proxy: WindowProxy; strictGlobal: boolean; - dynamicStyleSheetElements: HTMLStyleElement[]; + dynamicStyleSheetElements: Array; appWrapperGetter: CallableFunction; scopedCSS: boolean; excludeAssetFilter?: CallableFunction; @@ -168,7 +168,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { rawDOMAppendOrInsertBefore: (newChild: T, refChild?: Node | null) => T; isInvokedByMicroApp: (element: HTMLElement) => boolean; containerConfigGetter: (element: HTMLElement) => ContainerConfig; - target: DynamicAppendTarget; + target: DynamicDomMutationTarget; }) { return function appendChildOrInsertBefore( this: HTMLHeadElement | HTMLBodyElement, @@ -209,7 +209,6 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { }); const appWrapper = appWrapperGetter(); - const mountDOM = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; if (scopedCSS) { // exclude link elements like @@ -224,16 +223,17 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { : frameworkConfiguration.fetch?.fn; stylesheetElement = convertLinkAsStyle( element, - (styleElement) => css.process(mountDOM, styleElement, appName), + (styleElement) => css.process(appWrapper, styleElement, appName), fetch, ); dynamicLinkAttachedInlineStyleMap.set(element, stylesheetElement); } else { - css.process(mountDOM, stylesheetElement, appName); + css.process(appWrapper, stylesheetElement, appName); } } - // eslint-disable-next-line no-shadow + const mountDOM = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; + dynamicStyleSheetElements.push(stylesheetElement); const referenceNode = mountDOM.contains(refChild) ? refChild : null; return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode); @@ -301,7 +301,8 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { function getNewRemoveChild( headOrBodyRemoveChild: typeof HTMLElement.prototype.removeChild, - appWrapperGetterGetter: (element: HTMLElement) => ContainerConfig['appWrapperGetter'], + containerConfigGetter: (element: HTMLElement) => ContainerConfig, + target: DynamicDomMutationTarget, ) { return function removeChild(this: HTMLHeadElement | HTMLBodyElement, child: T) { const { tagName } = child as any; @@ -309,14 +310,19 @@ function getNewRemoveChild( try { let attachedElement: Node; + const { appWrapperGetter, dynamicStyleSheetElements } = containerConfigGetter(child as any); + switch (tagName) { + case STYLE_TAG_NAME: case LINK_TAG_NAME: { - attachedElement = (dynamicLinkAttachedInlineStyleMap.get(child as any) as Node) || child; + attachedElement = dynamicLinkAttachedInlineStyleMap.get(child as any) || child; + // try to remove the dynamic style sheet + dynamicStyleSheetElements.splice(dynamicStyleSheetElements.indexOf(attachedElement as any), 1); break; } case SCRIPT_TAG_NAME: { - attachedElement = (dynamicScriptAttachedCommentMap.get(child as any) as Node) || child; + attachedElement = dynamicScriptAttachedCommentMap.get(child as any) || child; break; } @@ -325,11 +331,11 @@ function getNewRemoveChild( } } - // container may had been removed while app unmounting if the removeChild action was async - const appWrapperGetter = appWrapperGetterGetter(child as any); - const container = appWrapperGetter(); + const appWrapper = appWrapperGetter(); + const container = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; + // container might have been removed while app unmounting if the removeChild action was async if (container.contains(attachedElement)) { - return rawRemoveChild.call(container, attachedElement) as T; + return rawRemoveChild.call(attachedElement.parentNode, attachedElement) as T; } } catch (e) { console.warn(e); @@ -375,14 +381,8 @@ export function patchHTMLDynamicAppendPrototypeFunctions( HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild && HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild ) { - HTMLHeadElement.prototype.removeChild = getNewRemoveChild( - rawHeadRemoveChild, - (element) => containerConfigGetter(element).appWrapperGetter, - ); - HTMLBodyElement.prototype.removeChild = getNewRemoveChild( - rawBodyRemoveChild, - (element) => containerConfigGetter(element).appWrapperGetter, - ); + HTMLHeadElement.prototype.removeChild = getNewRemoveChild(rawHeadRemoveChild, containerConfigGetter, 'head'); + HTMLBodyElement.prototype.removeChild = getNewRemoveChild(rawBodyRemoveChild, containerConfigGetter, 'body'); } return function unpatch() { diff --git a/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts b/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts index c301b4e..7f00314 100644 --- a/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts +++ b/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts @@ -117,7 +117,7 @@ export function patchStrictSandbox( if (mounting) mountingPatchCount--; const allMicroAppUnmounted = mountingPatchCount === 0 && bootstrappingPatchCount === 0; - // release the overwrite prototype after all the micro apps unmounted + // release the overwritten prototype after all the micro apps unmounted if (allMicroAppUnmounted) { unpatchDynamicAppendPrototypeFunctions(); unpatchDocumentCreate(); diff --git a/src/utils.ts b/src/utils.ts index 18cc1d1..c81e723 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -107,10 +107,18 @@ export const qiankunHeadTagName = 'qiankun-head'; export function getDefaultTplWrapper(name: string) { return (tpl: string) => { - // We need to mock a head placeholder as native head element will be erased by browser in micro app - const tplWithSimulatedHead = tpl - .replace('', `<${qiankunHeadTagName}>`) - .replace('', ``); + let tplWithSimulatedHead: string; + + if (tpl.indexOf('') !== -1) { + // We need to mock a head placeholder as native head element will be erased by browser in micro app + tplWithSimulatedHead = tpl + .replace('', `<${qiankunHeadTagName}>`) + .replace('', ``); + } else { + // Some template might not be a standard html document, thus we need to add a simulated head tag for them + tplWithSimulatedHead = `<${qiankunHeadTagName}>${tpl}`; + } + return `
${tplWithSimulatedHead}
`; diff --git a/src/version.ts b/src/version.ts index a725083..edf45a6 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export { version } from '../package.json'; +export const version = '2.7.1';