Files
michaelschiemer/resources/js/modules/index.js
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

179 lines
6.2 KiB
JavaScript

import { moduleConfig } from './config.js';
import { Logger } from '../core/logger.js';
import { moduleErrorBoundary } from '../core/ModuleErrorBoundary.js';
import { stateManager } from '../core/StateManager.js';
import { dependencyManager } from '../core/DependencyManager.js';
export const activeModules = new Map(); // key: modulename → { mod, config, state }
export async function registerModules() {
// Use EAGER loading for single bundle - all modules in one chunk
let modules;
if (typeof global !== 'undefined' && global.importMeta?.glob) {
modules = global.importMeta.glob('./*/index.js', { eager: true });
} else {
modules = import.meta.glob('./*/index.js', { eager: true });
}
Logger.info('[Modules] Found modules:', Object.keys(modules));
const domModules = new Set(
Array.from(document.querySelectorAll('[data-module]')).map(el => el.dataset.module).filter(Boolean)
);
// Always load these core modules
const coreModules = new Set(['spa-router', 'form-handling', 'api-manager']);
const usedModules = new Set([...domModules, ...coreModules]);
const fallbackMode = usedModules.size === coreModules.size && domModules.size === 0;
Logger.info('[Modules] DOM modules found:', [...domModules]);
Logger.info('[Modules] Core modules:', [...coreModules]);
Logger.info('[Modules] Used modules:', [...usedModules]);
Logger.info('[Modules] Fallback mode:', fallbackMode);
// Phase 1: Register only USED modules with dependency manager
Object.entries(modules).forEach(([path, mod]) => {
const name = path.split('/').slice(-2, -1)[0];
if(!fallbackMode && !usedModules.has(name)) {
Logger.info(`⏭️ [Module] Skipping unused module: ${name}`);
return;
}
// Register module definition if provided
if (typeof mod.definition === 'object') {
dependencyManager.register(mod.definition);
} else {
// Create default definition for modules without explicit dependencies
const defaultDef = {
name,
version: '1.0.0',
dependencies: [],
provides: [],
priority: 0
};
dependencyManager.register(defaultDef);
}
});
// Phase 2: Calculate initialization order
const initOrder = dependencyManager.calculateInitializationOrder();
// Phase 3: Initialize modules in dependency order
for (const name of initOrder) {
if(!fallbackMode && !usedModules.has(name)) {
Logger.info(`⏭️ [Module] Skipped (not used in DOM): ${name}`);
continue;
}
const modulePath = Object.keys(modules).find(path =>
path.split('/').slice(-2, -1)[0] === name
);
if (!modulePath) {
Logger.warn(`⛔ [Module] No implementation found for: ${name}`);
continue;
}
const mod = modules[modulePath];
const config = moduleConfig[name] || {};
// Check dependencies before initialization
const depCheck = dependencyManager.checkDependencies(name);
if (!depCheck.satisfied) {
Logger.error(`❌ [Module] Cannot initialize ${name}: ${depCheck.reason}`);
activeModules.set(name, { mod: null, config, error: new Error(depCheck.reason), original: mod });
continue;
}
if (typeof mod.init === 'function') {
try {
dependencyManager.markInitializing(name);
// Create scoped state manager for module
const scopedState = stateManager.createScope(name);
// Wrap module with error boundary
const protectedMod = moduleErrorBoundary.wrapModule(mod, name);
// Initialize module with config and state
await protectedMod.init(config, scopedState);
dependencyManager.markInitialized(name);
activeModules.set(name, {
mod: protectedMod,
config,
state: scopedState,
original: mod
});
Logger.info(`✅ [Module] Initialized: ${name}`);
} catch (error) {
Logger.error(`❌ [Module] Failed to initialize ${name}:`, error);
activeModules.set(name, { mod: null, config, error, original: mod });
}
} else {
Logger.warn(`⛔ [Module] No init() in ${name}`);
}
}
if (fallbackMode) {
Logger.info('⚠️ [Module] No data-module usage detected, fallback to full init mode');
}
}
export function destroyModules() {
for (const [name, { mod, state }] of activeModules.entries()) {
if (mod && typeof mod.destroy === 'function') {
try {
mod.destroy();
Logger.info(`🧹 [Module] Destroyed: ${name}`);
} catch (error) {
Logger.error(`❌ [Module] Failed to destroy ${name}:`, error);
}
}
// Clean up state subscriptions
if (state && typeof state.cleanup === 'function') {
state.cleanup();
}
}
activeModules.clear();
moduleErrorBoundary.reset();
stateManager.reset();
dependencyManager.reset();
}
/**
* Gets health status of all active modules
* @returns {Object} Module health report
*/
export function getModuleHealth() {
const health = {
total: activeModules.size,
active: 0,
failed: 0,
modules: {},
errorBoundary: moduleErrorBoundary.getHealthStatus()
};
for (const [name, { mod, error }] of activeModules.entries()) {
if (error) {
health.failed++;
health.modules[name] = { status: 'failed', error: error.message };
} else if (mod) {
health.active++;
health.modules[name] = { status: 'active' };
} else {
health.modules[name] = { status: 'unknown' };
}
}
return health;
}
// Debug function - access via console
if (typeof window !== 'undefined') {
window.moduleHealth = getModuleHealth;
}