Skip to content

Commit

Permalink
fix: 适配子应用处理script标签 (Tencent#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xusenbiao authored Mar 23, 2023
1 parent 4dbb179 commit f7e83b8
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/wujie-core/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export const windowRegWhiteList = [
// 子应用的Document.prototype已经被改写了
export const rawElementAppendChild = HTMLElement.prototype.appendChild;
export const rawElementRemoveChild = HTMLElement.prototype.removeChild;
export const rawElementContains = HTMLElement.prototype.contains;
export const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore;
export const rawBodyInsertBefore = HTMLBodyElement.prototype.insertBefore;
export const rawAddEventListener = Node.prototype.addEventListener;
Expand Down
1 change: 1 addition & 0 deletions packages/wujie-core/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export const WUJIE_TIPS_NOT_SUPPORTED = "当前浏览器不支持无界,子应
export const WUJIE_TIPS_SCRIPT_ERROR_REQUESTED = "脚本请求出现错误";
export const WUJIE_TIPS_CSS_ERROR_REQUESTED = "样式请求出现错误";
export const WUJIE_TIPS_REPEAT_RENDER = "无界组件短时间重复渲染了两次,可能存在性能问题请检查代码";
export const WUJIE_TIPS_NO_SCRIPT = "目标Script尚未准备好或已经被移除";
69 changes: 67 additions & 2 deletions packages/wujie-core/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@ import { getExternalStyleSheets, getExternalScripts } from "./entry";
import {
getWujieById,
rawAppendChild,
rawElementContains,
rawElementRemoveChild,
rawHeadInsertBefore,
rawBodyInsertBefore,
rawDocumentQuerySelector,
rawAddEventListener,
rawRemoveEventListener,
} from "./common";
import { isFunction, isHijackingTag, requestIdleCallback, error, warn, nextTick, getCurUrl, execHooks } from "./utils";
import {
isFunction,
isHijackingTag,
requestIdleCallback,
error,
warn,
nextTick,
getCurUrl,
execHooks,
isScriptElement,
setTagToScript,
getTagFromScript,
} from "./utils";
import { insertScriptToIframe, patchElementEffect } from "./iframe";
import Wujie from "./sandbox";
import { getPatchStyleElements } from "./shadow";
import { getCssLoader, getEffectLoaders, isMatchUrl } from "./plugin";
import { WUJIE_DATA_ID, WUJIE_DATA_FLAG, WUJIE_TIPS_REPEAT_RENDER } from "./constant";
import { WUJIE_DATA_ID, WUJIE_DATA_FLAG, WUJIE_TIPS_REPEAT_RENDER, WUJIE_TIPS_NO_SCRIPT } from "./constant";
import { ScriptObject } from "./template";

function patchCustomEvent(
Expand Down Expand Up @@ -234,6 +248,7 @@ function rewriteAppendOrInsertChild(opts: {
return res;
}
case "SCRIPT": {
setTagToScript(element);
const { src, text, type, crossOrigin } = element as HTMLScriptElement;
// 排除js
if (src && !isMatchUrl(src, getEffectLoaders("jsExcludes", plugins))) {
Expand Down Expand Up @@ -316,6 +331,44 @@ function rewriteAppendOrInsertChild(opts: {
};
}

function findScriptElementFromIframe(rawElement: HTMLScriptElement, wujieId: string) {
const wujieTag = getTagFromScript(rawElement);
const sandbox = getWujieById(wujieId);
const { iframe } = sandbox;
const targetScript = iframe.contentWindow.__WUJIE_RAW_DOCUMENT_HEAD__.querySelector(`script[wujie='${wujieTag}']`);
if (targetScript === null) {
warn(WUJIE_TIPS_NO_SCRIPT, `<script wujie='${wujieTag}'/>`);
}
return { targetScript, iframe };
}

function rewriteContains(opts: { rawElementContains: (other: Node | null) => boolean; wujieId: string }) {
return function contains(other: Node | null) {
const element = other as HTMLElement;
const { rawElementContains, wujieId } = opts;
if (element && isScriptElement(element)) {
const { targetScript } = findScriptElementFromIframe(element as HTMLScriptElement, wujieId);
return targetScript !== null;
}
return rawElementContains(element);
};
}

function rewriteRemoveChild(opts: { rawElementRemoveChild: <T extends Node>(child: T) => T; wujieId: string }) {
return function removeChild(child: Node) {
const element = child as HTMLElement;
const { rawElementRemoveChild, wujieId } = opts;
if (element && isScriptElement(element)) {
const { targetScript, iframe } = findScriptElementFromIframe(element as HTMLScriptElement, wujieId);
if (targetScript !== null) {
return iframe.contentWindow.__WUJIE_RAW_DOCUMENT_HEAD__.removeChild(targetScript);
}
return null;
}
return rawElementRemoveChild(element);
};
}

/**
* 记录head和body的事件,等重新渲染复用head和body时需要清空事件
*/
Expand Down Expand Up @@ -376,6 +429,18 @@ export function patchRenderEffect(render: ShadowRoot | Document, id: string, deg
rawDOMAppendOrInsertBefore: rawHeadInsertBefore as any,
wujieId: id,
}) as typeof rawHeadInsertBefore;
render.head.removeChild = rewriteRemoveChild({
rawElementRemoveChild: rawElementRemoveChild.bind(render.head),
wujieId: id,
}) as typeof rawElementRemoveChild;
render.head.contains = rewriteContains({
rawElementContains: rawElementContains.bind(render.head),
wujieId: id,
}) as typeof rawElementContains;
render.contains = rewriteContains({
rawElementContains: rawElementContains.bind(render),
wujieId: id,
}) as typeof rawElementContains;
render.body.appendChild = rewriteAppendOrInsertChild({
rawDOMAppendOrInsertBefore: rawAppendChild,
wujieId: id,
Expand Down
10 changes: 9 additions & 1 deletion packages/wujie-core/src/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
getCurUrl,
getAbsolutePath,
setAttrsToElement,
setTagToScript,
getTagFromScript,
} from "./utils";
import {
documentProxyProperties,
Expand Down Expand Up @@ -49,6 +51,9 @@ declare global {
// iframe内原生的createTextNode
__WUJIE_RAW_DOCUMENT_CREATE_TEXT_NODE__: typeof Document.prototype.createTextNode;

// iframe内原生的head
__WUJIE_RAW_DOCUMENT_HEAD__: typeof Document.prototype.head;

// 原生的querySelector
__WUJIE_RAW_DOCUMENT_QUERY_SELECTOR_ALL__: typeof Document.prototype.querySelectorAll;
// 原生的window对象
Expand Down Expand Up @@ -604,7 +609,7 @@ function initIframeDom(iframeWindow: Window, wujie: WuJie, mainHostPath: string,
iframeDocument.documentElement
? iframeDocument.replaceChild(newDocumentElement, iframeDocument.documentElement)
: iframeDocument.appendChild(newDocumentElement);

iframeWindow.__WUJIE_RAW_DOCUMENT_HEAD__ = iframeDocument.head;
initBase(iframeWindow, wujie.url);
patchIframeHistory(iframeWindow, appHostPath, mainHostPath);
patchIframeEvents(iframeWindow);
Expand Down Expand Up @@ -729,6 +734,9 @@ export function insertScriptToIframe(
error(WUJIE_TIPS_SCRIPT_ERROR_REQUESTED, scriptResult);
return !async && container.appendChild(nextScriptElement);
}
if (rawElement) {
setTagToScript(scriptElement, getTagFromScript(rawElement));
}
container.appendChild(scriptElement);

// 调用回调
Expand Down
18 changes: 18 additions & 0 deletions packages/wujie-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,24 @@ export function execHooks(plugins: Array<plugin>, hookName: string, ...args: Arr
}
}

export function isScriptElement(element: HTMLElement): boolean {
return element.tagName?.toUpperCase() === "SCRIPT";
}

export function setTagToScript(element: HTMLScriptElement, tag?: string): void {
if (isScriptElement(element)) {
const scriptTag = tag || `${new Date().valueOf()}`;
element.setAttribute("wujie", scriptTag);
}
}

export function getTagFromScript(element: HTMLScriptElement): string | null {
if (isScriptElement(element)) {
return element.getAttribute("wujie");
}
return null;
}

// 合并缓存
export function mergeOptions(options: cacheOptions, cacheOptions: cacheOptions) {
return {
Expand Down

0 comments on commit f7e83b8

Please sign in to comment.