// Dynamic Module Loader System window.ModuleLoader = (function () { const loadedScripts = new Set(); const loadedStyles = new Set(); const moduleInstances = new Map(); // Cache de recursos carregados const resourceCache = new Map(); function log(message, ...args) { console.log(`🔧 ModuleLoader: ${message}`, ...args); } function error(message, ...args) { console.error(`❌ ModuleLoader: ${message}`, ...args); } async function loadScript(url, moduleBaseUrl) { const fullUrl = resolveUrl(url, moduleBaseUrl); if (loadedScripts.has(fullUrl)) { log(`Script já carregado: ${fullUrl}`); return true; } try { log(`Carregando script: ${fullUrl}`); const response = await fetch(fullUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const scriptContent = await response.text(); // Criar script element e executar const script = document.createElement('script'); script.textContent = scriptContent; script.setAttribute('data-module-script', fullUrl); document.head.appendChild(script); loadedScripts.add(fullUrl); log(`✅ Script carregado com sucesso: ${fullUrl}`); return true; } catch (err) { error(`Falha ao carregar script ${fullUrl}:`, err); return false; } } async function loadStyle(url, moduleBaseUrl) { const fullUrl = resolveUrl(url, moduleBaseUrl); if (loadedStyles.has(fullUrl)) { log(`CSS já carregado: ${fullUrl}`); return true; } try { log(`Carregando CSS: ${fullUrl}`); const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = fullUrl; link.setAttribute('data-module-style', fullUrl); return new Promise((resolve, reject) => { link.onload = () => { loadedStyles.add(fullUrl); log(`✅ CSS carregado com sucesso: ${fullUrl}`); resolve(true); }; link.onerror = () => { error(`Falha ao carregar CSS: ${fullUrl}`); reject(false); }; document.head.appendChild(link); }); } catch (err) { error(`Erro ao carregar CSS ${fullUrl}:`, err); return false; } } function resolveUrl(url, baseUrl) { if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) { return url; } if (url.startsWith('/')) { const base = new URL(baseUrl); return `${base.protocol}//${base.host}${url}`; } return new URL(url, baseUrl).href; } function extractModuleMetadata(container) { const metadataScript = container.querySelector('#module-metadata'); if (!metadataScript) { log('Nenhum metadata encontrado no módulo'); return null; } try { const metadata = JSON.parse(metadataScript.textContent); log('Metadata extraído:', metadata); return metadata; } catch (err) { error('Erro ao parsear metadata:', err); return null; } } function getModuleBaseUrl(moduleUrl) { try { const url = new URL(moduleUrl, window.location.href); const pathParts = url.pathname.split('/'); pathParts.pop(); // Remove o último segmento (nome do endpoint) url.pathname = pathParts.join('/'); return url.href; } catch (err) { error('Erro ao extrair base URL:', err); return window.location.origin; } } async function initializeModule(containerId, moduleUrl, metadata) { const moduleBaseUrl = getModuleBaseUrl(moduleUrl); log(`Inicializando módulo em ${containerId} com base URL: ${moduleBaseUrl}`); try { // Carregar CSS se especificado if (metadata.cssUrl) { await loadStyle(metadata.cssUrl, moduleBaseUrl); } // Carregar JavaScript if (metadata.jsUrl) { const scriptLoaded = await loadScript(metadata.jsUrl, moduleBaseUrl); if (!scriptLoaded) { throw new Error('Falha ao carregar script principal'); } } // Aguardar um momento para o script ser executado await new Promise(resolve => setTimeout(resolve, 100)); // Chamar função de inicialização if (metadata.jsFunction) { const functionPath = metadata.jsFunction.split('.'); let func = window; for (const part of functionPath) { func = func[part]; if (!func) { throw new Error(`Função ${metadata.jsFunction} não encontrada`); } } if (typeof func === 'function') { log(`Chamando função de inicialização: ${metadata.jsFunction}`); const result = func(containerId); if (result) { moduleInstances.set(containerId, { metadata, moduleUrl, moduleBaseUrl, initialized: true }); log(`✅ Módulo ${metadata.moduleId} inicializado com sucesso`); return true; } } } return false; } catch (err) { error(`Erro ao inicializar módulo:`, err); return false; } } async function loadModule(moduleId, containerId, moduleUrl) { log(`Carregando módulo ${moduleId} em ${containerId} de ${moduleUrl}`); try { // Carregar HTML do módulo const response = await fetch(moduleUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const html = await response.text(); const container = document.getElementById(containerId); if (!container) { throw new Error(`Container ${containerId} não encontrado`); } // Inserir HTML container.innerHTML = html; // Extrair metadata const metadata = extractModuleMetadata(container); if (!metadata) { log('⚠️ Módulo sem metadata, tentando funcionar sem JavaScript'); return true; } // Inicializar módulo const success = await initializeModule(containerId, moduleUrl, metadata); if (success) { // Disparar evento const event = new CustomEvent('moduleLoaded', { detail: { moduleId: metadata.moduleId, containerId, moduleUrl, metadata } }); document.dispatchEvent(event); return true; } return false; } catch (err) { error(`Erro ao carregar módulo ${moduleId}:`, err); return false; } } function getModuleInfo(containerId) { return moduleInstances.get(containerId); } function unloadModule(containerId) { const info = moduleInstances.get(containerId); if (info) { // Limpar container const container = document.getElementById(containerId); if (container) { container.innerHTML = ''; } moduleInstances.delete(containerId); log(`Módulo removido: ${containerId}`); } } // API pública return { loadModule, getModuleInfo, unloadModule, version: '2.0.0' }; })(); // Compatibilidade com sistema antigo window.ModuleSystem = { loadModule: window.ModuleLoader.loadModule, version: '2.0.0-compat' };