269 lines
8.4 KiB
JavaScript
269 lines
8.4 KiB
JavaScript
// 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'
|
|
}; |