/* eslint-disable no-console, no-return-assign, prefer-const, prefer-spread, prefer-rest-params */ /* eslint-disable max-statements */ /* eslint-env browser */ /* global globalThis */ /** * SubApp client side shell version 2 */ import { LoadSubAppOptions, SubAppContainer, SubAppCSRData, XarcSubAppClientV2, _xarcV2RunTimeInfo } from "../subapp/index"; // // all console.log and console.debug in this file will be optimized out for production // Do not use xarcV2.debug in this file. // /** * xarc subapp client side lib version 2 * load into window.xarcV2 as a global * * @param w window object * @returns xarc v2 client */ export function xarcV2Client( w: Window & typeof globalThis & { _wml?: any; xarcV2?: XarcSubAppClientV2; _subapps?: SubAppContainer; } ) { if (!w._wml) { w._wml = {}; } let debugLog = () => { // }; const MAX_CALL_DEPTH = 15; const version = 2000000; // ###.###.### major.minor.patch console.log("xarcV2 version", version); console.debug("xarcV2 debug log", (debugLog = console.debug.bind(console))); if (w.xarcV2 && w.xarcV2.version >= version) return w.xarcV2; const runtimeInfo: _xarcV2RunTimeInfo = { instId: 1, subApps: {}, onLoadStart: {}, started: false, md: {} }; let xv2: XarcSubAppClientV2; return (w.xarcV2 = xv2 = { IS_BROWSER: true, HAS_WINDOW: typeof window !== "undefined", version, rt: runtimeInfo, // // empty place holders for CDN mapping // // eslint-disable-next-line cdnInit(_: any) { // }, // eslint-disable-next-line cdnUpdate(_: any) { // }, cdnMap(x) { return x; }, getOnLoadStart(name) { if (!runtimeInfo.onLoadStart[name]) runtimeInfo.onLoadStart[name] = []; return runtimeInfo.onLoadStart[name]; }, addOnLoadStart(name, load) { xv2.getOnLoadStart(name).push(load); }, // // When there are subapps that need to load and start up front, SSR could // be tricky if any of them is inlined within other components, because when // UI render the component tree, any kind of async op (such as data prep) // can't occur in the middle of the render and must return a fallback while // async is loading, and that would cause SSR content to be replaced with the // fallback, producing a flash, nullifying the SSR. // To remedy this, a single data prep action would be waiting for subapps // to load, all subapp that's starting onLoad will have to wait for the data prep // action. // // watchSubAppOnLoad(immediate) {}, startSubAppOnLoad(options: LoadSubAppOptions, data: any) { const { name } = options; const ols = runtimeInfo.onLoadStart; if (!ols[name]) { ols[name] = []; } ols[name].push(Object.assign({}, options, data)); }, _start(ignore: string[], callDepth) { runtimeInfo.started = true; const promises = []; const onLoadStart = runtimeInfo.onLoadStart; const subapps = w._subapps; const subappsBeforeLoad = subapps.getNames(); const beginTs = Date.now(); for (const name in onLoadStart) { const subapp = subapps.get(name); if (!subapp) { console.debug("onload start subapp not yet registered:", name); } else if (!subapp._module) { promises.push(subapp._getModule()); } } if (ignore.length > 0) { // some previously loaded subapps (in ignore) cause more subapps to be declared for (const name of subapps.getNames()) { const subapp = subapps.get(name); if (ignore.indexOf(name) < 0 && !subapp._module && subapp._ssr) { console.debug(" -> loading module of new SSR subapp declared", name, subapp._ssr); promises.push(subapp._getModule()); } } } return Promise.all(promises) .catch(err => { console.error( "subapp onStart load modules failed. Continue anyways, but things may be broken.", err ); }) .then(mods => { console.debug("subapp onStart loaded modules. msec taken:", Date.now() - beginTs, mods); // Some modules declared more subapps, load them first, but ignore what we just loaded. if (subapps.declareCount > subappsBeforeLoad.length) { if (callDepth < MAX_CALL_DEPTH) { console.debug("new subapps declared => loading them"); return this._start(subappsBeforeLoad, callDepth + 1); } else { console.error("subapp _start callDepth too deep, giving up:", callDepth); } } const doc: Document = w.document; for (const name in onLoadStart) { onLoadStart[name].forEach((startOpts: SubAppCSRData) => { const element = doc.getElementById(startOpts.elementId); console.debug(name, "starting subapp into", element); subapps.get(name)._start({ csrData: Object.assign({}, startOpts, { element }) }); }); } return mods; }); }, start() { if (!w._subapps) { console.error("No subapps registered, nothing to start."); return Promise.resolve(); } return this._start([], 0); }, dyn(id) { const msg = "ERROR: fail retrieve dynamic data from element"; const element = document.getElementById(id); if (!element) { console.error(msg, id, "- get"); return {}; } else { try { return JSON.parse(element.innerHTML.replace(/<(\/?)script>/g, "<$1script>")); } catch (err) { console.error(msg, id, "- parse"); return {}; } } }, get debug() { return debugLog; } }); }