UNPKG

@ksconsole/qiankun-plus

Version:

A completed implementation of Micro Frontends

275 lines 12.9 kB
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; /** * @author Kuitos * @since 2020-10-13 */ import { isBoundedFunction, isCallable, nativeDocument, nativeGlobal } from '../../../utils'; import { getCurrentRunningApp } from '../../common'; import { calcAppCount, getAppWrapperHeadElement, isAllAppsUnmounted, isHijackingTag, patchHTMLDynamicAppendPrototypeFunctions, rebuildCSSRules, recordStyledComponentsCSSRules, styleElementRefNodeNo, styleElementTargetSymbol } from './common'; var elementAttachedSymbol = Symbol('attachedApp'); // Get native global window with a sandbox disgusted way, thus we could share it between qiankun instances🤪 Object.defineProperty(nativeGlobal, '__proxyAttachContainerConfigMap__', { enumerable: false, writable: true }); Object.defineProperty(nativeGlobal, '__currentLockingSandbox__', { enumerable: false, writable: true, configurable: true }); var rawHeadAppendChild = HTMLHeadElement.prototype.appendChild; var rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; // Share proxyAttachContainerConfigMap between multiple qiankun instance, thus they could access the same record nativeGlobal.__proxyAttachContainerConfigMap__ = nativeGlobal.__proxyAttachContainerConfigMap__ || new WeakMap(); var proxyAttachContainerConfigMap = nativeGlobal.__proxyAttachContainerConfigMap__; var elementAttachContainerConfigMap = new WeakMap(); var docCreatePatchedMap = new WeakMap(); var patchMap = new WeakMap(); function patchDocument(cfg) { var sandbox = cfg.sandbox, speedy = cfg.speedy; var attachElementToProxy = function attachElementToProxy(element, proxy) { var proxyContainerConfig = proxyAttachContainerConfigMap.get(proxy); if (proxyContainerConfig) { elementAttachContainerConfigMap.set(element, proxyContainerConfig); } }; if (speedy) { var modifications = {}; var proxyDocument = new Proxy(document, { /** * Read and write must be paired, otherwise the write operation will leak to the global */ set: function set(target, p, value) { switch (p) { case 'createElement': { modifications.createElement = value; break; } case 'querySelector': { modifications.querySelector = value; break; } default: target[p] = value; break; } return true; }, get: function get(target, p, receiver) { switch (p) { case 'createElement': { // Must store the original createElement function to avoid error in nested sandbox var targetCreateElement = modifications.createElement || target.createElement; return function createElement() { if (!nativeGlobal.__currentLockingSandbox__) { nativeGlobal.__currentLockingSandbox__ = sandbox.name; } for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var element = targetCreateElement.call.apply(targetCreateElement, [target].concat(args)); // only record the element which is created by the current sandbox, thus we can avoid the element created by nested sandboxes if (nativeGlobal.__currentLockingSandbox__ === sandbox.name) { attachElementToProxy(element, sandbox.proxy); delete nativeGlobal.__currentLockingSandbox__; } return element; }; } case 'querySelector': { var targetQuerySelector = modifications.querySelector || target.querySelector; return function querySelector() { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } var selector = args[0]; switch (selector) { case 'head': { var containerConfig = proxyAttachContainerConfigMap.get(sandbox.proxy); if (containerConfig) { var qiankunHead = getAppWrapperHeadElement(containerConfig.appWrapperGetter()); qiankunHead.appendChild = HTMLHeadElement.prototype.appendChild; qiankunHead.insertBefore = HTMLHeadElement.prototype.insertBefore; qiankunHead.removeChild = HTMLHeadElement.prototype.removeChild; return qiankunHead; } break; } } return targetQuerySelector.call.apply(targetQuerySelector, [target].concat(args)); }; } default: break; } var value = target[p]; // must rebind the function to the target otherwise it will cause illegal invocation error if (isCallable(value) && !isBoundedFunction(value)) { return function proxyFunction() { for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } return value.call.apply(value, [target].concat(_toConsumableArray(args.map(function (arg) { return arg === receiver ? target : arg; })))); }; } return value; } }); sandbox.patchDocument(proxyDocument); // patch MutationObserver.prototype.observe to avoid type error // https://github.com/umijs/qiankun/issues/2406 var nativeMutationObserverObserveFn = MutationObserver.prototype.observe; if (!patchMap.has(nativeMutationObserverObserveFn)) { var observe = function observe(target, options) { var realTarget = target instanceof Document ? nativeDocument : target; return nativeMutationObserverObserveFn.call(this, realTarget, options); }; MutationObserver.prototype.observe = observe; patchMap.set(nativeMutationObserverObserveFn, observe); } // patch Node.prototype.compareDocumentPosition to avoid type error var prevCompareDocumentPosition = Node.prototype.compareDocumentPosition; if (!patchMap.has(prevCompareDocumentPosition)) { Node.prototype.compareDocumentPosition = function compareDocumentPosition(node) { var realNode = node instanceof Document ? nativeDocument : node; return prevCompareDocumentPosition.call(this, realNode); }; patchMap.set(prevCompareDocumentPosition, Node.prototype.compareDocumentPosition); } // patch parentNode getter to avoid document === html.parentNode // https://github.com/umijs/qiankun/issues/2408#issuecomment-1446229105 var parentNodeDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode'); if (parentNodeDescriptor && !patchMap.has(parentNodeDescriptor)) { var parentNodeGetter = parentNodeDescriptor.get, configurable = parentNodeDescriptor.configurable; if (parentNodeGetter && configurable) { var patchedParentNodeDescriptor = _objectSpread(_objectSpread({}, parentNodeDescriptor), {}, { get: function get() { var parentNode = parentNodeGetter.call(this); if (parentNode instanceof Document) { var _getCurrentRunningApp; var proxy = (_getCurrentRunningApp = getCurrentRunningApp()) === null || _getCurrentRunningApp === void 0 ? void 0 : _getCurrentRunningApp.window; if (proxy) { return proxy.document; } } return parentNode; } }); Object.defineProperty(Node.prototype, 'parentNode', patchedParentNodeDescriptor); patchMap.set(parentNodeDescriptor, patchedParentNodeDescriptor); } } return function () { MutationObserver.prototype.observe = nativeMutationObserverObserveFn; patchMap.delete(nativeMutationObserverObserveFn); Node.prototype.compareDocumentPosition = prevCompareDocumentPosition; patchMap.delete(prevCompareDocumentPosition); if (parentNodeDescriptor) { Object.defineProperty(Node.prototype, 'parentNode', parentNodeDescriptor); patchMap.delete(parentNodeDescriptor); } }; } var docCreateElementFnBeforeOverwrite = docCreatePatchedMap.get(document.createElement); if (!docCreateElementFnBeforeOverwrite) { var rawDocumentCreateElement = document.createElement; Document.prototype.createElement = function createElement(tagName, options) { var element = rawDocumentCreateElement.call(this, tagName, options); if (isHijackingTag(tagName)) { var _ref = getCurrentRunningApp() || {}, currentRunningSandboxProxy = _ref.window; if (currentRunningSandboxProxy) { attachElementToProxy(element, currentRunningSandboxProxy); } } return element; }; // It means it have been overwritten while createElement is an own property of document if (document.hasOwnProperty('createElement')) { document.createElement = Document.prototype.createElement; } docCreatePatchedMap.set(Document.prototype.createElement, rawDocumentCreateElement); } return function unpatch() { if (docCreateElementFnBeforeOverwrite) { Document.prototype.createElement = docCreateElementFnBeforeOverwrite; document.createElement = docCreateElementFnBeforeOverwrite; } }; } export function patchStrictSandbox(appName, appWrapperGetter, sandbox) { 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 speedySandbox = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false; var proxy = sandbox.proxy; var containerConfig = proxyAttachContainerConfigMap.get(proxy); if (!containerConfig) { containerConfig = { appName: appName, proxy: proxy, appWrapperGetter: appWrapperGetter, dynamicStyleSheetElements: [], strictGlobal: true, speedySandbox: speedySandbox, excludeAssetFilter: excludeAssetFilter, scopedCSS: scopedCSS }; proxyAttachContainerConfigMap.set(proxy, containerConfig); } // all dynamic style sheets are stored in proxy container var _containerConfig = containerConfig, dynamicStyleSheetElements = _containerConfig.dynamicStyleSheetElements; var unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(function (element) { return elementAttachContainerConfigMap.has(element); }, function (element) { return elementAttachContainerConfigMap.get(element); }); var unpatchDocument = patchDocument({ sandbox: sandbox, speedy: speedySandbox }); if (!mounting) calcAppCount(appName, 'increase', 'bootstrapping'); if (mounting) calcAppCount(appName, 'increase', 'mounting'); return function free() { if (!mounting) calcAppCount(appName, 'decrease', 'bootstrapping'); if (mounting) calcAppCount(appName, 'decrease', 'mounting'); // release the overwritten prototype after all the micro apps unmounted if (isAllAppsUnmounted()) { unpatchDynamicAppendPrototypeFunctions(); unpatchDocument(); } 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)) { var mountDom = stylesheetElement[styleElementTargetSymbol] === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; var refNo = stylesheetElement[styleElementRefNodeNo]; if (typeof refNo === 'number' && refNo !== -1) { // the reference node may be dynamic script comment which is not rebuilt while remounting thus reference node no longer exists var refNode = mountDom.childNodes[refNo] || null; rawHeadInsertBefore.call(mountDom, stylesheetElement, refNode); return true; } else { rawHeadAppendChild.call(mountDom, stylesheetElement); return true; } } return false; }); }; }; }