@memberjunction/react-runtime
Version:
Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, and execution capabilities for React components in any JavaScript environment.
476 lines • 21.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LibraryLoader = void 0;
const standard_libraries_1 = require("./standard-libraries");
const core_libraries_1 = require("./core-libraries");
const resource_manager_1 = require("./resource-manager");
const library_dependency_resolver_1 = require("./library-dependency-resolver");
const LIBRARY_LOADER_COMPONENT_ID = 'mj-react-runtime-library-loader-singleton';
class LibraryLoader {
static async loadAllLibraries(config, additionalLibraries, options) {
if (config) {
standard_libraries_1.StandardLibraryManager.setConfiguration(config);
}
if (additionalLibraries && additionalLibraries.length > 0) {
const currentConfig = standard_libraries_1.StandardLibraryManager.getConfiguration();
const mergedConfig = {
libraries: [...currentConfig.libraries, ...additionalLibraries],
metadata: {
...currentConfig.metadata,
lastUpdated: new Date().toISOString()
}
};
standard_libraries_1.StandardLibraryManager.setConfiguration(mergedConfig);
}
return this.loadLibrariesFromConfig(undefined, options?.debug);
}
static async loadLibrariesFromConfig(options, debug) {
const coreLibraries = (0, core_libraries_1.getCoreRuntimeLibraries)(debug);
const corePromises = coreLibraries.map(lib => this.loadScript(lib.cdnUrl, lib.globalVariable, debug));
const coreResults = await Promise.all(corePromises);
const React = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'React');
const ReactDOM = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'ReactDOM');
const Babel = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'Babel');
if (typeof window !== 'undefined') {
if (React && !window.React) {
window.React = React;
console.log('✓ Exposed React as window.React for UMD compatibility');
}
if (ReactDOM && !window.ReactDOM) {
window.ReactDOM = ReactDOM;
console.log('✓ Exposed ReactDOM as window.ReactDOM for UMD compatibility');
}
if (!window.PropTypes) {
window.PropTypes = {};
console.log('✓ Exposed empty PropTypes as window.PropTypes for UMD compatibility');
}
}
const config = standard_libraries_1.StandardLibraryManager.getConfiguration();
const enabledLibraries = standard_libraries_1.StandardLibraryManager.getEnabledLibraries();
let pluginLibraries = enabledLibraries.filter(lib => !(0, core_libraries_1.isCoreRuntimeLibrary)(lib.id));
if (options) {
if (options.categories) {
pluginLibraries = pluginLibraries.filter(lib => options.categories.includes(lib.category));
}
if (options.excludeRuntimeOnly) {
pluginLibraries = pluginLibraries.filter(lib => !lib.isRuntimeOnly);
}
}
pluginLibraries.forEach(lib => {
if (lib.cdnCssUrl) {
this.loadCSS(lib.cdnCssUrl);
}
});
const pluginPromises = pluginLibraries.map(lib => this.loadScript(lib.cdnUrl, lib.globalVariable, debug));
const pluginResults = await Promise.all(pluginPromises);
const libraries = {};
pluginLibraries.forEach((lib, index) => {
libraries[lib.globalVariable] = pluginResults[index];
});
return {
React: React || window.React,
ReactDOM: ReactDOM || window.ReactDOM,
Babel: Babel || window.Babel,
libraries
};
}
static async loadLibraries(options) {
const { loadCore = true, loadUI = true, loadCSS = true, customLibraries = [] } = options;
const categoriesToLoad = ['runtime'];
if (loadCore) {
categoriesToLoad.push('utility', 'charting');
}
if (loadUI) {
categoriesToLoad.push('ui');
}
const result = await this.loadLibrariesFromConfig({
categories: categoriesToLoad
});
if (customLibraries.length > 0) {
const customPromises = customLibraries.map(({ url, globalName }) => this.loadScript(url, globalName));
const customResults = await Promise.all(customPromises);
customLibraries.forEach(({ globalName }, index) => {
result.libraries[globalName] = customResults[index];
});
}
return result;
}
static async loadScript(url, globalName, debug = false) {
const existing = this.loadedResources.get(url);
if (existing) {
if (debug) {
console.log(`✅ Library '${globalName}' already loaded (cached)`);
}
return existing.promise;
}
const promise = new Promise((resolve, reject) => {
const existingGlobal = window[globalName];
if (existingGlobal) {
if (debug) {
console.log(`✅ Library '${globalName}' already available globally`);
}
resolve(existingGlobal);
return;
}
const existingScript = document.querySelector(`script[src="${url}"]`);
if (existingScript) {
this.waitForScriptLoad(existingScript, globalName, resolve, reject);
return;
}
const script = document.createElement('script');
script.src = url;
script.async = true;
script.crossOrigin = 'anonymous';
const cleanup = () => {
script.removeEventListener('load', onLoad);
script.removeEventListener('error', onError);
};
const onLoad = async () => {
cleanup();
if (LibraryLoader.enableProgressiveDelay) {
try {
const global = await LibraryLoader.waitForGlobalVariable(globalName, url, debug);
resolve(global);
}
catch (error) {
reject(error);
}
}
else {
const global = window[globalName];
if (global) {
if (debug) {
console.log(`✅ Library '${globalName}' loaded successfully from ${url}`);
}
resolve(global);
}
else {
const timeoutId = resource_manager_1.resourceManager.setTimeout(LIBRARY_LOADER_COMPONENT_ID, () => {
const delayedGlobal = window[globalName];
if (delayedGlobal) {
if (debug) {
console.log(`✅ Library '${globalName}' loaded successfully (delayed initialization)`);
}
resolve(delayedGlobal);
}
else {
reject(new Error(`${globalName} not found after script load`));
}
}, 100, { url, globalName });
}
}
};
const onError = () => {
cleanup();
reject(new Error(`Failed to load script: ${url}`));
};
script.addEventListener('load', onLoad);
script.addEventListener('error', onError);
if (debug) {
console.log(`📦 Loading library '${globalName}' from ${url}...`);
}
document.head.appendChild(script);
resource_manager_1.resourceManager.registerDOMElement(LIBRARY_LOADER_COMPONENT_ID, script);
});
this.loadedResources.set(url, {
element: document.querySelector(`script[src="${url}"]`),
promise
});
return promise;
}
static isLibraryReady(globalVariable) {
if (!globalVariable) {
return false;
}
if (typeof globalVariable === 'function') {
return true;
}
if (typeof globalVariable === 'object') {
const keys = Object.keys(globalVariable);
return keys.length > 0 ||
Object.getOwnPropertyNames(globalVariable).length > 1 ||
globalVariable.constructor !== Object;
}
return true;
}
static async waitForGlobalVariable(globalName, url, debug = false) {
const delays = [0, 100, 200, 300, 400];
const maxAttempts = delays.length;
let totalDelay = 0;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (attempt > 0) {
const delay = delays[attempt];
if (debug) {
console.log(`⏳ Waiting ${delay}ms for ${globalName} to initialize (attempt ${attempt + 1}/${maxAttempts})...`);
}
await new Promise(resolve => {
resource_manager_1.resourceManager.setTimeout(LIBRARY_LOADER_COMPONENT_ID, () => resolve(undefined), delay, { globalName, attempt });
});
totalDelay += delay;
}
const global = window[globalName];
if (global) {
const isReady = this.isLibraryReady(global);
if (isReady) {
if (debug) {
if (totalDelay > 0) {
console.log(`✅ ${globalName} ready after ${totalDelay}ms delay`);
}
else {
console.log(`✅ Library '${globalName}' loaded successfully from ${url}`);
}
}
return global;
}
else if (debug && attempt < maxAttempts - 1) {
console.log(`🔄 ${globalName} exists but not fully initialized, will retry...`);
}
}
}
const finalGlobal = window[globalName];
if (finalGlobal) {
console.warn(`⚠️ ${globalName} loaded but may not be fully initialized after ${totalDelay}ms`);
return finalGlobal;
}
throw new Error(`${globalName} not found after script load and ${totalDelay}ms delay`);
}
static loadCSS(url) {
if (this.loadedResources.has(url)) {
return;
}
const existingLink = document.querySelector(`link[href="${url}"]`);
if (existingLink) {
return;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
resource_manager_1.resourceManager.registerDOMElement(LIBRARY_LOADER_COMPONENT_ID, link);
this.loadedResources.set(url, {
element: link,
promise: Promise.resolve()
});
}
static waitForScriptLoad(script, globalName, resolve, reject) {
const checkGlobal = () => {
const global = window[globalName];
if (global) {
resolve(global);
}
else {
resource_manager_1.resourceManager.setTimeout(LIBRARY_LOADER_COMPONENT_ID, () => {
const delayedGlobal = window[globalName];
if (delayedGlobal) {
resolve(delayedGlobal);
}
else {
reject(new Error(`${globalName} not found after script load`));
}
}, 100, { context: 'waitForScriptLoad', globalName });
}
};
if (script.complete || script.readyState === 'complete') {
checkGlobal();
return;
}
const loadHandler = () => {
checkGlobal();
};
resource_manager_1.resourceManager.addEventListener(LIBRARY_LOADER_COMPONENT_ID, script, 'load', loadHandler, { once: true });
}
static getLoadedResources() {
return this.loadedResources;
}
static clearCache() {
this.loadedResources.forEach((resource, url) => {
if (resource.element && resource.element.parentNode) {
resource.element.parentNode.removeChild(resource.element);
}
});
this.loadedResources.clear();
this.loadedLibraryStates.clear();
resource_manager_1.resourceManager.cleanupComponent(LIBRARY_LOADER_COMPONENT_ID);
}
static async loadLibraryWithDependencies(libraryName, allLibraries, requestedBy = 'user', options) {
const debug = options?.debug || false;
if (debug) {
console.log(`📚 Loading library '${libraryName}' with dependencies`);
}
const existingState = this.loadedLibraryStates.get(libraryName);
if (existingState) {
if (debug) {
console.log(`✅ Library '${libraryName}' already loaded (version: ${existingState.version})`);
}
if (!existingState.requestedBy.includes(requestedBy)) {
existingState.requestedBy.push(requestedBy);
}
return window[existingState.globalVariable];
}
const loadOrderResult = this.dependencyResolver.getLoadOrder([libraryName], allLibraries, options);
if (!loadOrderResult.success) {
const errors = loadOrderResult.errors?.join(', ') || 'Unknown error';
throw new Error(`Failed to resolve dependencies for '${libraryName}': ${errors}`);
}
if (loadOrderResult.warnings && debug) {
console.warn(`⚠️ Warnings for '${libraryName}':`, loadOrderResult.warnings);
}
const loadOrder = loadOrderResult.order || [];
if (debug) {
console.log(`📋 Load order for '${libraryName}':`, loadOrder.map(lib => `${lib.Name}@${lib.Version}`));
}
for (const library of loadOrder) {
if (this.loadedLibraryStates.has(library.Name)) {
if (debug) {
console.log(`⏭️ Skipping '${library.Name}' (already loaded)`);
}
continue;
}
if (library.Status) {
if (library.Status === 'Disabled') {
console.error(`🚫 ERROR: Library '${library.Name}' is DISABLED and should not be used`);
}
else if (library.Status === 'Deprecated') {
console.warn(`⚠️ WARNING: Library '${library.Name}' is DEPRECATED. Consider using an alternative.`);
}
}
if (debug) {
console.log(`📥 Loading '${library.Name}@${library.Version}'`);
}
if (!library.CDNUrl || !library.GlobalVariable) {
throw new Error(`Library '${library.Name}' missing CDN URL or global variable`);
}
if (library.CDNCssUrl) {
const cssUrls = library.CDNCssUrl.split(',').map(url => url.trim());
for (const cssUrl of cssUrls) {
if (cssUrl) {
this.loadCSS(cssUrl);
}
}
}
const loadedGlobal = await this.loadScript(library.CDNUrl, library.GlobalVariable, debug);
const dependencies = Array.from(this.dependencyResolver.getDirectDependencies(library).keys());
this.loadedLibraryStates.set(library.Name, {
name: library.Name,
version: library.Version || 'unknown',
globalVariable: library.GlobalVariable,
loadedAt: new Date(),
requestedBy: library.Name === libraryName ? [requestedBy] : [],
dependencies
});
if (debug) {
console.log(`✅ Loaded '${library.Name}@${library.Version}'`);
}
}
const targetLibrary = loadOrder.find(lib => lib.Name === libraryName);
if (!targetLibrary || !targetLibrary.GlobalVariable) {
throw new Error(`Failed to load library '${libraryName}'`);
}
return window[targetLibrary.GlobalVariable];
}
static async loadLibrariesWithDependencies(libraryNames, allLibraries, requestedBy = 'user', options) {
const debug = options?.debug || false;
const result = new Map();
if (debug) {
console.log(`📚 Loading libraries with dependencies:`, libraryNames);
console.log(` 📦 Total available libraries: ${allLibraries.length}`);
}
const loadOrderResult = this.dependencyResolver.getLoadOrder(libraryNames, allLibraries, options);
if (!loadOrderResult.success) {
const errors = loadOrderResult.errors?.join(', ') || 'Unknown error';
throw new Error(`Failed to resolve dependencies: ${errors}`);
}
if (debug) {
console.log(` 📊 Dependency resolution result:`, {
success: loadOrderResult.success,
errors: loadOrderResult.errors || [],
warnings: loadOrderResult.warnings || []
});
if (loadOrderResult.order) {
console.log(` 🔄 Resolved dependencies for each library:`);
loadOrderResult.order.forEach(lib => {
const deps = this.dependencyResolver.parseDependencies(lib.Dependencies);
if (deps.size > 0) {
console.log(` • ${lib.Name}@${lib.Version} requires:`, Array.from(deps.entries()));
}
else {
console.log(` • ${lib.Name}@${lib.Version} (no dependencies)`);
}
});
}
}
if (loadOrderResult.warnings && debug) {
console.warn(` ⚠️ Warnings:`, loadOrderResult.warnings);
}
const loadOrder = loadOrderResult.order || [];
if (debug) {
console.log(` 📋 Final load order:`, loadOrder.map(lib => `${lib.Name}@${lib.Version}`));
}
for (const library of loadOrder) {
if (this.loadedLibraryStates.has(library.Name)) {
if (debug) {
console.log(`⏭️ Skipping '${library.Name}' (already loaded)`);
}
const state = this.loadedLibraryStates.get(library.Name);
if (libraryNames.includes(library.Name)) {
result.set(library.Name, window[state.globalVariable]);
}
continue;
}
if (library.Status) {
if (library.Status === 'Disabled') {
console.error(`🚫 ERROR: Library '${library.Name}' is DISABLED and should not be used`);
}
else if (library.Status === 'Deprecated') {
console.warn(`⚠️ WARNING: Library '${library.Name}' is DEPRECATED. Consider using an alternative.`);
}
}
if (debug) {
console.log(`📥 Loading '${library.Name}@${library.Version}'`);
}
if (!library.CDNUrl || !library.GlobalVariable) {
throw new Error(`Library '${library.Name}' missing CDN URL or global variable`);
}
if (library.CDNCssUrl) {
const cssUrls = library.CDNCssUrl.split(',').map(url => url.trim());
for (const cssUrl of cssUrls) {
if (cssUrl) {
this.loadCSS(cssUrl);
}
}
}
const loadedGlobal = await this.loadScript(library.CDNUrl, library.GlobalVariable, debug);
const dependencies = Array.from(this.dependencyResolver.getDirectDependencies(library).keys());
this.loadedLibraryStates.set(library.Name, {
name: library.Name,
version: library.Version || 'unknown',
globalVariable: library.GlobalVariable,
loadedAt: new Date(),
requestedBy: libraryNames.includes(library.Name) ? [requestedBy] : [],
dependencies
});
if (libraryNames.includes(library.Name)) {
result.set(library.Name, loadedGlobal);
}
if (debug) {
console.log(`✅ Loaded '${library.Name}@${library.Version}'`);
}
}
return result;
}
static getLoadedLibraryStates() {
return new Map(this.loadedLibraryStates);
}
static isLibraryLoaded(libraryName) {
return this.loadedLibraryStates.has(libraryName);
}
static getLoadedLibraryVersion(libraryName) {
return this.loadedLibraryStates.get(libraryName)?.version;
}
}
exports.LibraryLoader = LibraryLoader;
LibraryLoader.loadedResources = new Map();
LibraryLoader.loadedLibraryStates = new Map();
LibraryLoader.dependencyResolver = new library_dependency_resolver_1.LibraryDependencyResolver({ debug: false });
LibraryLoader.enableProgressiveDelay = false;
//# sourceMappingURL=library-loader.js.map