🐛 simulate micro app head element for dynamic appending styles (#2097)
This commit is contained in:
parent
7fa3eb28f3
commit
526a3f610c
|
|
@ -3,7 +3,7 @@
|
|||
* @since 2020-04-10
|
||||
*/
|
||||
|
||||
import { initGlobalState, getMicroAppStateActions } from '../globalState';
|
||||
import { getMicroAppStateActions, initGlobalState } from '../globalState';
|
||||
|
||||
const master = initGlobalState({ user: 'qiankun' });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { version } from '../../package.json';
|
||||
import {
|
||||
Deferred,
|
||||
genAppInstanceIdByName,
|
||||
|
|
@ -8,7 +9,6 @@ import {
|
|||
sleep,
|
||||
validateExportLifecycle,
|
||||
} from '../utils';
|
||||
import { version } from '../../package.json';
|
||||
|
||||
test('should wrap the id [1]', () => {
|
||||
const id = 'REACT16';
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
import { concat, mergeWith } from 'lodash';
|
||||
import type { FrameworkLifeCycles, ObjectType } from '../interfaces';
|
||||
|
||||
import getRuntimePublicPathAddOn from './runtimePublicPath';
|
||||
import getEngineFlagAddon from './engineFlag';
|
||||
import getRuntimePublicPathAddOn from './runtimePublicPath';
|
||||
|
||||
export default function getAddOns<T extends ObjectType>(global: Window, publicPath: string): FrameworkLifeCycles<T> {
|
||||
return mergeWith({}, getEngineFlagAddon(global), getRuntimePublicPathAddOn(global, publicPath), (v1, v2) =>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { patchAtBootstrapping, patchAtMounting } from './patchers';
|
|||
import ProxySandbox from './proxySandbox';
|
||||
import SnapshotSandbox from './snapshotSandbox';
|
||||
|
||||
export { css } from './patchers';
|
||||
export { getCurrentRunningApp } from './common';
|
||||
export { css } from './patchers';
|
||||
|
||||
/**
|
||||
* 生成应用运行时沙箱
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* @since 2020-04-19
|
||||
*/
|
||||
|
||||
import { ScopedCSS } from '../css';
|
||||
import { sleep } from '../../../utils';
|
||||
import { ScopedCSS } from '../css';
|
||||
|
||||
let CSSProcessor: ScopedCSS;
|
||||
beforeAll(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { rebuildCSSRules, recordStyledComponentsCSSRules, getStyledElementCSSRules } from '../common';
|
||||
import { getStyledElementCSSRules, rebuildCSSRules, recordStyledComponentsCSSRules } from '../common';
|
||||
|
||||
jest.mock('import-html-entry', () => ({
|
||||
execScripts: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import { execScripts } from 'import-html-entry';
|
||||
import { isFunction } from 'lodash';
|
||||
import { frameworkConfiguration } from '../../../apis';
|
||||
|
||||
import { qiankunHeadTagName } from '../../../utils';
|
||||
import * as css from '../css';
|
||||
|
||||
export const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
|
||||
|
|
@ -19,6 +19,25 @@ const SCRIPT_TAG_NAME = 'SCRIPT';
|
|||
const LINK_TAG_NAME = 'LINK';
|
||||
const STYLE_TAG_NAME = 'STYLE';
|
||||
|
||||
export const styleElementTargetSymbol = Symbol('target');
|
||||
|
||||
type DynamicAppendTarget = 'head' | 'body';
|
||||
|
||||
declare global {
|
||||
interface HTMLLinkElement {
|
||||
[styleElementTargetSymbol]: DynamicAppendTarget;
|
||||
}
|
||||
|
||||
interface HTMLStyleElement {
|
||||
[styleElementTargetSymbol]: DynamicAppendTarget;
|
||||
}
|
||||
}
|
||||
|
||||
export const getAppWrapperHeadElement = (appWrapper: Element | ShadowRoot) => {
|
||||
const rootElement = 'host' in appWrapper ? appWrapper.host : appWrapper;
|
||||
return rootElement.getElementsByTagName(qiankunHeadTagName)[0];
|
||||
};
|
||||
|
||||
export function isExecutableScriptType(script: HTMLScriptElement) {
|
||||
return (
|
||||
!script.type ||
|
||||
|
|
@ -149,6 +168,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
|||
rawDOMAppendOrInsertBefore: <T extends Node>(newChild: T, refChild?: Node | null) => T;
|
||||
isInvokedByMicroApp: (element: HTMLElement) => boolean;
|
||||
containerConfigGetter: (element: HTMLElement) => ContainerConfig;
|
||||
target: DynamicAppendTarget;
|
||||
}) {
|
||||
return function appendChildOrInsertBefore<T extends Node>(
|
||||
this: HTMLHeadElement | HTMLBodyElement,
|
||||
|
|
@ -156,7 +176,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
|||
refChild: Node | null = null,
|
||||
) {
|
||||
let element = newChild as any;
|
||||
const { rawDOMAppendOrInsertBefore, isInvokedByMicroApp, containerConfigGetter } = opts;
|
||||
const { rawDOMAppendOrInsertBefore, isInvokedByMicroApp, containerConfigGetter, target = 'body' } = opts;
|
||||
if (!isHijackingTag(element.tagName) || !isInvokedByMicroApp(element)) {
|
||||
return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
|
||||
}
|
||||
|
|
@ -182,7 +202,14 @@ function getOverwrittenAppendChildOrInsertBefore(opts: {
|
|||
return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T;
|
||||
}
|
||||
|
||||
const mountDOM = appWrapperGetter();
|
||||
Object.defineProperty(stylesheetElement, styleElementTargetSymbol, {
|
||||
value: target,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const appWrapper = appWrapperGetter();
|
||||
const mountDOM = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper;
|
||||
|
||||
if (scopedCSS) {
|
||||
// exclude link elements like <link rel="icon" href="favicon.ico">
|
||||
|
|
@ -326,17 +353,20 @@ export function patchHTMLDynamicAppendPrototypeFunctions(
|
|||
rawDOMAppendOrInsertBefore: rawHeadAppendChild,
|
||||
containerConfigGetter,
|
||||
isInvokedByMicroApp,
|
||||
target: 'head',
|
||||
}) as typeof rawHeadAppendChild;
|
||||
HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
|
||||
rawDOMAppendOrInsertBefore: rawBodyAppendChild,
|
||||
containerConfigGetter,
|
||||
isInvokedByMicroApp,
|
||||
target: 'body',
|
||||
}) as typeof rawBodyAppendChild;
|
||||
|
||||
HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({
|
||||
rawDOMAppendOrInsertBefore: rawHeadInsertBefore as any,
|
||||
containerConfigGetter,
|
||||
isInvokedByMicroApp,
|
||||
target: 'head',
|
||||
}) as typeof rawHeadInsertBefore;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import { nativeGlobal } from '../../../utils';
|
|||
import { getCurrentRunningApp } from '../../common';
|
||||
import type { ContainerConfig } from './common';
|
||||
import {
|
||||
getAppWrapperHeadElement,
|
||||
isHijackingTag,
|
||||
patchHTMLDynamicAppendPrototypeFunctions,
|
||||
rawHeadAppendChild,
|
||||
rebuildCSSRules,
|
||||
recordStyledComponentsCSSRules,
|
||||
styleElementTargetSymbol,
|
||||
} from './common';
|
||||
|
||||
declare global {
|
||||
|
|
@ -130,7 +132,9 @@ export function patchStrictSandbox(
|
|||
rebuildCSSRules(dynamicStyleSheetElements, (stylesheetElement) => {
|
||||
const appWrapper = appWrapperGetter();
|
||||
if (!appWrapper.contains(stylesheetElement)) {
|
||||
rawHeadAppendChild.call(appWrapper, stylesheetElement);
|
||||
const mountDom =
|
||||
stylesheetElement[styleElementTargetSymbol] === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper;
|
||||
rawHeadAppendChild.call(mountDom, stylesheetElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import type { SandBox } from '../interfaces';
|
||||
import { SandBoxType } from '../interfaces';
|
||||
import { nativeGlobal, nextTask } from '../utils';
|
||||
import { getTargetValue, setCurrentRunningApp, getCurrentRunningApp } from './common';
|
||||
import { getCurrentRunningApp, getTargetValue, setCurrentRunningApp } from './common';
|
||||
|
||||
type SymbolTarget = 'target' | 'globalContext';
|
||||
|
||||
|
|
|
|||
20
src/utils.ts
20
src/utils.ts
|
|
@ -3,10 +3,9 @@
|
|||
* @since 2019-05-15
|
||||
*/
|
||||
|
||||
import { isFunction, snakeCase, once } from 'lodash';
|
||||
import { version } from './version';
|
||||
|
||||
import { isFunction, once, snakeCase } from 'lodash';
|
||||
import type { FrameworkConfiguration } from './interfaces';
|
||||
import { version } from './version';
|
||||
|
||||
export function toArray<T>(array: T | T[]): T[] {
|
||||
return Array.isArray(array) ? array : [array];
|
||||
|
|
@ -21,6 +20,7 @@ const nextTick: (cb: () => void) => void =
|
|||
typeof window.Zone === 'function' ? setTimeout : (cb) => Promise.resolve().then(cb);
|
||||
|
||||
let globalTaskPending = false;
|
||||
|
||||
/**
|
||||
* Run a callback before next task executing, and the invocation is idempotent in every singular task
|
||||
* That means even we called nextTask multi times in one task, only the first callback will be pushed to nextTick to be invoked.
|
||||
|
|
@ -37,6 +37,7 @@ export function nextTask(cb: () => void): void {
|
|||
}
|
||||
|
||||
const fnRegexCheckCacheMap = new WeakMap<any | FunctionConstructor, boolean>();
|
||||
|
||||
export function isConstructable(fn: () => any | FunctionConstructor) {
|
||||
// prototype methods might be changed while code running, so we need check it every time
|
||||
const hasPrototypeMethods =
|
||||
|
|
@ -88,6 +89,7 @@ export const isCallable = (fn: any) => {
|
|||
};
|
||||
|
||||
const boundedMap = new WeakMap<CallableFunction, boolean>();
|
||||
|
||||
export function isBoundedFunction(fn: CallableFunction) {
|
||||
if (boundedMap.has(fn)) {
|
||||
return boundedMap.get(fn);
|
||||
|
|
@ -101,8 +103,18 @@ export function isBoundedFunction(fn: CallableFunction) {
|
|||
return bounded;
|
||||
}
|
||||
|
||||
export const qiankunHeadTagName = 'qiankun-head';
|
||||
|
||||
export function getDefaultTplWrapper(name: string) {
|
||||
return (tpl: string) => `<div id="${getWrapperId(name)}" data-name="${name}" data-version="${version}">${tpl}</div>`;
|
||||
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('<head>', `<${qiankunHeadTagName}>`)
|
||||
.replace('</head>', `</${qiankunHeadTagName}>`);
|
||||
return `<div id="${getWrapperId(
|
||||
name,
|
||||
)}" data-name="${name}" data-version="${version}">${tplWithSimulatedHead}</div>`;
|
||||
};
|
||||
}
|
||||
|
||||
export function getWrapperId(name: string) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user