416 lines
18 KiB
Java
416 lines
18 KiB
Java
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.getAppWrapperHeadElement = void 0;
|
|
exports.getStyledElementCSSRules = getStyledElementCSSRules;
|
|
exports.isExecutableScriptType = isExecutableScriptType;
|
|
exports.isHijackingTag = isHijackingTag;
|
|
exports.isStyledComponentsLike = isStyledComponentsLike;
|
|
exports.patchHTMLDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions;
|
|
exports.rawHeadAppendChild = void 0;
|
|
exports.rebuildCSSRules = rebuildCSSRules;
|
|
exports.recordStyledComponentsCSSRules = recordStyledComponentsCSSRules;
|
|
exports.styleElementTargetSymbol = void 0;
|
|
|
|
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
|
|
|
|
var _importHtmlEntry = require("import-html-entry");
|
|
|
|
var _apis = require("../../../apis");
|
|
|
|
var _utils = require("../../../utils");
|
|
|
|
var css = _interopRequireWildcard(require("../css"));
|
|
|
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
|
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
|
|
/**
|
|
* @author Kuitos
|
|
* @since 2019-10-21
|
|
*/
|
|
var rawHeadAppendChild = HTMLHeadElement.prototype.appendChild;
|
|
exports.rawHeadAppendChild = rawHeadAppendChild;
|
|
var rawHeadRemoveChild = HTMLHeadElement.prototype.removeChild;
|
|
var rawBodyAppendChild = HTMLBodyElement.prototype.appendChild;
|
|
var rawBodyRemoveChild = HTMLBodyElement.prototype.removeChild;
|
|
var rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore;
|
|
var rawRemoveChild = HTMLElement.prototype.removeChild;
|
|
var SCRIPT_TAG_NAME = 'SCRIPT';
|
|
var LINK_TAG_NAME = 'LINK';
|
|
var STYLE_TAG_NAME = 'STYLE';
|
|
var styleElementTargetSymbol = Symbol('target');
|
|
exports.styleElementTargetSymbol = styleElementTargetSymbol;
|
|
|
|
var getAppWrapperHeadElement = function getAppWrapperHeadElement(appWrapper) {
|
|
return appWrapper.querySelector(_utils.qiankunHeadTagName);
|
|
};
|
|
|
|
exports.getAppWrapperHeadElement = getAppWrapperHeadElement;
|
|
|
|
function isExecutableScriptType(script) {
|
|
return !script.type || ['text/javascript', 'module', 'application/javascript', 'text/ecmascript', 'application/ecmascript'].indexOf(script.type) !== -1;
|
|
}
|
|
|
|
function isHijackingTag(tagName) {
|
|
return (tagName === null || tagName === void 0 ? void 0 : tagName.toUpperCase()) === LINK_TAG_NAME || (tagName === null || tagName === void 0 ? void 0 : tagName.toUpperCase()) === STYLE_TAG_NAME || (tagName === null || tagName === void 0 ? void 0 : tagName.toUpperCase()) === SCRIPT_TAG_NAME;
|
|
}
|
|
/**
|
|
* Check if a style element is a styled-component liked.
|
|
* A styled-components liked element is which not have textContext but keep the rules in its styleSheet.cssRules.
|
|
* Such as the style element generated by styled-components and emotion.
|
|
* @param element
|
|
*/
|
|
|
|
|
|
function isStyledComponentsLike(element) {
|
|
var _a, _b;
|
|
|
|
return !element.textContent && (((_a = element.sheet) === null || _a === void 0 ? void 0 : _a.cssRules.length) || ((_b = getStyledElementCSSRules(element)) === null || _b === void 0 ? void 0 : _b.length));
|
|
}
|
|
|
|
function patchCustomEvent(e, elementGetter) {
|
|
Object.defineProperties(e, {
|
|
srcElement: {
|
|
get: elementGetter
|
|
},
|
|
target: {
|
|
get: elementGetter
|
|
}
|
|
});
|
|
return e;
|
|
}
|
|
|
|
function manualInvokeElementOnLoad(element) {
|
|
// 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
|
|
var loadEvent = new CustomEvent('load');
|
|
var patchedEvent = patchCustomEvent(loadEvent, function () {
|
|
return element;
|
|
});
|
|
|
|
if ((0, _isFunction2.default)(element.onload)) {
|
|
element.onload(patchedEvent);
|
|
} else {
|
|
element.dispatchEvent(patchedEvent);
|
|
}
|
|
}
|
|
|
|
function manualInvokeElementOnError(element) {
|
|
var errorEvent = new CustomEvent('error');
|
|
var patchedEvent = patchCustomEvent(errorEvent, function () {
|
|
return element;
|
|
});
|
|
|
|
if ((0, _isFunction2.default)(element.onerror)) {
|
|
element.onerror(patchedEvent);
|
|
} else {
|
|
element.dispatchEvent(patchedEvent);
|
|
}
|
|
}
|
|
|
|
function convertLinkAsStyle(element, postProcess) {
|
|
var fetchFn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : fetch;
|
|
var styleElement = document.createElement('style');
|
|
var href = element.href; // add source link element href
|
|
|
|
styleElement.dataset.qiankunHref = href;
|
|
fetchFn(href).then(function (res) {
|
|
return res.text();
|
|
}).then(function (styleContext) {
|
|
styleElement.appendChild(document.createTextNode(styleContext));
|
|
postProcess(styleElement);
|
|
manualInvokeElementOnLoad(element);
|
|
}).catch(function () {
|
|
return manualInvokeElementOnError(element);
|
|
});
|
|
return styleElement;
|
|
}
|
|
|
|
var styledComponentCSSRulesMap = new WeakMap();
|
|
var dynamicScriptAttachedCommentMap = new WeakMap();
|
|
var dynamicLinkAttachedInlineStyleMap = new WeakMap();
|
|
|
|
function recordStyledComponentsCSSRules(styleElements) {
|
|
styleElements.forEach(function (styleElement) {
|
|
/*
|
|
With a styled-components generated style element, we need to record its cssRules for restore next re-mounting time.
|
|
We're doing this because the sheet of style element is going to be cleaned automatically by browser after the style element dom removed from document.
|
|
see https://www.w3.org/TR/cssom-1/#associated-css-style-sheet
|
|
*/
|
|
if (styleElement instanceof HTMLStyleElement && isStyledComponentsLike(styleElement)) {
|
|
if (styleElement.sheet) {
|
|
// record the original css rules of the style element for restore
|
|
styledComponentCSSRulesMap.set(styleElement, styleElement.sheet.cssRules);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function getStyledElementCSSRules(styledElement) {
|
|
return styledComponentCSSRulesMap.get(styledElement);
|
|
}
|
|
|
|
function getOverwrittenAppendChildOrInsertBefore(opts) {
|
|
return function appendChildOrInsertBefore(newChild) {
|
|
var refChild = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
|
|
var _a, _b;
|
|
|
|
var element = newChild;
|
|
var rawDOMAppendOrInsertBefore = opts.rawDOMAppendOrInsertBefore,
|
|
isInvokedByMicroApp = opts.isInvokedByMicroApp,
|
|
containerConfigGetter = opts.containerConfigGetter,
|
|
_opts$target = opts.target,
|
|
target = _opts$target === void 0 ? 'body' : _opts$target;
|
|
|
|
if (!isHijackingTag(element.tagName) || !isInvokedByMicroApp(element)) {
|
|
return rawDOMAppendOrInsertBefore.call(this, element, refChild);
|
|
}
|
|
|
|
if (element.tagName) {
|
|
var containerConfig = containerConfigGetter(element);
|
|
var appName = containerConfig.appName,
|
|
appWrapperGetter = containerConfig.appWrapperGetter,
|
|
proxy = containerConfig.proxy,
|
|
strictGlobal = containerConfig.strictGlobal,
|
|
dynamicStyleSheetElements = containerConfig.dynamicStyleSheetElements,
|
|
scopedCSS = containerConfig.scopedCSS,
|
|
excludeAssetFilter = containerConfig.excludeAssetFilter;
|
|
|
|
switch (element.tagName) {
|
|
case LINK_TAG_NAME:
|
|
case STYLE_TAG_NAME:
|
|
{
|
|
var stylesheetElement = newChild;
|
|
var _stylesheetElement = stylesheetElement,
|
|
href = _stylesheetElement.href;
|
|
|
|
if (excludeAssetFilter && href && excludeAssetFilter(href)) {
|
|
return rawDOMAppendOrInsertBefore.call(this, element, refChild);
|
|
}
|
|
|
|
Object.defineProperty(stylesheetElement, styleElementTargetSymbol, {
|
|
value: target,
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
var appWrapper = appWrapperGetter();
|
|
|
|
if (scopedCSS) {
|
|
// exclude link elements like <link rel="icon" href="favicon.ico">
|
|
var linkElementUsingStylesheet = ((_a = element.tagName) === null || _a === void 0 ? void 0 : _a.toUpperCase()) === LINK_TAG_NAME && element.rel === 'stylesheet' && element.href;
|
|
|
|
if (linkElementUsingStylesheet) {
|
|
var _fetch = typeof _apis.frameworkConfiguration.fetch === 'function' ? _apis.frameworkConfiguration.fetch : (_b = _apis.frameworkConfiguration.fetch) === null || _b === void 0 ? void 0 : _b.fn;
|
|
|
|
stylesheetElement = convertLinkAsStyle(element, function (styleElement) {
|
|
return css.process(appWrapper, styleElement, appName);
|
|
}, _fetch);
|
|
dynamicLinkAttachedInlineStyleMap.set(element, stylesheetElement);
|
|
} else {
|
|
css.process(appWrapper, stylesheetElement, appName);
|
|
}
|
|
}
|
|
|
|
var mountDOM = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper;
|
|
dynamicStyleSheetElements.push(stylesheetElement);
|
|
var referenceNode = mountDOM.contains(refChild) ? refChild : null;
|
|
return rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode);
|
|
}
|
|
|
|
case SCRIPT_TAG_NAME:
|
|
{
|
|
var _element = element,
|
|
src = _element.src,
|
|
text = _element.text; // some script like jsonp maybe not support cors which should't use execScripts
|
|
|
|
if (excludeAssetFilter && src && excludeAssetFilter(src) || !isExecutableScriptType(element)) {
|
|
return rawDOMAppendOrInsertBefore.call(this, element, refChild);
|
|
}
|
|
|
|
var _appWrapper = appWrapperGetter();
|
|
|
|
var _mountDOM = target === 'head' ? getAppWrapperHeadElement(_appWrapper) : _appWrapper;
|
|
|
|
var _fetch2 = _apis.frameworkConfiguration.fetch;
|
|
|
|
var _referenceNode = _mountDOM.contains(refChild) ? refChild : null;
|
|
|
|
if (src) {
|
|
(0, _importHtmlEntry.execScripts)(null, [src], proxy, {
|
|
fetch: _fetch2,
|
|
strictGlobal: strictGlobal,
|
|
beforeExec: function beforeExec() {
|
|
var isCurrentScriptConfigurable = function isCurrentScriptConfigurable() {
|
|
var descriptor = Object.getOwnPropertyDescriptor(document, 'currentScript');
|
|
return !descriptor || descriptor.configurable;
|
|
};
|
|
|
|
if (isCurrentScriptConfigurable()) {
|
|
Object.defineProperty(document, 'currentScript', {
|
|
get: function get() {
|
|
return element;
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
},
|
|
success: function success() {
|
|
manualInvokeElementOnLoad(element);
|
|
element = null;
|
|
},
|
|
error: function error() {
|
|
manualInvokeElementOnError(element);
|
|
element = null;
|
|
}
|
|
});
|
|
var dynamicScriptCommentElement = document.createComment("dynamic script ".concat(src, " replaced by qiankun"));
|
|
dynamicScriptAttachedCommentMap.set(element, dynamicScriptCommentElement);
|
|
return rawDOMAppendOrInsertBefore.call(_mountDOM, dynamicScriptCommentElement, _referenceNode);
|
|
} // inline script never trigger the onload and onerror event
|
|
|
|
|
|
(0, _importHtmlEntry.execScripts)(null, ["<script>".concat(text, "</script>")], proxy, {
|
|
strictGlobal: strictGlobal
|
|
});
|
|
var dynamicInlineScriptCommentElement = document.createComment('dynamic inline script replaced by qiankun');
|
|
dynamicScriptAttachedCommentMap.set(element, dynamicInlineScriptCommentElement);
|
|
return rawDOMAppendOrInsertBefore.call(_mountDOM, dynamicInlineScriptCommentElement, _referenceNode);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rawDOMAppendOrInsertBefore.call(this, element, refChild);
|
|
};
|
|
}
|
|
|
|
function getNewRemoveChild(headOrBodyRemoveChild, containerConfigGetter, target) {
|
|
return function removeChild(child) {
|
|
var tagName = child.tagName;
|
|
if (!isHijackingTag(tagName)) return headOrBodyRemoveChild.call(this, child);
|
|
|
|
try {
|
|
var attachedElement;
|
|
|
|
var _containerConfigGette = containerConfigGetter(child),
|
|
appWrapperGetter = _containerConfigGette.appWrapperGetter,
|
|
dynamicStyleSheetElements = _containerConfigGette.dynamicStyleSheetElements;
|
|
|
|
switch (tagName) {
|
|
case STYLE_TAG_NAME:
|
|
case LINK_TAG_NAME:
|
|
{
|
|
attachedElement = dynamicLinkAttachedInlineStyleMap.get(child) || child; // try to remove the dynamic style sheet
|
|
|
|
var dynamicElementIndex = dynamicStyleSheetElements.indexOf(attachedElement);
|
|
|
|
if (dynamicElementIndex !== -1) {
|
|
dynamicStyleSheetElements.splice(dynamicElementIndex, 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SCRIPT_TAG_NAME:
|
|
{
|
|
attachedElement = dynamicScriptAttachedCommentMap.get(child) || child;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
attachedElement = child;
|
|
}
|
|
}
|
|
|
|
var appWrapper = appWrapperGetter();
|
|
var container = target === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; // container might have been removed while app unmounting if the removeChild action was async
|
|
|
|
if (container.contains(attachedElement)) {
|
|
return rawRemoveChild.call(attachedElement.parentNode, attachedElement);
|
|
}
|
|
} catch (e) {
|
|
console.warn(e);
|
|
}
|
|
|
|
return headOrBodyRemoveChild.call(this, child);
|
|
};
|
|
}
|
|
|
|
function patchHTMLDynamicAppendPrototypeFunctions(isInvokedByMicroApp, containerConfigGetter) {
|
|
// Just overwrite it while it have not been overwrite
|
|
if (HTMLHeadElement.prototype.appendChild === rawHeadAppendChild && HTMLBodyElement.prototype.appendChild === rawBodyAppendChild && HTMLHeadElement.prototype.insertBefore === rawHeadInsertBefore) {
|
|
HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
|
|
rawDOMAppendOrInsertBefore: rawHeadAppendChild,
|
|
containerConfigGetter: containerConfigGetter,
|
|
isInvokedByMicroApp: isInvokedByMicroApp,
|
|
target: 'head'
|
|
});
|
|
HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
|
|
rawDOMAppendOrInsertBefore: rawBodyAppendChild,
|
|
containerConfigGetter: containerConfigGetter,
|
|
isInvokedByMicroApp: isInvokedByMicroApp,
|
|
target: 'body'
|
|
});
|
|
HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({
|
|
rawDOMAppendOrInsertBefore: rawHeadInsertBefore,
|
|
containerConfigGetter: containerConfigGetter,
|
|
isInvokedByMicroApp: isInvokedByMicroApp,
|
|
target: 'head'
|
|
});
|
|
} // Just overwrite it while it have not been overwrite
|
|
|
|
|
|
if (HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild && HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild) {
|
|
HTMLHeadElement.prototype.removeChild = getNewRemoveChild(rawHeadRemoveChild, containerConfigGetter, 'head');
|
|
HTMLBodyElement.prototype.removeChild = getNewRemoveChild(rawBodyRemoveChild, containerConfigGetter, 'body');
|
|
}
|
|
|
|
return function unpatch() {
|
|
HTMLHeadElement.prototype.appendChild = rawHeadAppendChild;
|
|
HTMLHeadElement.prototype.removeChild = rawHeadRemoveChild;
|
|
HTMLBodyElement.prototype.appendChild = rawBodyAppendChild;
|
|
HTMLBodyElement.prototype.removeChild = rawBodyRemoveChild;
|
|
HTMLHeadElement.prototype.insertBefore = rawHeadInsertBefore;
|
|
};
|
|
}
|
|
|
|
function rebuildCSSRules(styleSheetElements, reAppendElement) {
|
|
styleSheetElements.forEach(function (stylesheetElement) {
|
|
// re-append the dynamic stylesheet to sub-app container
|
|
var appendSuccess = reAppendElement(stylesheetElement);
|
|
|
|
if (appendSuccess) {
|
|
/*
|
|
get the stored css rules from styled-components generated element, and the re-insert rules for them.
|
|
note that we must do this after style element had been added to document, which stylesheet would be associated to the document automatically.
|
|
check the spec https://www.w3.org/TR/cssom-1/#associated-css-style-sheet
|
|
*/
|
|
if (stylesheetElement instanceof HTMLStyleElement && isStyledComponentsLike(stylesheetElement)) {
|
|
var cssRules = getStyledElementCSSRules(stylesheetElement);
|
|
|
|
if (cssRules) {
|
|
// eslint-disable-next-line no-plusplus
|
|
for (var i = 0; i < cssRules.length; i++) {
|
|
var cssRule = cssRules[i];
|
|
var cssStyleSheetElement = stylesheetElement.sheet;
|
|
cssStyleSheetElement.insertRule(cssRule.cssText, cssStyleSheetElement.cssRules.length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} |