UNPKG

@hsui/micro-app

Version:

Hundsun micro-app framework

289 lines (278 loc) 13.9 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"; var _excluded = ["mount"]; import { noop } from 'lodash'; import { getAppNames, getAppStatus, mountRootParcel, registerApplication, start as startSingleSpa } from 'single-spa'; import { createLogger } from '@hsui/logger'; import { loadApp } from './loader'; import { Deferred, getContainer, getXPathForElement, toArray } from './utils'; import { frameworkConfiguration } from './config'; import { doPrefetchStrategy, prefetchImmediately as prefetchApps } from './prefetch'; frameworkConfiguration.logger = createLogger(); frameworkConfiguration.logger.setLevel('info'); var microApps = []; var started = false; var defaultUrlRerouteOnly = true; var frameworkStartedDefer = new Deferred(); export function registerMicroApps(apps, lifeCycles) { // Each app only needs to be registered once var unregisteredApps = apps.filter(function (app) { return !microApps.some(function (registeredApp) { return registeredApp.name === app.name; }); }); microApps = [].concat(_toConsumableArray(microApps), _toConsumableArray(unregisteredApps)); unregisteredApps.forEach(function (_app) { var name = _app.name, base = _app.base, activeWhen = _app.activeWhen, props = _app.props, _app$loader = _app.loader, loader = _app$loader === void 0 ? noop : _app$loader; // 兼容 activeWhen 为 "/layout/#/foo, /layout/#/bar" 的场景 if (activeWhen && typeof activeWhen === 'string' && activeWhen.includes(',')) { activeWhen = activeWhen.split(','); } registerApplication({ name: name, app: function () { var _app2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() { var _yield$loadApp, mount, otherMicroAppConfigs; return _regeneratorRuntime().wrap(function _callee3$(_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: loader(true); _context3.next = 3; return frameworkStartedDefer.promise; case 3: _context3.next = 5; return loadApp(_app, frameworkConfiguration, lifeCycles); case 5: _context3.t0 = _context3.sent; _yield$loadApp = (0, _context3.t0)(); mount = _yield$loadApp.mount; otherMicroAppConfigs = _objectWithoutProperties(_yield$loadApp, _excluded); return _context3.abrupt("return", _objectSpread({ mount: [/*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() { return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", loader(true)); case 1: case "end": return _context.stop(); } }, _callee); }))].concat(_toConsumableArray(toArray(mount)), [/*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() { return _regeneratorRuntime().wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt("return", loader(false)); case 1: case "end": return _context2.stop(); } }, _callee2); }))]) }, otherMicroAppConfigs)); case 10: case "end": return _context3.stop(); } }, _callee3); })); function app() { return _app2.apply(this, arguments); } return app; }(), // 换成 base 可能更好理解一点儿,便于推广 // 但会牺牲一些 single-spa@5 里面一些好用的功能,比如多个 prefix activeWhen: activeWhen || base, customProps: props }); }); } // 手动加载微应用 var appConfigPromiseGetterMap = new Map(); export function loadMicroApp(app, lifeCycles) { var props = app.props, name = app.name; frameworkConfiguration.logger.debug('手动加载微应用', app); var getContainerXpath = function getContainerXpath(container) { var containerElement = getContainer(container); if (containerElement) { return getXPathForElement(containerElement, document); } return undefined; }; var wrapParcelConfigForRemount = function wrapParcelConfigForRemount(config) { return _objectSpread(_objectSpread({}, config), {}, { // empty bootstrap hook which should not run twice while it calling from cached micro app bootstrap: function bootstrap() { return Promise.resolve(); } }); }; /** * using name + container xpath as the micro app instance id, * it means if you rendering a micro app to a dom which have been rendered before, * the micro app would not load and evaluate its lifecycles again */ var memorizedLoadingFn = /*#__PURE__*/function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4() { var userConfiguration, container, xpath, parcelConfigGetterPromise, parcelConfigObjectGetterPromise, _xpath; return _regeneratorRuntime().wrap(function _callee4$(_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: userConfiguration = _objectSpread(_objectSpread({}, frameworkConfiguration), {}, { singular: false }); // const { $$cacheLifecycleByAppName } = userConfiguration; container = 'container' in app ? app.container : undefined; if (!container) { _context4.next = 13; break; } // using appName as cache for internal experimental scenario // if ($$cacheLifecycleByAppName) { // const parcelConfigGetterPromise = appConfigPromiseGetterMap.get(name); // if (parcelConfigGetterPromise) return wrapParcelConfigForRemount((await parcelConfigGetterPromise)(container)); // } xpath = getContainerXpath(container); if (!xpath) { _context4.next = 13; break; } parcelConfigGetterPromise = appConfigPromiseGetterMap.get("".concat(name, "-").concat(xpath)); if (!parcelConfigGetterPromise) { _context4.next = 13; break; } _context4.t0 = wrapParcelConfigForRemount; _context4.next = 10; return parcelConfigGetterPromise; case 10: _context4.t1 = _context4.sent; _context4.t2 = (0, _context4.t1)(container); return _context4.abrupt("return", (0, _context4.t0)(_context4.t2)); case 13: parcelConfigObjectGetterPromise = loadApp(app, userConfiguration, lifeCycles); if (container) { // if ($$cacheLifecycleByAppName) { // appConfigPromiseGetterMap.set(name, parcelConfigObjectGetterPromise); // } else { _xpath = getContainerXpath(container); if (_xpath) appConfigPromiseGetterMap.set("".concat(name, "-").concat(_xpath), parcelConfigObjectGetterPromise); // } } _context4.next = 17; return parcelConfigObjectGetterPromise; case 17: _context4.t3 = _context4.sent; return _context4.abrupt("return", (0, _context4.t3)(container)); case 19: case "end": return _context4.stop(); } }, _callee4); })); return function memorizedLoadingFn() { return _ref3.apply(this, arguments); }; }(); if (!started) { var _frameworkConfigurati; // We need to invoke start method of single-spa as the popstate event should be dispatched while the main app calling pushState/replaceState automatically, // but in single-spa it will check the start status before it dispatch popstate // see https://github.com/single-spa/single-spa/blob/f28b5963be1484583a072c8145ac0b5a28d91235/src/navigation/navigation-events.js#L101 // ref https://github.com/umijs/qiankun/pull/1071 startSingleSpa({ urlRerouteOnly: (_frameworkConfigurati = frameworkConfiguration.urlRerouteOnly) !== null && _frameworkConfigurati !== void 0 ? _frameworkConfigurati : defaultUrlRerouteOnly }); } return mountRootParcel(memorizedLoadingFn, _objectSpread({ domElement: document.createElement('div') }, props)); } /** * @description 启动 * @property singular - 可选, 是否为单实例场景, 单实例指的是同一时间只会渲染一个微应用, 默认为 true * @property strictGlobal - 可选, 是否走 with 闭包, 这会对性能造成影响, 不走的话要结合 CLI 的构建能力, 默认为 true * @property prefetch - 可选, 执行预加载策略, 默认为 false * @todo frameworkConfiguration * 默认 strictGlobal 配置为 true, 基于 CLI 的构建能力允许关闭 * 可以作为 proxySandbox 沙盒性能问题的临时的解决方案 */ export function start() { var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; for (var key in opts) { if (Object.hasOwnProperty.call(opts, key)) { frameworkConfiguration[key] = opts[key]; } } // 在 $$syncOnly 模式下,默认不走 with 闭包,兼容原有的解决方案 if (frameworkConfiguration.$$syncOnly) { frameworkConfiguration.logger.warn('$$syncOnly will be removed in next major version'); frameworkConfiguration.strictGlobal = false; } var prefetch = frameworkConfiguration.prefetch, singular = frameworkConfiguration.singular, strictGlobal = frameworkConfiguration.strictGlobal, _frameworkConfigurati2 = frameworkConfiguration.urlRerouteOnly, urlRerouteOnly = _frameworkConfigurati2 === void 0 ? defaultUrlRerouteOnly : _frameworkConfigurati2; if (prefetch) { doPrefetchStrategy(microApps, prefetch); } if (!window.Proxy) { frameworkConfiguration.logger.warn('Miss window.Proxy, proxySandbox will degenerate into snapshotSandbox'); if (!singular) { frameworkConfiguration.logger.warn('Setting singular as false may cause unexpected behavior while your browser not support window.Proxy'); } } if (!frameworkConfiguration.$$syncOnly && !strictGlobal && process.env.NODE_ENV === 'development') { frameworkConfiguration.logger.warn('Disabling with closure requires HUI CLI > 1.6.2 and HUI Bundler > 1.10.6'); } startSingleSpa({ urlRerouteOnly: urlRerouteOnly }); started = true; frameworkConfiguration.logger.info('微前端框架初始化应用配置选项', microApps); frameworkConfiguration.logger.info('当前注册的微应用集合', getAppNames()); frameworkStartedDefer.resolve(); } // 部署 single-spa 事件监听 window.addEventListener('single-spa:app-change', function (evt) { var _evt$detail, _evt$detail$appsByNew, _evt$detail2, _evt$detail2$appsByNe, _evt$detail3, _evt$detail3$appsByNe; frameworkConfiguration.logger.debug('微前端框架执行应用调度'); frameworkConfiguration.logger.debug('当前处于激活状态的应用', (_evt$detail = evt.detail) === null || _evt$detail === void 0 ? void 0 : (_evt$detail$appsByNew = _evt$detail.appsByNewStatus) === null || _evt$detail$appsByNew === void 0 ? void 0 : _evt$detail$appsByNew.MOUNTED); ((_evt$detail2 = evt.detail) === null || _evt$detail2 === void 0 ? void 0 : (_evt$detail2$appsByNe = _evt$detail2.appsByNewStatus) === null || _evt$detail2$appsByNe === void 0 ? void 0 : _evt$detail2$appsByNe.LOAD_ERROR) && frameworkConfiguration.logger.debug('加载失败的应用', (_evt$detail3 = evt.detail) === null || _evt$detail3 === void 0 ? void 0 : (_evt$detail3$appsByNe = _evt$detail3.appsByNewStatus) === null || _evt$detail3$appsByNe === void 0 ? void 0 : _evt$detail3$appsByNe.LOAD_ERROR); var statusMap = ['NOT_BOOTSTRAPPED', 'BOOTSTRAPPING', 'NOT_MOUNTED', 'MOUNTING', 'UNMOUNTING', 'UNLOADING']; var LoadedApps = microApps.filter(function (app) { var appStatus = getAppStatus(app.name); return statusMap.indexOf(appStatus) !== -1; }).map(function (item) { return item.name; }); frameworkConfiguration.logger.debug('加载完成的应用', LoadedApps); }); // 手动预加载 export { prefetchApps }; // 全局的状态管理 export { initGlobalState } from './store'; // 工具方法 https://single-spa.js.org/docs/api export { checkActivityFunctions, getAppNames, getAppStatus, getMountedApps } from 'single-spa'; // 错误处理 https://single-spa.js.org/docs/api export { addErrorHandler, removeErrorHandler } from 'single-spa'; // 部署全局的未捕获异常处理 export function addGlobalUncaughtErrorHandler(errorHandler) { window.addEventListener('error', errorHandler); window.addEventListener('unhandledrejection', errorHandler); } // 移除全局的未捕获异常处理 export function removeGlobalUncaughtErrorHandler(errorHandler) { window.removeEventListener('error', errorHandler); window.removeEventListener('unhandledrejection', errorHandler); }