/* eslint-env browser */ /* eslint-disable no-console, @typescript-eslint/ban-ts-comment */ /* global window */ import { __declareSubApp, envHooks, SubAppContainer, SubAppOptions, SubAppDef, SubAppMountInfo } from "../subapp/index"; import { ClientRenderPipeline } from "./client-render-pipeline"; import { xarcV2 } from "./xarcv2"; // // re-exports // export * from "../subapp/index"; export { xarcV2 }; export { ClientRenderPipeline }; /** * Get the subapp container * * @returns subapp container */ export function getContainer(): SubAppContainer { const w: any = window; if (!w._subapps) { w._subapps = new SubAppContainer({}); } return w._subapps; } /** * Setup event hooks * */ export function _setupEnvHooks(): void { if (!envHooks.getContainer) { envHooks.getContainer = getContainer; } } const clientOverrideMethods: Partial = { _start({ csrData }) { xarcV2.debug("subapp _start - creating pipeline and calling start", this.name); if (csrData.inlineId) { const ix = this._renderPipelines.findIndex(p => p.csrData.inlineId === csrData.inlineId); if (ix >= 0) { xarcV2.debug("subapp _start - removing existing inline pipeline", this.name); this._renderPipelines.slice(ix, 1); } } const pipeline = this._pipelineFactory({ csrData }); this._renderPipelines.push(pipeline); return pipeline.start(); } }; const _dynamics = []; /** * Declare a subapp. * * **NOTE**: A webpack plugin will statically analyze the code to extract the subapp info * so options must be a literal object in place. It cannot be a variable declared elsewhere. * * GOOD: * * ```js * export const subapp1 = declareSubApp({ * name: "Subapp1", * getModule: () => import("subapp1") * }) * ``` * * BAD: * * ```js * const subappInfo = { name: "Subapp1", getModule: () => import("subapp1")}; * export const subapp1 = declareSubApp(subappInfo); * ``` * * @param options - options * @returns subapp definition * */ export function declareSubApp(options: SubAppOptions): SubAppDef { _setupEnvHooks(); const existDef = getContainer().get(options.name); const def = __declareSubApp(options, clientOverrideMethods); // transfer render pipelines from exist definition for hot reload support if (existDef) { def._renderPipelines = existDef._renderPipelines; } // // Preload module so it's available ASAP. This also ensure module is reloaded when // a module that HMR updated is re-declaring a subapp. // def._getModule(); // In production build, webpack will replace module.hot with false and the code will be optimized out // @ts-ignore if (module.hot) { def._mount = function (info: SubAppMountInfo) { // TODO: need to attach pipeline to component xarcV2.debug("subapp _mount for", info.subapp.name, info, info.component.constructor.name); const pipelines = this._renderPipelines; for (const pipeline of pipelines) { pipeline._mount(info); } // dynamic import components are just plain components without the subapp // rendering features so there are no pipelines for them so need to track // them here in order to reload them. if (info.type === "dynamic" && _dynamics.indexOf(info) < 0) { _dynamics.push(info); } }; def._unmount = function (info: SubAppMountInfo) { xarcV2.debug("subapp _unmount for", info.subapp.name, info, info.component.constructor.name); const pipelines = this._renderPipelines; for (const pipeline of pipelines) { pipeline._unmount(info); } const ix = _dynamics.indexOf(info); if (ix >= 0) { _dynamics.splice(ix, 1); } }; def._reload = function (subAppName: string, _modName?: string) { xarcV2.debug("subapp _reload", subAppName); // get the potentially new subapp definition const subapp = getContainer().get(subAppName); // transfer render pipelines over subapp._renderPipelines = this._renderPipelines; // reload its module and reload all pipelines return subapp ._getModule() .then(() => { const pipelines = this._renderPipelines; const promises = pipelines.map(pipeline => pipeline._reload(subAppName, _modName)); return Promise.all(promises); }) .then(() => { _dynamics.forEach(dyn => { if (dyn.subapp.name === subAppName) { dyn.component.reload(def._module); } }); }); }; } return def; } export const IS_BROWSER = true;