diff --git a/src/__tests__/globalState.test.ts b/src/__tests__/globalState.test.ts index 565daa4..3b5658e 100644 --- a/src/__tests__/globalState.test.ts +++ b/src/__tests__/globalState.test.ts @@ -3,7 +3,7 @@ * @since 2020-04-10 */ -import { initGlobalState, getMicroAppStateActions } from '../globalState'; +import { getMicroAppStateActions, initGlobalState } from '../globalState'; const master = initGlobalState({ user: 'qiankun' }); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index bf08070..2fb1683 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -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'; diff --git a/src/addons/index.ts b/src/addons/index.ts index e5aa126..ebd72c8 100644 --- a/src/addons/index.ts +++ b/src/addons/index.ts @@ -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(global: Window, publicPath: string): FrameworkLifeCycles { return mergeWith({}, getEngineFlagAddon(global), getRuntimePublicPathAddOn(global, publicPath), (v1, v2) => diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 9f983a6..38a71b9 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -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'; /** * 生成应用运行时沙箱 diff --git a/src/sandbox/patchers/__tests__/css.test.ts b/src/sandbox/patchers/__tests__/css.test.ts index 1fdb5a9..e6b7855 100644 --- a/src/sandbox/patchers/__tests__/css.test.ts +++ b/src/sandbox/patchers/__tests__/css.test.ts @@ -3,8 +3,8 @@ * @since 2020-04-19 */ -import { ScopedCSS } from '../css'; import { sleep } from '../../../utils'; +import { ScopedCSS } from '../css'; let CSSProcessor: ScopedCSS; beforeAll(() => { diff --git a/src/sandbox/patchers/dynamicAppend/__tests__/common.test.ts b/src/sandbox/patchers/dynamicAppend/__tests__/common.test.ts index d058145..b7c2c9b 100644 --- a/src/sandbox/patchers/dynamicAppend/__tests__/common.test.ts +++ b/src/sandbox/patchers/dynamicAppend/__tests__/common.test.ts @@ -1,4 +1,4 @@ -import { rebuildCSSRules, recordStyledComponentsCSSRules, getStyledElementCSSRules } from '../common'; +import { getStyledElementCSSRules, rebuildCSSRules, recordStyledComponentsCSSRules } from '../common'; jest.mock('import-html-entry', () => ({ execScripts: jest.fn(), diff --git a/src/sandbox/patchers/dynamicAppend/common.ts b/src/sandbox/patchers/dynamicAppend/common.ts index edf9a36..3e62daf 100644 --- a/src/sandbox/patchers/dynamicAppend/common.ts +++ b/src/sandbox/patchers/dynamicAppend/common.ts @@ -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: (newChild: T, refChild?: Node | null) => T; isInvokedByMicroApp: (element: HTMLElement) => boolean; containerConfigGetter: (element: HTMLElement) => ContainerConfig; + target: DynamicAppendTarget; }) { return function appendChildOrInsertBefore( 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 @@ -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; } diff --git a/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts b/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts index 16cbd38..c301b4e 100644 --- a/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts +++ b/src/sandbox/patchers/dynamicAppend/forStrictSandbox.ts @@ -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; } diff --git a/src/sandbox/proxySandbox.ts b/src/sandbox/proxySandbox.ts index e37a215..db40308 100644 --- a/src/sandbox/proxySandbox.ts +++ b/src/sandbox/proxySandbox.ts @@ -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'; diff --git a/src/utils.ts b/src/utils.ts index dabbe5b..18cc1d1 100644 --- a/src/utils.ts +++ b/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(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(); + 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(); + 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) => `
${tpl}
`; + 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('', ``); + return `
${tplWithSimulatedHead}
`; + }; } export function getWrapperId(name: string) {