🐛 fix the simulated head issues (#2121)
This commit is contained in:
parent
a885c4ab8a
commit
9c77055535
|
|
@ -1,12 +1,12 @@
|
||||||
import 'zone.js'; // for angular subapp
|
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';
|
import './index.less';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主应用 **可以使用任意技术栈**
|
* 主应用 **可以使用任意技术栈**
|
||||||
* 以下分别是 React 和 Vue 的示例,可切换尝试
|
* 以下分别是 React 和 Vue 的示例,可切换尝试
|
||||||
*/
|
*/
|
||||||
import render from './render/ReactRender';
|
import render from './render/ReactRender';
|
||||||
|
|
||||||
// import render from './render/VueRender';
|
// import render from './render/VueRender';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,7 +14,7 @@ import render from './render/ReactRender';
|
||||||
*/
|
*/
|
||||||
render({ loading: true });
|
render({ loading: true });
|
||||||
|
|
||||||
const loader = loading => render({ loading });
|
const loader = (loading) => render({ loading });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step2 注册子应用
|
* Step2 注册子应用
|
||||||
|
|
@ -67,17 +67,17 @@ registerMicroApps(
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
beforeLoad: [
|
beforeLoad: [
|
||||||
app => {
|
(app) => {
|
||||||
console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
|
console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
beforeMount: [
|
beforeMount: [
|
||||||
app => {
|
(app) => {
|
||||||
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
|
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
afterUnmount: [
|
afterUnmount: [
|
||||||
app => {
|
(app) => {
|
||||||
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
|
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
* @author Kuitos
|
* @author Kuitos
|
||||||
* @since 2019-05-16
|
* @since 2019-05-16
|
||||||
*/
|
*/
|
||||||
import './public-path';
|
import 'antd/dist/antd.min.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
import 'antd/dist/antd.min.css';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
import './public-path';
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
console.log('[react15] react app bootstraped');
|
console.log('[react15] react app bootstraped');
|
||||||
|
|
@ -24,6 +23,14 @@ export async function mount(props = {}) {
|
||||||
import('./dynamic.css').then(() => {
|
import('./dynamic.css').then(() => {
|
||||||
console.log('[react15] dynamic style load');
|
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) {
|
export async function unmount(props) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ test('should wrap string with div', () => {
|
||||||
const ret = factory(tpl);
|
const ret = factory(tpl);
|
||||||
|
|
||||||
expect(ret).toBe(
|
expect(ret).toBe(
|
||||||
`<div id="__qiankun_microapp_wrapper_for_react_16__" data-name="react16" data-version="${version}">${tpl}</div>`,
|
// eslint-disable-next-line max-len
|
||||||
|
`<div id="__qiankun_microapp_wrapper_for_react_16__" data-name="react16" data-version="${version}"><qiankun-head></qiankun-head>${tpl}</div>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,19 @@ const STYLE_TAG_NAME = 'STYLE';
|
||||||
|
|
||||||
export const styleElementTargetSymbol = Symbol('target');
|
export const styleElementTargetSymbol = Symbol('target');
|
||||||
|
|
||||||
type DynamicAppendTarget = 'head' | 'body';
|
type DynamicDomMutationTarget = 'head' | 'body';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLLinkElement {
|
interface HTMLLinkElement {
|
||||||
[styleElementTargetSymbol]: DynamicAppendTarget;
|
[styleElementTargetSymbol]: DynamicDomMutationTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLStyleElement {
|
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;
|
const rootElement = 'host' in appWrapper ? appWrapper.host : appWrapper;
|
||||||
return rootElement.getElementsByTagName(qiankunHeadTagName)[0];
|
return rootElement.getElementsByTagName(qiankunHeadTagName)[0];
|
||||||
};
|
};
|
||||||
|
|
@ -158,7 +158,7 @@ export type ContainerConfig = {
|
||||||
appName: string;
|
appName: string;
|
||||||
proxy: WindowProxy;
|
proxy: WindowProxy;
|
||||||
strictGlobal: boolean;
|
strictGlobal: boolean;
|
||||||
dynamicStyleSheetElements: HTMLStyleElement[];
|
dynamicStyleSheetElements: Array<HTMLStyleElement | HTMLLinkElement>;
|
||||||
appWrapperGetter: CallableFunction;
|
appWrapperGetter: CallableFunction;
|
||||||
scopedCSS: boolean;
|
scopedCSS: boolean;
|
||||||
excludeAssetFilter?: CallableFunction;
|
excludeAssetFilter?: CallableFunction;
|
||||||
|
|
@ -168,7 +168,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
||||||
rawDOMAppendOrInsertBefore: <T extends Node>(newChild: T, refChild?: Node | null) => T;
|
rawDOMAppendOrInsertBefore: <T extends Node>(newChild: T, refChild?: Node | null) => T;
|
||||||
isInvokedByMicroApp: (element: HTMLElement) => boolean;
|
isInvokedByMicroApp: (element: HTMLElement) => boolean;
|
||||||
containerConfigGetter: (element: HTMLElement) => ContainerConfig;
|
containerConfigGetter: (element: HTMLElement) => ContainerConfig;
|
||||||
target: DynamicAppendTarget;
|
target: DynamicDomMutationTarget;
|
||||||
}) {
|
}) {
|
||||||
return function appendChildOrInsertBefore<T extends Node>(
|
return function appendChildOrInsertBefore<T extends Node>(
|
||||||
this: HTMLHeadElement | HTMLBodyElement,
|
this: HTMLHeadElement | HTMLBodyElement,
|
||||||
|
|
@ -209,7 +209,6 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
||||||
});
|
});
|
||||||
|
|
||||||
const appWrapper = appWrapperGetter();
|
const appWrapper = appWrapperGetter();
|
||||||
const mountDOM = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper;
|
|
||||||
|
|
||||||
if (scopedCSS) {
|
if (scopedCSS) {
|
||||||
// exclude link elements like <link rel="icon" href="favicon.ico">
|
// exclude link elements like <link rel="icon" href="favicon.ico">
|
||||||
|
|
@ -224,16 +223,17 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
||||||
: frameworkConfiguration.fetch?.fn;
|
: frameworkConfiguration.fetch?.fn;
|
||||||
stylesheetElement = convertLinkAsStyle(
|
stylesheetElement = convertLinkAsStyle(
|
||||||
element,
|
element,
|
||||||
(styleElement) => css.process(mountDOM, styleElement, appName),
|
(styleElement) => css.process(appWrapper, styleElement, appName),
|
||||||
fetch,
|
fetch,
|
||||||
);
|
);
|
||||||
dynamicLinkAttachedInlineStyleMap.set(element, stylesheetElement);
|
dynamicLinkAttachedInlineStyleMap.set(element, stylesheetElement);
|
||||||
} else {
|
} 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);
|
dynamicStyleSheetElements.push(stylesheetElement);
|
||||||
const referenceNode = mountDOM.contains(refChild) ? refChild : null;
|
const referenceNode = mountDOM.contains(refChild) ? refChild : null;
|
||||||
return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode);
|
return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode);
|
||||||
|
|
@ -301,7 +301,8 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
||||||
|
|
||||||
function getNewRemoveChild(
|
function getNewRemoveChild(
|
||||||
headOrBodyRemoveChild: typeof HTMLElement.prototype.removeChild,
|
headOrBodyRemoveChild: typeof HTMLElement.prototype.removeChild,
|
||||||
appWrapperGetterGetter: (element: HTMLElement) => ContainerConfig['appWrapperGetter'],
|
containerConfigGetter: (element: HTMLElement) => ContainerConfig,
|
||||||
|
target: DynamicDomMutationTarget,
|
||||||
) {
|
) {
|
||||||
return function removeChild<T extends Node>(this: HTMLHeadElement | HTMLBodyElement, child: T) {
|
return function removeChild<T extends Node>(this: HTMLHeadElement | HTMLBodyElement, child: T) {
|
||||||
const { tagName } = child as any;
|
const { tagName } = child as any;
|
||||||
|
|
@ -309,14 +310,19 @@ function getNewRemoveChild(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let attachedElement: Node;
|
let attachedElement: Node;
|
||||||
|
const { appWrapperGetter, dynamicStyleSheetElements } = containerConfigGetter(child as any);
|
||||||
|
|
||||||
switch (tagName) {
|
switch (tagName) {
|
||||||
|
case STYLE_TAG_NAME:
|
||||||
case LINK_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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SCRIPT_TAG_NAME: {
|
case SCRIPT_TAG_NAME: {
|
||||||
attachedElement = (dynamicScriptAttachedCommentMap.get(child as any) as Node) || child;
|
attachedElement = dynamicScriptAttachedCommentMap.get(child as any) || child;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,11 +331,11 @@ function getNewRemoveChild(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// container may had been removed while app unmounting if the removeChild action was async
|
const appWrapper = appWrapperGetter();
|
||||||
const appWrapperGetter = appWrapperGetterGetter(child as any);
|
const container = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper;
|
||||||
const container = appWrapperGetter();
|
// container might have been removed while app unmounting if the removeChild action was async
|
||||||
if (container.contains(attachedElement)) {
|
if (container.contains(attachedElement)) {
|
||||||
return rawRemoveChild.call(container, attachedElement) as T;
|
return rawRemoveChild.call(attachedElement.parentNode, attachedElement) as T;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
@ -375,14 +381,8 @@ export function patchHTMLDynamicAppendPrototypeFunctions(
|
||||||
HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild &&
|
HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild &&
|
||||||
HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild
|
HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild
|
||||||
) {
|
) {
|
||||||
HTMLHeadElement.prototype.removeChild = getNewRemoveChild(
|
HTMLHeadElement.prototype.removeChild = getNewRemoveChild(rawHeadRemoveChild, containerConfigGetter, 'head');
|
||||||
rawHeadRemoveChild,
|
HTMLBodyElement.prototype.removeChild = getNewRemoveChild(rawBodyRemoveChild, containerConfigGetter, 'body');
|
||||||
(element) => containerConfigGetter(element).appWrapperGetter,
|
|
||||||
);
|
|
||||||
HTMLBodyElement.prototype.removeChild = getNewRemoveChild(
|
|
||||||
rawBodyRemoveChild,
|
|
||||||
(element) => containerConfigGetter(element).appWrapperGetter,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return function unpatch() {
|
return function unpatch() {
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ export function patchStrictSandbox(
|
||||||
if (mounting) mountingPatchCount--;
|
if (mounting) mountingPatchCount--;
|
||||||
|
|
||||||
const allMicroAppUnmounted = mountingPatchCount === 0 && bootstrappingPatchCount === 0;
|
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) {
|
if (allMicroAppUnmounted) {
|
||||||
unpatchDynamicAppendPrototypeFunctions();
|
unpatchDynamicAppendPrototypeFunctions();
|
||||||
unpatchDocumentCreate();
|
unpatchDocumentCreate();
|
||||||
|
|
|
||||||
16
src/utils.ts
16
src/utils.ts
|
|
@ -107,10 +107,18 @@ export const qiankunHeadTagName = 'qiankun-head';
|
||||||
|
|
||||||
export function getDefaultTplWrapper(name: string) {
|
export function getDefaultTplWrapper(name: string) {
|
||||||
return (tpl: string) => {
|
return (tpl: string) => {
|
||||||
// We need to mock a head placeholder as native head element will be erased by browser in micro app
|
let tplWithSimulatedHead: string;
|
||||||
const tplWithSimulatedHead = tpl
|
|
||||||
.replace('<head>', `<${qiankunHeadTagName}>`)
|
if (tpl.indexOf('<head>') !== -1) {
|
||||||
.replace('</head>', `</${qiankunHeadTagName}>`);
|
// We need to mock a head placeholder as native head element will be erased by browser in micro app
|
||||||
|
tplWithSimulatedHead = tpl
|
||||||
|
.replace('<head>', `<${qiankunHeadTagName}>`)
|
||||||
|
.replace('</head>', `</${qiankunHeadTagName}>`);
|
||||||
|
} else {
|
||||||
|
// Some template might not be a standard html document, thus we need to add a simulated head tag for them
|
||||||
|
tplWithSimulatedHead = `<${qiankunHeadTagName}></${qiankunHeadTagName}>${tpl}`;
|
||||||
|
}
|
||||||
|
|
||||||
return `<div id="${getWrapperId(
|
return `<div id="${getWrapperId(
|
||||||
name,
|
name,
|
||||||
)}" data-name="${name}" data-version="${version}">${tplWithSimulatedHead}</div>`;
|
)}" data-name="${name}" data-version="${version}">${tplWithSimulatedHead}</div>`;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export { version } from '../package.json';
|
export const version = '2.7.1';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user