From 80ea7340845e7925cb2e8a30f04a7288442c1ffc Mon Sep 17 00:00:00 2001 From: Kuitos Date: Tue, 20 Oct 2020 14:09:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20manual=20invoke=20dynamic=20link?= =?UTF-8?q?=20load=20event=20(#1007)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sandbox/patchers/dynamicAppend/common.ts | 92 ++++++++++++-------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/src/sandbox/patchers/dynamicAppend/common.ts b/src/sandbox/patchers/dynamicAppend/common.ts index 7491c36..c07f451 100644 --- a/src/sandbox/patchers/dynamicAppend/common.ts +++ b/src/sandbox/patchers/dynamicAppend/common.ts @@ -40,6 +40,46 @@ export function isStyledComponentsLike(element: HTMLStyleElement) { ); } +function patchCustomEvent( + e: CustomEvent, + elementGetter: () => HTMLScriptElement | HTMLLinkElement | null, +): CustomEvent { + Object.defineProperties(e, { + srcElement: { + get: elementGetter, + }, + target: { + get: elementGetter, + }, + }); + + return e; +} + +function manualInvokeElementOnLoad(element: HTMLLinkElement | HTMLScriptElement) { + // we need to invoke the onload event manually to notify the event listener that the script was completed + // here are the two typical ways of dynamic script loading + // 1. element.onload callback way, which webpack and loadjs used, see https://github.com/muicss/loadjs/blob/master/src/loadjs.js#L138 + // 2. addEventListener way, which toast-loader used, see https://github.com/pyrsmk/toast/blob/master/src/Toast.ts#L64 + const loadEvent = new CustomEvent('load'); + const patchedEvent = patchCustomEvent(loadEvent, () => element); + if (isFunction(element.onload)) { + element.onload(patchedEvent); + } else { + element.dispatchEvent(patchedEvent); + } +} + +function manualInvokeElementOnError(element: HTMLLinkElement | HTMLScriptElement) { + const errorEvent = new CustomEvent('error'); + const patchedEvent = patchCustomEvent(errorEvent, () => element); + if (isFunction(element.onerror)) { + element.onerror(patchedEvent); + } else { + element.dispatchEvent(patchedEvent); + } +} + function convertLinkAsStyle( element: HTMLLinkElement, postProcess: (styleElement: HTMLStyleElement) => void, @@ -55,7 +95,9 @@ function convertLinkAsStyle( .then((styleContext: string) => { styleElement.appendChild(document.createTextNode(styleContext)); postProcess(styleElement); - }); + manualInvokeElementOnLoad(element); + }) + .catch(() => manualInvokeElementOnError(element)); return styleElement; } @@ -84,18 +126,6 @@ export function getStyledElementCSSRules(styledElement: HTMLStyleElement): CSSRu return styledComponentCSSRulesMap.get(styledElement); } -function patchCustomEvent(e: CustomEvent, elementGetter: () => HTMLScriptElement | null): CustomEvent { - Object.defineProperties(e, { - srcElement: { - get: elementGetter, - }, - target: { - get: elementGetter, - }, - }); - return e; -} - export type ContainerConfig = { appName: string; proxy: WindowProxy; @@ -118,7 +148,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { ) { let element = newChild as any; const { rawDOMAppendOrInsertBefore, isInvokedByMicroApp, containerConfigGetter } = opts; - if (!isInvokedByMicroApp(element)) { + if (!isHijackingTag(element.tagName) || !isInvokedByMicroApp(element)) { return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T; } @@ -146,7 +176,12 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { const mountDOM = appWrapperGetter(); if (scopedCSS) { - if (element.tagName === LINK_TAG_NAME) { + // exclude link elements like + const linkElementUsingStylesheet = + element.tagName?.toUpperCase() === LINK_TAG_NAME && + (element as HTMLLinkElement).rel === 'stylesheet' && + (element as HTMLLinkElement).href; + if (linkElementUsingStylesheet) { const { fetch } = frameworkConfiguration; stylesheetElement = convertLinkAsStyle( element, @@ -189,27 +224,11 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { }); }, success: () => { - // we need to invoke the onload event manually to notify the event listener that the script was completed - // here are the two typical ways of dynamic script loading - // 1. element.onload callback way, which webpack and loadjs used, see https://github.com/muicss/loadjs/blob/master/src/loadjs.js#L138 - // 2. addEventListener way, which toast-loader used, see https://github.com/pyrsmk/toast/blob/master/src/Toast.ts#L64 - const loadEvent = new CustomEvent('load'); - if (isFunction(element.onload)) { - element.onload(patchCustomEvent(loadEvent, () => element)); - } else { - element.dispatchEvent(loadEvent); - } - + manualInvokeElementOnLoad(element); element = null; }, error: () => { - const errorEvent = new CustomEvent('error'); - if (isFunction(element.onerror)) { - element.onerror(patchCustomEvent(errorEvent, () => element)); - } else { - element.dispatchEvent(errorEvent); - } - + manualInvokeElementOnError(element); element = null; }, }); @@ -219,11 +238,8 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { return rawDOMAppendOrInsertBefore.call(mountDOM, dynamicScriptCommentElement, referenceNode); } - execScripts(null, [``], proxy, { - strictGlobal, - success: element.onload, - error: element.onerror, - }); + // inline script never trigger the onload and onerror event + execScripts(null, [``], proxy, { strictGlobal }); const dynamicInlineScriptCommentElement = document.createComment('dynamic inline script replaced by qiankun'); dynamicScriptAttachedCommentMap.set(element, dynamicInlineScriptCommentElement); return rawDOMAppendOrInsertBefore.call(mountDOM, dynamicInlineScriptCommentElement, referenceNode);