UNPKG

@hsui/micro-app

Version:

Hundsun micro-app framework

494 lines (488 loc) 23.1 kB
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime"; import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator"; var _excluded = ["singular", "strictGlobal", "excludeAssetFilter"]; import { importEntry } from 'import-html-entry'; import { snakeCase, concat, mergeWith } from 'lodash'; import { getMicroAppStateActions } from './store'; import { createSandboxContainer } from './sandbox'; import { Deferred, toArray, getContainer, validateExportLifecycle } from './utils'; import getAddOns from './addons'; export function getDefaultTplWrapper(id, name, appClass) { return function (tpl) { return "<div id=\"".concat(getWrapperId(id), "\" class=\"micro-app-wrapper").concat(appClass ? ' ' + appClass : '', "\" data-name=\"").concat(name, "\">").concat(tpl, "</div>"); }; } export function getWrapperId(id) { return "__micro_app_wrapper_for_".concat(snakeCase(id), "__"); } function assertElementExist(element, msg) { if (!element) { if (msg) { throw new Error(msg); } throw new Error('[hui] element not existed!'); } } function execHooksChain(hooks, app, global) { if (hooks.length) { return hooks.reduce(function (chain, hook) { return chain.then(function () { return hook(app, global); }); }, Promise.resolve()); } return Promise.resolve(); } function validateSingularMode(_x, _x2) { return _validateSingularMode.apply(this, arguments); } function _validateSingularMode() { _validateSingularMode = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(validate, app) { return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", typeof validate === 'function' ? validate(app) : !!validate); case 1: case "end": return _context.stop(); } }, _callee); })); return _validateSingularMode.apply(this, arguments); } function createElement(appContent) { var containerElement = document.createElement('div'); containerElement.innerHTML = appContent; // appContent always wrapped with a singular div var appElement = containerElement.firstChild; return appElement; } /** generate app wrapper dom getter */ function getAppWrapperGetter(appName, appInstanceId, elementGetter) { return function () { var element = elementGetter(); assertElementExist(element, "[hui] Wrapper element for ".concat(appName, " with instance ").concat(appInstanceId, " is not existed!")); return element; }; } var rawAppendChild = HTMLElement.prototype.appendChild; var rawRemoveChild = HTMLElement.prototype.removeChild; /** * Get the render function * @param appName */ function getRender(appName) { var render = function render(_ref, phase) { var element = _ref.element, container = _ref.container; var containerElement = getContainer(container); // The container might have be removed after micro app unmounted. // Such as the micro app unmount lifecycle called by a react componentWillUnmount lifecycle, after micro app unmounted, the react component might also be removed if (phase !== 'unmounted') { var errorMsg = function () { switch (phase) { case 'loading': case 'mounting': return "[hui] Target container with ".concat(container, " not existed while ").concat(appName, " ").concat(phase, "!"); case 'mounted': return "[hui] Target container with ".concat(container, " not existed after ").concat(appName, " ").concat(phase, "!"); default: return "[hui] Target container with ".concat(container, " not existed while ").concat(appName, " rendering!"); } }(); assertElementExist(containerElement, errorMsg); } if (containerElement && !containerElement.contains(element)) { // clear the container while (containerElement.firstChild) { rawRemoveChild.call(containerElement, containerElement.firstChild); } // append the element to container if it exist if (element) { rawAppendChild.call(containerElement, element); } } return undefined; }; return render; } function getLifecyclesFromExports(scriptExports, appName, global, globalLatestSetProp) { if (validateExportLifecycle(scriptExports)) { return scriptExports; } // fallback to sandbox latest set property if it had if (globalLatestSetProp) { var lifecycles = global[globalLatestSetProp]; if (validateExportLifecycle(lifecycles)) { return lifecycles; } } if (process.env.NODE_ENV === 'development') { console.warn("[qiankun] lifecycle not found from ".concat(appName, " entry exports, fallback to get from window['").concat(appName, "']")); } // fallback to global variable who named with ${appName} while module exports not found var globalVariableExports = global[appName]; if (validateExportLifecycle(globalVariableExports)) { return globalVariableExports; } throw new Error("[hui] You need to export lifecycle functions in ".concat(appName, " entry")); } var prevAppUnmountedDeferred; export function loadApp(_x3, _x4) { return _loadApp.apply(this, arguments); } function _loadApp() { _loadApp = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14(app, configuration) { var _sandboxContainer, _sandboxContainer$ins; var lifeCycles, entry, appName, appClass, appInstanceId, singular, strictGlobal, excludeAssetFilter, importEntryOpts, _yield$importEntry, template, execScripts, assetPublicPath, appContent, sandbox, useLooseSandbox, strictStyleIsolation, scopedCSS, initialAppWrapperElement, initialContainer, render, initialAppWrapperGetter, global, mountSandbox, unmountSandbox, sandboxContainer, _mergeWith, _mergeWith$beforeUnmo, beforeUnmount, _mergeWith$afterUnmou, afterUnmount, _mergeWith$afterMount, afterMount, _mergeWith$beforeMoun, beforeMount, _mergeWith$beforeLoad, beforeLoad, scriptExports, _getLifecyclesFromExp, bootstrap, mount, unmount, update, _getMicroAppStateActi, onGlobalStateChange, setGlobalState, offGlobalStateChange, syncAppWrapperElement2Sandbox, parcelConfigGetter, _args14 = arguments; return _regeneratorRuntime().wrap(function _callee14$(_context14) { while (1) switch (_context14.prev = _context14.next) { case 0: lifeCycles = _args14.length > 2 && _args14[2] !== undefined ? _args14[2] : {}; entry = app.entry, appName = app.name, appClass = app.appClass; appInstanceId = "".concat(appName, "_").concat(+new Date(), "_").concat(Math.floor(Math.random() * 1000)); singular = configuration.singular, strictGlobal = configuration.strictGlobal, excludeAssetFilter = configuration.excludeAssetFilter, importEntryOpts = _objectWithoutProperties(configuration, _excluded); // get the entry html content and script executor _context14.next = 6; return importEntry(entry, importEntryOpts); case 6: _yield$importEntry = _context14.sent; template = _yield$importEntry.template; execScripts = _yield$importEntry.execScripts; assetPublicPath = _yield$importEntry.assetPublicPath; _context14.next = 12; return validateSingularMode(singular, app); case 12: if (!_context14.sent) { _context14.next = 15; break; } _context14.next = 15; return prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise; case 15: appContent = getDefaultTplWrapper(appInstanceId, appName, appClass)(template); // 目前 qiankun 默认运行的沙盒 proxySandbox 对应到 import-html-entry 里面是基于 with 语句执行子系统脚本的,目前会有性能问题 sandbox = true, useLooseSandbox = configuration.$$syncOnly ? true : false, strictStyleIsolation = false, scopedCSS = false; initialAppWrapperElement = createElement(appContent); initialContainer = 'container' in app ? app.container : undefined; render = getRender(appName); // 第一次加载设置应用可见区域 dom 结构 // 确保每次应用加载前容器 dom 结构已经设置完毕 render({ element: initialAppWrapperElement, container: initialContainer }, 'loading'); initialAppWrapperGetter = getAppWrapperGetter(appName, appInstanceId, function () { return initialAppWrapperElement; }); global = window; mountSandbox = function mountSandbox() { return Promise.resolve(); }; unmountSandbox = function unmountSandbox() { return Promise.resolve(); }; if (sandbox) { sandboxContainer = createSandboxContainer(appName, // FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518 initialAppWrapperGetter, scopedCSS, useLooseSandbox, excludeAssetFilter); // 用沙箱的代理对象作为接下来使用的全局对象 global = sandboxContainer.instance.proxy; mountSandbox = sandboxContainer.mount; unmountSandbox = sandboxContainer.unmount; } _mergeWith = mergeWith({}, getAddOns(global, assetPublicPath), lifeCycles, function (v1, v2) { return concat(v1 !== null && v1 !== void 0 ? v1 : [], v2 !== null && v2 !== void 0 ? v2 : []); }), _mergeWith$beforeUnmo = _mergeWith.beforeUnmount, beforeUnmount = _mergeWith$beforeUnmo === void 0 ? [] : _mergeWith$beforeUnmo, _mergeWith$afterUnmou = _mergeWith.afterUnmount, afterUnmount = _mergeWith$afterUnmou === void 0 ? [] : _mergeWith$afterUnmou, _mergeWith$afterMount = _mergeWith.afterMount, afterMount = _mergeWith$afterMount === void 0 ? [] : _mergeWith$afterMount, _mergeWith$beforeMoun = _mergeWith.beforeMount, beforeMount = _mergeWith$beforeMoun === void 0 ? [] : _mergeWith$beforeMoun, _mergeWith$beforeLoad = _mergeWith.beforeLoad, beforeLoad = _mergeWith$beforeLoad === void 0 ? [] : _mergeWith$beforeLoad; _context14.next = 29; return execHooksChain(toArray(beforeLoad), app, global); case 29: _context14.next = 31; return execScripts(global, strictGlobal); case 31: scriptExports = _context14.sent; _getLifecyclesFromExp = getLifecyclesFromExports(scriptExports, appName, global, (_sandboxContainer = sandboxContainer) === null || _sandboxContainer === void 0 ? void 0 : (_sandboxContainer$ins = _sandboxContainer.instance) === null || _sandboxContainer$ins === void 0 ? void 0 : _sandboxContainer$ins.latestSetProp), bootstrap = _getLifecyclesFromExp.bootstrap, mount = _getLifecyclesFromExp.mount, unmount = _getLifecyclesFromExp.unmount, update = _getLifecyclesFromExp.update; // 状态管理 _getMicroAppStateActi = getMicroAppStateActions(appInstanceId), onGlobalStateChange = _getMicroAppStateActi.onGlobalStateChange, setGlobalState = _getMicroAppStateActi.setGlobalState, offGlobalStateChange = _getMicroAppStateActi.offGlobalStateChange; // FIXME temporary way syncAppWrapperElement2Sandbox = function syncAppWrapperElement2Sandbox(element) { return initialAppWrapperElement = element; }; parcelConfigGetter = function parcelConfigGetter() { var remountContainer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialContainer; var appWrapperElement = initialAppWrapperElement; var appWrapperGetter = getAppWrapperGetter(appName, appInstanceId, function () { return appWrapperElement; }); var parcelConfig = { name: appInstanceId, bootstrap: bootstrap, mount: [/*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() { return _regeneratorRuntime().wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return validateSingularMode(singular, app); case 2: _context2.t0 = _context2.sent; if (!_context2.t0) { _context2.next = 5; break; } _context2.t0 = prevAppUnmountedDeferred; case 5: if (!_context2.t0) { _context2.next = 7; break; } return _context2.abrupt("return", prevAppUnmountedDeferred.promise); case 7: return _context2.abrupt("return", undefined); case 8: case "end": return _context2.stop(); } }, _callee2); })), /*#__PURE__*/ // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕 _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() { var useNewContainer; return _regeneratorRuntime().wrap(function _callee3$(_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: useNewContainer = remountContainer !== initialContainer; if (useNewContainer || !appWrapperElement) { // element will be destroyed after unmounted, we need to recreate it if it not exist // or we try to remount into a new container appWrapperElement = createElement(appContent); syncAppWrapperElement2Sandbox(appWrapperElement); } render({ element: appWrapperElement, container: remountContainer }, 'mounting'); case 3: case "end": return _context3.stop(); } }, _callee3); })), mountSandbox, /*#__PURE__*/ // exec the chain after rendering to keep the behavior with beforeLoad _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4() { return _regeneratorRuntime().wrap(function _callee4$(_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: return _context4.abrupt("return", execHooksChain(toArray(beforeMount), app, global)); case 1: case "end": return _context4.stop(); } }, _callee4); })), /*#__PURE__*/function () { var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(props) { return _regeneratorRuntime().wrap(function _callee5$(_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: return _context5.abrupt("return", mount(_objectSpread(_objectSpread({}, props), {}, { container: appWrapperGetter(), setGlobalState: setGlobalState, onGlobalStateChange: onGlobalStateChange }))); case 1: case "end": return _context5.stop(); } }, _callee5); })); return function (_x5) { return _ref5.apply(this, arguments); }; }(), /*#__PURE__*/ // finish loading after app mounted _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6() { return _regeneratorRuntime().wrap(function _callee6$(_context6) { while (1) switch (_context6.prev = _context6.next) { case 0: return _context6.abrupt("return", render({ element: appWrapperElement, container: remountContainer }, 'mounted')); case 1: case "end": return _context6.stop(); } }, _callee6); })), /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7() { return _regeneratorRuntime().wrap(function _callee7$(_context7) { while (1) switch (_context7.prev = _context7.next) { case 0: return _context7.abrupt("return", execHooksChain(toArray(afterMount), app, global)); case 1: case "end": return _context7.stop(); } }, _callee7); })), /*#__PURE__*/ // initialize the unmount defer after app mounted and resolve the defer after it unmounted _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee8() { return _regeneratorRuntime().wrap(function _callee8$(_context8) { while (1) switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return validateSingularMode(singular, app); case 2: if (!_context8.sent) { _context8.next = 4; break; } prevAppUnmountedDeferred = new Deferred(); case 4: case "end": return _context8.stop(); } }, _callee8); }))], unmount: [/*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee9() { return _regeneratorRuntime().wrap(function _callee9$(_context9) { while (1) switch (_context9.prev = _context9.next) { case 0: return _context9.abrupt("return", execHooksChain(toArray(beforeUnmount), app, global)); case 1: case "end": return _context9.stop(); } }, _callee9); })), /*#__PURE__*/function () { var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee10(props) { return _regeneratorRuntime().wrap(function _callee10$(_context10) { while (1) switch (_context10.prev = _context10.next) { case 0: return _context10.abrupt("return", unmount(_objectSpread(_objectSpread({}, props), {}, { container: appWrapperGetter() }))); case 1: case "end": return _context10.stop(); } }, _callee10); })); return function (_x6) { return _ref10.apply(this, arguments); }; }(), unmountSandbox, /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11() { return _regeneratorRuntime().wrap(function _callee11$(_context11) { while (1) switch (_context11.prev = _context11.next) { case 0: return _context11.abrupt("return", execHooksChain(toArray(afterUnmount), app, global)); case 1: case "end": return _context11.stop(); } }, _callee11); })), /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee12() { return _regeneratorRuntime().wrap(function _callee12$(_context12) { while (1) switch (_context12.prev = _context12.next) { case 0: render({ element: null, container: remountContainer }, 'unmounted'); offGlobalStateChange(appInstanceId); // for gc appWrapperElement = null; syncAppWrapperElement2Sandbox(appWrapperElement); case 4: case "end": return _context12.stop(); } }, _callee12); })), /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13() { return _regeneratorRuntime().wrap(function _callee13$(_context13) { while (1) switch (_context13.prev = _context13.next) { case 0: _context13.next = 2; return validateSingularMode(singular, app); case 2: _context13.t0 = _context13.sent; if (!_context13.t0) { _context13.next = 5; break; } _context13.t0 = prevAppUnmountedDeferred; case 5: if (!_context13.t0) { _context13.next = 7; break; } prevAppUnmountedDeferred.resolve(); case 7: case "end": return _context13.stop(); } }, _callee13); }))] }; if (typeof update === 'function') { parcelConfig.update = update; } return parcelConfig; }; return _context14.abrupt("return", parcelConfigGetter); case 37: case "end": return _context14.stop(); } }, _callee14); })); return _loadApp.apply(this, arguments); }