@j2inn/app
Version:
J2 Innovations core application framework
146 lines (145 loc) • 5.21 kB
JavaScript
/*
* 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;
}, {});
}