@hsui/micro-app
Version:
Hundsun micro-app framework
494 lines (488 loc) • 23.1 kB
JavaScript
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);
}