UNPKG

@hsui/micro-app

Version:

Hundsun micro-app framework

1,210 lines (1,158 loc) 105 kB
import _regeneratorRuntime from '@babel/runtime/helpers/esm/regeneratorRuntime'; import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2'; import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties'; import _asyncToGenerator from '@babel/runtime/helpers/esm/asyncToGenerator'; import _toConsumableArray from '@babel/runtime/helpers/esm/toConsumableArray'; import { cloneDeep, isFunction, noop, mergeWith, concat, snakeCase } from 'lodash'; import { checkActivityFunctions, getAppStatus, NOT_LOADED, getMountedApps, registerApplication, start as start$1, mountRootParcel, getAppNames } from 'single-spa'; export { addErrorHandler, checkActivityFunctions, getAppNames, getAppStatus, getMountedApps, removeErrorHandler } from 'single-spa'; import { createLogger } from '@hsui/logger'; import { execScripts, importEntry } from 'import-html-entry'; import _defineProperty from '@babel/runtime/helpers/esm/defineProperty'; import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; import _createClass from '@babel/runtime/helpers/esm/createClass'; import _typeof from '@babel/runtime/helpers/esm/typeof'; var globalState = {}; var deps = {}; // 触发全局监听 function emitGlobal(state, prevState) { Object.keys(deps).forEach(function (id) { if (deps[id] instanceof Function) { deps[id](cloneDeep(state), cloneDeep(prevState)); } }); } function initGlobalState() { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (state === globalState) { console.warn('[hui] state has not changed!'); } else { var prevGlobalState = cloneDeep(globalState); globalState = cloneDeep(state); emitGlobal(globalState, prevGlobalState); } return getMicroAppStateActions("global-".concat(+new Date()), true); } function getMicroAppStateActions(id, isMaster) { return { /** * onGlobalStateChange 全局依赖监听 * * 收集 setState 时所需要触发的依赖 * * 限制条件:每个子应用只有一个激活状态的全局监听,新监听覆盖旧监听,若只是监听部分属性,请使用 onGlobalStateChange * * 这么设计是为了减少全局监听滥用导致的内存爆炸 * * 依赖数据结构为: * { * {id}: callback * } * * @param callback * @param fireImmediately */ onGlobalStateChange: function onGlobalStateChange(callback, fireImmediately) { if (!(callback instanceof Function)) { console.error('[hui] callback must be function!'); return; } if (deps[id]) { console.warn("[hui] '".concat(id, "' global listener already exists before this, new listener will overwrite it.")); } deps[id] = callback; if (fireImmediately) { var cloneState = cloneDeep(globalState); callback(cloneState, cloneState); } }, /** * setGlobalState 更新 store 数据 * * 1. 对输入 state 的第一层属性做校验,只有初始化时声明过的第一层(bucket)属性才会被更改 * 2. 修改 store 并触发全局监听 * * @param state */ setGlobalState: function setGlobalState() { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (state === globalState) { console.warn('[hui] state has not changed!'); return false; } var changeKeys = []; var prevGlobalState = cloneDeep(globalState); globalState = cloneDeep(Object.keys(state).reduce(function (_globalState, changeKey) { if (isMaster || _globalState.hasOwnProperty(changeKey)) { changeKeys.push(changeKey); return Object.assign(_globalState, _defineProperty({}, changeKey, state[changeKey])); } console.warn("[hui] '".concat(changeKey, "' not declared when init state\uFF01")); return _globalState; }, globalState)); if (changeKeys.length === 0) { console.warn('[hui] state has not changed!'); return false; } emitGlobal(globalState, prevGlobalState); return true; }, // 注销该应用下的依赖 offGlobalStateChange: function offGlobalStateChange() { delete deps[id]; return true; } }; } /** * @author kuitos * @since 2019-05-16 */ // just for manual loaded apps, in single-spa it called parcel // for the route-based apps var SandBoxType; (function (SandBoxType) { SandBoxType["Proxy"] = "Proxy"; SandBoxType["Snapshot"] = "Snapshot"; SandBoxType["LegacyProxy"] = "LegacyProxy"; })(SandBoxType || (SandBoxType = {})); function toArray(array) { return Array.isArray(array) ? array : [array]; } /** * run a callback after next tick * @param cb */ function nextTick(cb) { Promise.resolve().then(cb); } var fnRegexCheckCacheMap = new WeakMap(); function isConstructable(fn) { // prototype methods might be changed while code running, so we need check it every time var hasPrototypeMethods = fn.prototype && fn.prototype.constructor === fn && Object.getOwnPropertyNames(fn.prototype).length > 1; if (hasPrototypeMethods) return true; if (fnRegexCheckCacheMap.has(fn)) { return fnRegexCheckCacheMap.get(fn); } /* 1. 有 prototype 并且 prototype 上有定义一系列非 constructor 属性 2. 函数名大写开头 3. class 函数 满足其一则可认定为构造函数 */ var constructable = hasPrototypeMethods; if (!constructable) { // fn.toString has a significant performance overhead, if hasPrototypeMethods check not passed, we will check the function string with regex var fnString = fn.toString(); var constructableFunctionRegex = /^function\b\s[A-Z].*/; var classRegex = /^class\b/; constructable = constructableFunctionRegex.test(fnString) || classRegex.test(fnString); } fnRegexCheckCacheMap.set(fn, constructable); return constructable; } /** * in safari * typeof document.all === 'undefined' // true * typeof document.all === 'function' // true * We need to discriminate safari for better performance */ var naughtySafari = typeof document.all === 'function' && typeof document.all === 'undefined'; var callableFnCacheMap = new WeakMap(); var isCallable = function isCallable(fn) { if (callableFnCacheMap.has(fn)) { return true; } var callable = naughtySafari ? typeof fn === 'function' && typeof fn !== 'undefined' : typeof fn === 'function'; if (callable) { callableFnCacheMap.set(fn, callable); } return callable; }; var boundedMap = new WeakMap(); function isBoundedFunction(fn) { if (boundedMap.has(fn)) { return boundedMap.get(fn); } /* indexOf is faster than startsWith see https://jsperf.com/string-startswith/72 */ var bounded = fn.name.indexOf('bound ') === 0 && !fn.hasOwnProperty('prototype'); boundedMap.set(fn, bounded); return bounded; } /** 校验子应用导出的 生命周期 对象是否正确 */ function validateExportLifecycle(exports) { var _ref = exports !== null && exports !== void 0 ? exports : {}, bootstrap = _ref.bootstrap, mount = _ref.mount, unmount = _ref.unmount; return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount); } var Deferred = /*#__PURE__*/_createClass(function Deferred() { var _this = this; _classCallCheck(this, Deferred); this.promise = void 0; this.resolve = void 0; this.reject = void 0; this.promise = new Promise(function (resolve, reject) { _this.resolve = resolve; _this.reject = reject; }); }); /** * copy from https://developer.mozilla.org/zh-CN/docs/Using_XPath * @param el * @param document */ function getXPathForElement(el, document) { // not support that if el not existed in document yet(such as it not append to document before it mounted) if (!document.body.contains(el)) { return undefined; } var xpath = ''; var pos; var tmpEle; var element = el; while (element !== document.documentElement) { pos = 0; tmpEle = element; while (tmpEle) { if (tmpEle.nodeType === 1 && tmpEle.nodeName === element.nodeName) { // If it is ELEMENT_NODE of the same name pos += 1; } tmpEle = tmpEle.previousSibling; } xpath = "*[name()='".concat(element.nodeName, "'][").concat(pos, "]/").concat(xpath); element = element.parentNode; } xpath = "/*[name()='".concat(document.documentElement.nodeName, "']/").concat(xpath); xpath = xpath.replace(/\/$/, ''); return xpath; } function getContainer(container) { return typeof container === 'string' ? document.querySelector(container) : container; } /** * @author Kuitos * @since 2020-04-13 */ // Get native global window with a sandbox disgusted way, thus we could share it between qiankun instances🤪 // eslint-disable-next-line no-new-func var nativeGlobal = new Function('return this')(); Object.defineProperty(nativeGlobal, '__currentRunningSandboxProxy__', { enumerable: false, writable: true }); function getCurrentRunningSandboxProxy() { return nativeGlobal.__currentRunningSandboxProxy__; } function setCurrentRunningSandboxProxy(proxy) { // set currentRunningSandboxProxy to global window, as its only use case is for document.createElement from now on, which hijacked by a global way nativeGlobal.__currentRunningSandboxProxy__ = proxy; } var functionBoundedValueMap = new WeakMap(); function getTargetValue(target, value) { /* 仅绑定 isCallable && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常 目前没有完美的检测方式,这里通过 prototype 中是否还有可枚举的拓展方法的方式来判断 @warning 这里不要随意替换成别的判断方式,因为可能触发一些 edge case(比如在 lodash.isFunction 在 iframe 上下文中可能由于调用了 top window 对象触发的安全异常) */ if (isCallable(value) && !isBoundedFunction(value) && !isConstructable(value)) { var cachedBoundFunction = functionBoundedValueMap.get(value); if (cachedBoundFunction) { return cachedBoundFunction; } var boundValue = Function.prototype.bind.call(value, target); // some callable function has custom fields, we need to copy the enumerable props to boundValue. such as moment function. // use for..in rather than Object.keys.forEach for performance reason // eslint-disable-next-line guard-for-in,no-restricted-syntax for (var key in value) { boundValue[key] = value[key]; } // copy prototype if bound function not have but target one have // as prototype is non-enumerable mostly, we need to copy it from target function manually if (value.hasOwnProperty('prototype') && !boundValue.hasOwnProperty('prototype')) { // we should not use assignment operator to set boundValue prototype like `boundValue.prototype = value.prototype` // as the assignment will also look up prototype chain while it hasn't own prototype property, // when the lookup succeed, the assignment will throw an TypeError like `Cannot assign to read only property 'prototype' of function` if its descriptor configured with writable false or just have a getter accessor // see https://github.com/umijs/qiankun/issues/1121 Object.defineProperty(boundValue, 'prototype', { value: value.prototype, enumerable: false, writable: true }); } functionBoundedValueMap.set(value, boundValue); return boundValue; } return value; } function isPropConfigurable(target, prop) { var descriptor = Object.getOwnPropertyDescriptor(target, prop); return descriptor ? descriptor.configurable : true; } function setWindowProp(prop, value, toDelete) { if (value === undefined && toDelete) { delete window[prop]; } else if (isPropConfigurable(window, prop) && _typeof(prop) !== 'symbol') { Object.defineProperty(window, prop, { writable: true, configurable: true }); window[prop] = value; } } /** * 基于 Proxy 实现的沙箱 * TODO: 为了兼容性 singular 模式下依旧使用该沙箱,等新沙箱稳定之后再切换 */ var SingularProxySandbox = /*#__PURE__*/function () { function SingularProxySandbox(name) { var _this = this; _classCallCheck(this, SingularProxySandbox); /** 沙箱期间新增的全局变量 */ this.addedPropsMapInSandbox = new Map(); /** 沙箱期间更新的全局变量 */ this.modifiedPropsOriginalValueMapInSandbox = new Map(); /** 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot */ this.currentUpdatedPropsValueMap = new Map(); this.name = void 0; this.proxy = void 0; this.type = void 0; this.sandboxRunning = true; this.latestSetProp = null; this.name = name; this.type = SandBoxType.LegacyProxy; var addedPropsMapInSandbox = this.addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox = this.modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap = this.currentUpdatedPropsValueMap; var rawWindow = window; var fakeWindow = Object.create(null); var setTrap = function setTrap(p, value, originalValue) { var sync2Window = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; if (_this.sandboxRunning) { if (!rawWindow.hasOwnProperty(p)) { addedPropsMapInSandbox.set(p, value); } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) { // 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值 modifiedPropsOriginalValueMapInSandbox.set(p, originalValue); } currentUpdatedPropsValueMap.set(p, value); if (sync2Window) { // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据 rawWindow[p] = value; } _this.latestSetProp = p; return true; } if (process.env.NODE_ENV === 'development') { console.warn("[hui] Set window.".concat(p.toString(), " while sandbox destroyed or inactive in ").concat(name, "!")); } // 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,在沙箱卸载的情况下应该忽略错误 return true; }; var proxy = new Proxy(fakeWindow, { set: function set(_, p, value) { var originalValue = rawWindow[p]; return setTrap(p, value, originalValue, true); }, get: function get(_, p) { // avoid who using window.window or window.self to escape the sandbox environment to touch the really window // or use window.top to check if an iframe context // see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13 if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') { return proxy; } var value = rawWindow[p]; return getTargetValue(rawWindow, value); }, // trap in operator // see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12 has: function has(_, p) { return p in rawWindow; }, getOwnPropertyDescriptor: function getOwnPropertyDescriptor(_, p) { var descriptor = Object.getOwnPropertyDescriptor(rawWindow, p); // A property cannot be reported as non-configurable, if it does not exists as an own property of the target object if (descriptor && !descriptor.configurable) { descriptor.configurable = true; } return descriptor; }, defineProperty: function defineProperty(_, p, attributes) { var originalValue = rawWindow[p]; var done = Reflect.defineProperty(rawWindow, p, attributes); var value = rawWindow[p]; setTrap(p, value, originalValue, false); return done; } }); this.proxy = proxy; } _createClass(SingularProxySandbox, [{ key: "active", value: function active() { if (!this.sandboxRunning) { this.currentUpdatedPropsValueMap.forEach(function (v, p) { return setWindowProp(p, v); }); } this.sandboxRunning = true; } }, { key: "inactive", value: function inactive() { if (process.env.NODE_ENV === 'development') { console.info("[hui:sandbox] ".concat(this.name, " modified global properties restore..."), [].concat(_toConsumableArray(this.addedPropsMapInSandbox.keys()), _toConsumableArray(this.modifiedPropsOriginalValueMapInSandbox.keys()))); } // renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot); // restore global props to initial snapshot this.modifiedPropsOriginalValueMapInSandbox.forEach(function (v, p) { return setWindowProp(p, v); }); this.addedPropsMapInSandbox.forEach(function (_, p) { return setWindowProp(p, undefined, true); }); this.sandboxRunning = false; } }]); return SingularProxySandbox; }(); var frameworkConfiguration = { prefetch: false, singular: true, strictGlobal: true, $$syncOnly: false, logger: null }; /** * @author Kuitos * @since 2019-10-21 */ var rawHeadAppendChild = HTMLHeadElement.prototype.appendChild; var rawHeadRemoveChild = HTMLHeadElement.prototype.removeChild; var rawBodyAppendChild = HTMLBodyElement.prototype.appendChild; var rawBodyRemoveChild = HTMLBodyElement.prototype.removeChild; var rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; var rawRemoveChild$1 = HTMLElement.prototype.removeChild; var SCRIPT_TAG_NAME = 'SCRIPT'; var LINK_TAG_NAME = 'LINK'; var STYLE_TAG_NAME = 'STYLE'; 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 _element$sheet, _getStyledElementCSSR; return !element.textContent && (((_element$sheet = element.sheet) === null || _element$sheet === void 0 ? void 0 : _element$sheet.cssRules.length) || ((_getStyledElementCSSR = getStyledElementCSSRules(element)) === null || _getStyledElementCSSR === void 0 ? void 0 : _getStyledElementCSSR.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 (isFunction(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 (isFunction(element.onerror)) { element.onerror(patchedEvent); } else { element.dispatchEvent(patchedEvent); } } 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, refChild) { var element = newChild; var rawDOMAppendOrInsertBefore = opts.rawDOMAppendOrInsertBefore, isInvokedByMicroApp = opts.isInvokedByMicroApp, containerConfigGetter = opts.containerConfigGetter; if (!isHijackingTag(element.tagName) || !isInvokedByMicroApp(element)) { return rawDOMAppendOrInsertBefore.call(this, element, refChild); } if (element.tagName) { var containerConfig = containerConfigGetter(element); containerConfig.appName; var appWrapperGetter = containerConfig.appWrapperGetter, proxy = containerConfig.proxy, strictGlobal = containerConfig.strictGlobal, dynamicStyleSheetElements = containerConfig.dynamicStyleSheetElements; containerConfig.scopedCSS; var excludeAssetFilter = containerConfig.excludeAssetFilter; switch (element.tagName) { case LINK_TAG_NAME: case STYLE_TAG_NAME: { var _stylesheetElement = newChild; var _ref = _stylesheetElement, href = _ref.href; if (excludeAssetFilter && href && excludeAssetFilter(href)) { return rawDOMAppendOrInsertBefore.call(this, element, refChild); } var mountDOM = appWrapperGetter(); // eslint-disable-next-line no-shadow dynamicStyleSheetElements.push(_stylesheetElement); var referenceNode = mountDOM.contains(refChild) ? refChild : null; return rawDOMAppendOrInsertBefore.call(mountDOM, _stylesheetElement, referenceNode); } case SCRIPT_TAG_NAME: { var _ref2 = element, src = _ref2.src, text = _ref2.text; // some script like jsonp maybe not support cors which should't use execScripts if (excludeAssetFilter && src && excludeAssetFilter(src)) { return rawDOMAppendOrInsertBefore.call(this, element, refChild); } var _mountDOM = appWrapperGetter(); var _fetch = frameworkConfiguration.fetch; var _referenceNode = _mountDOM.contains(refChild) ? refChild : null; if (src) { execScripts(null, [src], proxy, { fetch: _fetch, 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 hui")); dynamicScriptAttachedCommentMap.set(element, dynamicScriptCommentElement); return rawDOMAppendOrInsertBefore.call(_mountDOM, dynamicScriptCommentElement, _referenceNode); } // inline script never trigger the onload and onerror event execScripts(null, ["<script>".concat(text, "</script>")], proxy, { strictGlobal: strictGlobal }); var dynamicInlineScriptCommentElement = document.createComment('dynamic inline script replaced by by hui'); dynamicScriptAttachedCommentMap.set(element, dynamicInlineScriptCommentElement); return rawDOMAppendOrInsertBefore.call(_mountDOM, dynamicInlineScriptCommentElement, _referenceNode); } } } return rawDOMAppendOrInsertBefore.call(this, element, refChild); }; } function getNewRemoveChild(headOrBodyRemoveChild, appWrapperGetterGetter) { return function removeChild(child) { var _ref3 = child, tagName = _ref3.tagName; if (!isHijackingTag(tagName)) return headOrBodyRemoveChild.call(this, child); try { var attachedElement; switch (tagName) { case LINK_TAG_NAME: { attachedElement = dynamicLinkAttachedInlineStyleMap.get(child) || child; break; } case SCRIPT_TAG_NAME: { attachedElement = dynamicScriptAttachedCommentMap.get(child) || child; break; } default: { attachedElement = child; } } // container may had been removed while app unmounting if the removeChild action was async var appWrapperGetter = appWrapperGetterGetter(child); var container = appWrapperGetter(); if (container.contains(attachedElement)) { return rawRemoveChild$1.call(container, 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 }); HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({ rawDOMAppendOrInsertBefore: rawBodyAppendChild, containerConfigGetter: containerConfigGetter, isInvokedByMicroApp: isInvokedByMicroApp }); HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({ rawDOMAppendOrInsertBefore: rawHeadInsertBefore, containerConfigGetter: containerConfigGetter, isInvokedByMicroApp: isInvokedByMicroApp }); } // Just overwrite it while it have not been overwrite if (HTMLHeadElement.prototype.removeChild === rawHeadRemoveChild && HTMLBodyElement.prototype.removeChild === rawBodyRemoveChild) { HTMLHeadElement.prototype.removeChild = getNewRemoveChild(rawHeadRemoveChild, function (element) { return containerConfigGetter(element).appWrapperGetter; }); HTMLBodyElement.prototype.removeChild = getNewRemoveChild(rawBodyRemoveChild, function (element) { return containerConfigGetter(element).appWrapperGetter; }); } 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); } } } } }); } /** * @author Kuitos * @since 2020-10-13 */ var bootstrappingPatchCount$1 = 0; var mountingPatchCount$1 = 0; /** * Just hijack dynamic head append, that could avoid accidentally hijacking the insertion of elements except in head. * Such a case: ReactDOM.createPortal(<style>.test{color:blue}</style>, container), * this could made we append the style element into app wrapper but it will cause an error while the react portal unmounting, as ReactDOM could not find the style in body children list. * @param appName * @param appWrapperGetter * @param proxy * @param mounting * @param scopedCSS * @param excludeAssetFilter */ function patchLooseSandbox(appName, appWrapperGetter, proxy) { var mounting = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; var scopedCSS = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var excludeAssetFilter = arguments.length > 5 ? arguments[5] : undefined; var dynamicStyleSheetElements = []; var unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions( /* check if the currently specified application is active While we switch page from qiankun app to a normal react routing page, the normal one may load stylesheet dynamically while page rendering, but the url change listener must to wait until the current call stack is flushed. This scenario may cause we record the stylesheet from react routing page dynamic injection, and remove them after the url change triggered and qiankun app is unmouting see https://github.com/ReactTraining/history/blob/master/modules/createHashHistory.js#L222-L230 */ function () { return checkActivityFunctions(window.location).some(function (name) { return name === appName; }); }, function () { return { appName: appName, appWrapperGetter: appWrapperGetter, proxy: proxy, strictGlobal: false, scopedCSS: scopedCSS, dynamicStyleSheetElements: dynamicStyleSheetElements, excludeAssetFilter: excludeAssetFilter }; }); if (!mounting) bootstrappingPatchCount$1++; if (mounting) mountingPatchCount$1++; return function free() { // bootstrap patch just called once but its freer will be called multiple times if (!mounting && bootstrappingPatchCount$1 !== 0) bootstrappingPatchCount$1--; if (mounting) mountingPatchCount$1--; var allMicroAppUnmounted = mountingPatchCount$1 === 0 && bootstrappingPatchCount$1 === 0; // release the overwrite prototype after all the micro apps unmounted if (allMicroAppUnmounted) unpatchDynamicAppendPrototypeFunctions(); recordStyledComponentsCSSRules(dynamicStyleSheetElements); // As now the sub app content all wrapped with a special id container, // the dynamic style sheet would be removed automatically while unmoutting return function rebuild() { rebuildCSSRules(dynamicStyleSheetElements, function (stylesheetElement) { var appWrapper = appWrapperGetter(); if (!appWrapper.contains(stylesheetElement)) { // Using document.head.appendChild ensures that appendChild invocation can also directly use the HTMLHeadElement.prototype.appendChild method which is overwritten at mounting phase document.head.appendChild.call(appWrapper, stylesheetElement); return true; } return false; }); // As the patcher will be invoked every mounting phase, we could release the cache for gc after rebuilding if (mounting) { dynamicStyleSheetElements = []; } }; }; } /** * @author Kuitos * @since 2020-10-13 */ var rawDocumentCreateElement = Document.prototype.createElement; var proxyAttachContainerConfigMap = new WeakMap(); var elementAttachContainerConfigMap = new WeakMap(); function patchDocumentCreateElement() { if (Document.prototype.createElement === rawDocumentCreateElement) { Document.prototype.createElement = function createElement(tagName, options) { var element = rawDocumentCreateElement.call(this, tagName, options); if (isHijackingTag(tagName)) { var currentRunningSandboxProxy = getCurrentRunningSandboxProxy(); if (currentRunningSandboxProxy) { var proxyContainerConfig = proxyAttachContainerConfigMap.get(currentRunningSandboxProxy); if (proxyContainerConfig) { elementAttachContainerConfigMap.set(element, proxyContainerConfig); } } } return element; }; } return function unpatch() { Document.prototype.createElement = rawDocumentCreateElement; }; } var bootstrappingPatchCount = 0; var mountingPatchCount = 0; function patchStrictSandbox(appName, appWrapperGetter, proxy) { var mounting = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; var scopedCSS = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var excludeAssetFilter = arguments.length > 5 ? arguments[5] : undefined; var containerConfig = proxyAttachContainerConfigMap.get(proxy); if (!containerConfig) { var strictGlobal = frameworkConfiguration.strictGlobal; containerConfig = { appName: appName, proxy: proxy, appWrapperGetter: appWrapperGetter, dynamicStyleSheetElements: [], strictGlobal: strictGlobal, excludeAssetFilter: excludeAssetFilter, scopedCSS: scopedCSS }; proxyAttachContainerConfigMap.set(proxy, containerConfig); } // all dynamic style sheets are stored in proxy container var _containerConfig = containerConfig, dynamicStyleSheetElements = _containerConfig.dynamicStyleSheetElements; var unpatchDocumentCreate = patchDocumentCreateElement(); var unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(function (element) { return elementAttachContainerConfigMap.has(element); }, function (element) { return elementAttachContainerConfigMap.get(element); }); if (!mounting) bootstrappingPatchCount++; if (mounting) mountingPatchCount++; return function free() { // bootstrap patch just called once but its freer will be called multiple times if (!mounting && bootstrappingPatchCount !== 0) bootstrappingPatchCount--; if (mounting) mountingPatchCount--; var allMicroAppUnmounted = mountingPatchCount === 0 && bootstrappingPatchCount === 0; // release the overwrite prototype after all the micro apps unmounted if (allMicroAppUnmounted) { unpatchDynamicAppendPrototypeFunctions(); unpatchDocumentCreate(); } recordStyledComponentsCSSRules(dynamicStyleSheetElements); // As now the sub app content all wrapped with a special id container, // the dynamic style sheet would be removed automatically while unmoutting return function rebuild() { rebuildCSSRules(dynamicStyleSheetElements, function (stylesheetElement) { var appWrapper = appWrapperGetter(); if (!appWrapper.contains(stylesheetElement)) { rawHeadAppendChild.call(appWrapper, stylesheetElement); return true; } return false; }); }; }; } var rawWindowInterval = window.setInterval; var rawWindowClearInterval = window.clearInterval; function patch$1(global) { var intervals = []; global.clearInterval = function (intervalId) { intervals = intervals.filter(function (id) { return id !== intervalId; }); return rawWindowClearInterval.call(window, intervalId); }; global.setInterval = function (handler, timeout) { for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } var intervalId = rawWindowInterval.apply(void 0, [handler, timeout].concat(args)); intervals = [].concat(_toConsumableArray(intervals), [intervalId]); return intervalId; }; return function free() { intervals.forEach(function (id) { return global.clearInterval(id); }); global.setInterval = rawWindowInterval; global.clearInterval = rawWindowClearInterval; return noop; }; } /* eslint-disable no-param-reassign */ /** * @author Kuitos * @since 2019-04-11 */ var rawAddEventListener = window.addEventListener; var rawRemoveEventListener = window.removeEventListener; function patch(global) { var listenerMap = new Map(); global.addEventListener = function (type, listener, options) { var listeners = listenerMap.get(type) || []; listenerMap.set(type, [].concat(_toConsumableArray(listeners), [{ listener: listener, options: options }])); return rawAddEventListener.call(window, type, listener, options); }; global.removeEventListener = function (type, listener, options) { var storedTypeListeners = listenerMap.get(type); if (storedTypeListeners && storedTypeListeners.length) { var index = storedTypeListeners.findIndex(function (item) { return item.listener === listener; }); if (index > -1) { storedTypeListeners.splice(index, 1); } } return rawRemoveEventListener.call(window, type, listener, options); }; return function free() { global.addEventListener = rawAddEventListener; global.removeEventListener = rawRemoveEventListener; listenerMap.forEach(function (listeners, type) { return _toConsumableArray(listeners).forEach(function (_ref) { var listener = _ref.listener; return global.removeEventListener(type, listener); }); }); // Qiankun remove all listeners and won't rebuild them, so the micro apps have to reinitialize. return function rebuild() { listenerMap.forEach(function (listeners, type) { _toConsumableArray(listeners).forEach(function (_ref2) { var listener = _ref2.listener, options = _ref2.options; return global.addEventListener(type, listener, options); }); }); }; }; } function patchAtMounting(appName, elementGetter, sandbox, scopedCSS, excludeAssetFilter) { var _patchersInSandbox, _patchersInSandbox$sa; var basePatchers = [function () { return patch$1(sandbox.proxy); }, function () { return patch(sandbox.proxy); }]; var patchersInSandbox = (_patchersInSandbox = {}, _defineProperty(_patchersInSandbox, SandBoxType.LegacyProxy, [].concat(basePatchers, [function () { return frameworkConfiguration.$$syncOnly ? patchStrictSandbox(appName, elementGetter, sandbox.proxy, true, scopedCSS, excludeAssetFilter) : patchLooseSandbox(appName, elementGetter, sandbox.proxy, true, scopedCSS, excludeAssetFilter); }])), _defineProperty(_patchersInSandbox, SandBoxType.Proxy, [].concat(basePatchers, [function () { return patchStrictSandbox(appName, elementGetter, sandbox.proxy, true, scopedCSS, excludeAssetFilter); }])), _defineProperty(_patchersInSandbox, SandBoxType.Snapshot, [].concat(basePatchers, [function () { return patchLooseSandbox(appName, elementGetter, sandbox.proxy, true, scopedCSS, excludeAssetFilter); }])), _patchersInSandbox); return (_patchersInSandbox$sa = patchersInSandbox[sandbox.type]) === null || _patchersInSandbox$sa === void 0 ? void 0 : _patchersInSandbox$sa.map(function (patch) { return patch(); }); } function patchAtBootstrapping(appName, elementGetter, sandbox, scopedCSS, excludeAssetFilter) { var _patchersInSandbox2, _patchersInSandbox$sa2; var patchersInSandbox = (_patchersInSandbox2 = {}, _defineProperty(_patchersInSandbox2, SandBoxType.LegacyProxy, [function () { return frameworkConfiguration.$$syncOnly ? patchStrictSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter) : patchLooseSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter); }]), _defineProperty(_patchersInSandbox2, SandBoxType.Proxy, [function () { return patchStrictSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter); }]), _defineProperty(_patchersInSandbox2, SandBoxType.Snapshot, [function () { return patchLooseSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter); }]), _patchersInSandbox2); return (_patchersInSandbox$sa2 = patchersInSandbox[sandbox.type]) === null || _patchersInSandbox$sa2 === void 0 ? void 0 : _patchersInSandbox$sa2.map(function (patch) { return patch(); }); } /** * fastest(at most time) unique array method * @see https://jsperf.com/array-filter-unique/30 */ function uniq(array) { return array.filter(function filter(element) { return element in this ? false : this[element] = true; }, Object.create(null)); } // zone.js will overwrite Object.defineProperty var rawObjectDefineProperty = Object.defineProperty; var variableWhiteListInDev = process.env.NODE_ENV === 'development' ? [ // for react hot reload // see https://github.com/facebook/create-react-app/blob/66bf7dfc43350249e2f09d138a20840dae8a0a4a/packages/react-error-overlay/src/index.js#L180 '__REACT_ERROR_OVERLAY_GLOBAL_HOOK__'] : []; // who could escape the sandbox var variableWhiteList = [ // FIXME System.js used a indirect call with eval, which would make it scope escape to global // To make System.js works well, we write it back to global window temporary // see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/evaluate.js#L106 'System', // see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/instantiate.js#L357 '__cjsWrapper'].concat(variableWhiteListInDev); // fix AMD Module var AMDRawValueMap = new Map([['requirejs', window.requirejs], ['require', window.require], ['define', window.define]]); /* variables who are impossible to be overwrite need to be escaped from proxy sandbox for performance reasons */ var unscopables = { undefined: true, Array: true, Object: true, String: true, Boolean: true, Math: true, Number: true, Symbol: true, parseFloat: true, Float32Array: true }; function createFakeWindow(global) { // map always has the fastest performance in has check scenario // see https://jsperf.com/array-indexof-vs-set-has/23 var propertiesWithGetter = new Map(); var fakeWindow = {}; /* copy the non-configurable property of global to fakeWindow see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor > A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object. */ Object.getOwnPropertyNames(global).filter(function (p) { var descriptor = Object.getOwnPropertyDescriptor(global, p); return !(descriptor !== null && descriptor !== void 0 && descriptor.configurable); }).forEach(function (p) { var descriptor = Object.getOwnPropertyDescriptor(global, p); if (descriptor) { var hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get'); /* make top/self/window property configurable and writable, otherwise it will cause TypeError while get trap return. see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get > The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property. */ if (p === 'top' || p === 'parent' || p === 'self' || p === 'window' || process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop')) { descriptor.configurable = true; /* The descriptor of window.window/window.top/window.self in Safari/FF are accessor descriptors, we need to avoid adding a data descriptor while it was Example: Safari/FF: Object.getOwnPropertyDescriptor(window, 'top') -> {get: function, set: undefined, enumerable: true, configurable: false} Chrome: Object.getOwnPropertyDescriptor(window, 'top') -> {value: Window, writable: false, enumerable: true, configurable: false} */ if (!hasGetter) { descriptor.writable = true; } } if (hasGetter) propertiesWithGetter.set(p, true); // freeze the descriptor to avoid being modified by zone.js // see https://github.com/angular/zone.js/blob/a5fe09b0fac27ac5df1fa746042f96f05ccb6a00/lib/browser/define-property.ts#L71 rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor)); } }); return { fakeWindow: fakeWindow, propertiesWithGetter: propertiesWithGetter }; } var activeSandboxCount = 0; /** * 基于 Proxy 实现的沙箱 */ var ProxySandbox = /*#__PURE__*/function () { function ProxySandbox(name) { var _this = this; _classCallCheck(this, ProxySandbox); /** window 值变更记录 */ this.updatedValueSet = new Set(); /** AMD Module */ this.AMDProxyValueMap = new Map(); this.name = void 0; this.type = void 0; this.proxy = void 0; this.sandboxRunning = true; this.latestSetProp = null; this.name = name; this.type = SandBoxType.Proxy; var updatedValueSet = this.updatedValueSet; var rawWindow = window; var _createFakeWindow = createFakeWindow(rawWindow), fakeWindow = _createFakeWindow.fakeWindow, propertiesWithGetter = _createFakeWindow.propertiesWithGetter; var descriptorTargetMap = new Map(); var hasOwnProperty = function hasOwnProperty(key) { return fakeWindow.hasOwnProperty(key) || rawWindow.hasOwnProperty(key); }; var proxy = new Proxy(fakeWindow, { set: function set(target, p, value) { if (_this.sandboxRunning) { // We must kept its description while the property existed in rawWindow before if (!target.hasOwnProperty(p) && rawWindow.hasOwnProperty(p)) { var descriptor = Object.getOwnPropertyDescriptor(rawWindow, p); var _ref = descriptor, writable = _ref.writable, configurable = _ref.configurable, enumerable = _ref.enumerable; if (writable) { Object.defineProperty(target, p, { configurable: configurable, enumerable: enumerable, writable: writable, value: value }); } } else { // @ts-ignore target[p] = value; } if (variableWhiteList.indexOf(p) !== -1) { // @ts-ignore rawWindow[p] = value; } if (!frameworkConfiguration.strictGlobal && AMDRawValueMap.has(p)) { // @ts-ignore _this.AMDProxyValueMap.set(p, value); rawWindow[p] = value; return true; } updatedValueSet.add(p); _this.latestSetProp = p; return true; } if (process.env.NODE_ENV === 'development') { console.warn("[hui] Set window.".concat(p.toString(), " while sandbox destroyed or inactive in ").concat(name, "!")); } // 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,在沙箱卸载的情况下应该忽略错误 return true; }, get: function get(target, p) { if (p === Symbol.unscopables) return unscopables; // avoid who using window.window or window.self to escape the sandbox environment to touch the really window // see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13 if (p === 'window' || p === 'self') { return proxy; } // hijack global accessing with globalThis keyword if (p === 'globalThis') { return proxy; } if (p === 'top' || p === 'parent' || process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop')) { // if your master app in an iframe context, allow these props escape the sandbox if (rawWindow === rawWindow.parent) { return proxy; } return rawWindow[p]; } // proxy.hasOwnProperty would invoke getter firstly, then its value represented as rawWindow.hasOwnProperty if (p === 'hasOwnProperty') { return hasOwnProperty; } // mark the symbol to document while accessing as document.createElement could know is invoked by which sandbox for dynamic append patcher if (p === 'document' || p === 'eval') { setCurrentRunningSandboxProxy(proxy); // FIXME if you have any other good ideas // remove the mark in next tick, thus we can identify whether it in micro app or not // this approach is just a workaround, it could not cover all complex cases, such as the micro app runs in the same task context with master in some case nextTick(function () { return setCurrentRunningSandboxProxy(null); }); switch (p) { case 'document': return document; case 'eval': // eslint-disable-next-line no-eval return eval; /