UNPKG

@j2inn/app

Version:

J2 Innovations core application framework

146 lines (145 loc) 5.21 kB
/* * Copyright (c) 2021, J2 Innovations. All Rights Reserved */ import { HNamespace } from 'haystack-core'; import { ResourceElement } from './ResourceElement'; /** * Loads the default exported module from some JS with the defined scope and module. * * @param scope The scope of the module to load. * @param module The name of the module to load. * @returns The loaded component. */ export function loadDefault(scope, module) { return async () => { const container = globalThis.FIN_REMOTES?.[scope]; if (!container) { throw new Error(`Remote container with scope '${scope}' has not been loaded and initialized.`); } const factory = (await container.get(module)); return factory(); }; } /** * Asynchronously load the dynamic resource at runtime and wait for it to load. * * @param url The URL to load. * @returns A promise that's resolved once the resource has loaded. */ export async function loadDynamicResource(scope, url) { globalThis.FIN_RESOURCE_PROMISE_CACHE ??= {}; let promise = globalThis.FIN_RESOURCE_PROMISE_CACHE[url]; // return the existing promise if the resource is already available or loading. if (promise) { return promise; } if (isRemoteEntry(url)) { promise = loadRemoteEntry(scope, url); } else { promise = loadRemoteResource(url); } // Cache the promise to avoid loading the same resource multiple times. globalThis.FIN_RESOURCE_PROMISE_CACHE[url] = promise; return promise; } async function loadRemoteEntry(scope, url) { // Initializes the share scope. await __webpack_init_sharing__('default'); if (globalThis.FIN_REMOTES?.[scope]) { return globalThis.FIN_REMOTES[scope]; } // Dynamically import the container, ignore webpack processing as the url is dynamic. const container = await import( /* webpackIgnore: true */ url); //Initialize the container. await container.init(__webpack_share_scopes__.default); // Cache the remote container to avoid loading it multiple times. globalThis.FIN_REMOTES ??= {}; globalThis.FIN_REMOTES[scope] = container; return container; } function loadRemoteResource(url) { return new Promise((resolve, reject) => { let element; if (isCss(url)) { element = document.createElement('link'); element.href = url; element.rel = 'stylesheet'; } else { element = document.createElement('script'); element.src = url; element.type = 'text/javascript'; element.async = true; } const resource = new ResourceElement(element); document.head.appendChild(element); const loadHandler = () => { resource.removeListeners(loadHandler, loadErrorHandler); resolve(resource); }; const loadErrorHandler = () => { resource.removeListeners(loadHandler, loadErrorHandler); reject(new Error('Resource loading failed')); }; resource.addListeners(loadHandler, loadErrorHandler); }); } export async function loadDynamicResources(scope, urls) { return Promise.all(urls.map((url) => loadDynamicResource(scope, url))); } function isRemoteEntry(url) { return url.endsWith('remoteEntry.js'); } function isCss(url) { return url.toLowerCase().endsWith('.css'); } /** * Returns the pod name. * * @param def The def dict. * @returns The name of the pod the resource was loaded from. */ export function getPodName(def) { return HNamespace.getFeatureName(def.get('lib') ?? ''); } /** * Returns the remote entry point for an app. * * @param app The FIN app to get the remote entry point from. * @param ui The FIN app view to load the remote entry point from. * @param path The remote path to load. Defaults to `remoteEntry.js`. * @returns The remote entry point. */ export function getRemoteEntry(app, ui, path = 'remoteEntry.js') { const appName = HNamespace.getFeatureName(app.defName); // Find the POD file the resource needs to be loaded from. const podName = HNamespace.getFeatureName((ui ?? app).get('lib') ?? ''); // If the application is running in debug mode attempt to load the remote entry from the debug remote url. const isDebugMode = typeof DEBUG === 'boolean' && DEBUG; if (isDebugMode) { const remote = globalThis.DEBUG_REMOTES?.[podName] ?? DEBUG_REMOTES?.[podName]; if (remote) { return `${remote}/${path}`; } } return `/api/apps/${appName}/files/${podName}/dist/${path}`; } /** * Returns properties with values that can be rendered nicely as strings. * All objects and functions are excluded. All keys are made lowercase. * * @param props The properties to convert. * @returns The properties as strings. */ export function toStringProps(props) { return Object.keys(props).reduce((obj, key) => { const value = props[key]; const type = typeof value; if (type === 'string' || type === 'boolean' || type === 'number') { obj[key.toLowerCase()] = String(value); } return obj; }, {}); }