|
|
|
|
|
|
|
|
|
|
|
export const treeShakeOptimizations = { |
|
|
|
|
|
|
|
|
|
sections: { |
|
DualGameViewer: () => import('../components/DualGameViewer'), |
|
Statistics: () => import('../components/Statistics'), |
|
ProbabilityCalculator: () => import('../components/ProbabilityCalculator'), |
|
EnhancedLotomaniaGrid: () => import('../components/EnhancedLotomaniaGrid'), |
|
LotomaniaFeatures: () => import('../components/LotomaniaFeatures'), |
|
GameByGameResults: () => import('../components/GameByGameResults') |
|
}, |
|
|
|
|
|
utils: { |
|
chartSetup: () => import('../utils/chartSetup'), |
|
securityUtils: () => import('../utils/SecurityUtils'), |
|
performanceAnalyzer: () => import('../utils/PerformanceAnalyzer') |
|
} |
|
}; |
|
|
|
|
|
export class SmartPreloader { |
|
private static instance: SmartPreloader; |
|
private preloadedModules = new Set<string>(); |
|
private userInteractions = new Map<string, number>(); |
|
|
|
static getInstance(): SmartPreloader { |
|
if (!SmartPreloader.instance) { |
|
SmartPreloader.instance = new SmartPreloader(); |
|
} |
|
return SmartPreloader.instance; |
|
} |
|
|
|
|
|
preloadOnHover(moduleName: string, importFn: () => Promise<any>): () => void { |
|
let timeoutId: NodeJS.Timeout; |
|
|
|
const handleMouseEnter = () => { |
|
if (this.preloadedModules.has(moduleName)) return; |
|
|
|
timeoutId = setTimeout(() => { |
|
importFn().then(() => { |
|
this.preloadedModules.add(moduleName); |
|
console.log(`📦 Preloaded: ${moduleName}`); |
|
}).catch(err => { |
|
console.warn(`Failed to preload ${moduleName}:`, err); |
|
}); |
|
}, 50); |
|
}; |
|
|
|
const handleMouseLeave = () => { |
|
clearTimeout(timeoutId); |
|
}; |
|
|
|
|
|
return () => { |
|
clearTimeout(timeoutId); |
|
}; |
|
} |
|
|
|
|
|
async preloadByPriority(modules: Array<{ name: string; import: () => Promise<any>; priority: number }>) { |
|
|
|
const connection = (navigator as any).connection; |
|
const isSlowConnection = connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g'); |
|
|
|
if (isSlowConnection) { |
|
console.log('🐌 Slow connection detected, skipping preload'); |
|
return; |
|
} |
|
|
|
|
|
const sortedModules = modules.sort((a, b) => b.priority - a.priority); |
|
|
|
for (const module of sortedModules) { |
|
if (this.preloadedModules.has(module.name)) continue; |
|
|
|
try { |
|
await module.import(); |
|
this.preloadedModules.add(module.name); |
|
console.log(`📦 Preloaded: ${module.name} (priority: ${module.priority})`); |
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 10)); |
|
} catch (error) { |
|
console.warn(`Failed to preload ${module.name}:`, error); |
|
} |
|
} |
|
} |
|
|
|
|
|
trackInteraction(section: string) { |
|
const current = this.userInteractions.get(section) || 0; |
|
this.userInteractions.set(section, current + 1); |
|
|
|
|
|
if (current >= 3) { |
|
this.preloadRelatedModules(section); |
|
} |
|
} |
|
|
|
private preloadRelatedModules(section: string) { |
|
const relatedModules: Record<string, string[]> = { |
|
'dual-visualizer': ['Statistics', 'ProbabilityCalculator'], |
|
'results-analysis': ['GameByGameResults', 'EnhancedLotomaniaGrid'], |
|
'probability-calculator': ['Statistics', 'DualGameViewer'] |
|
}; |
|
|
|
const related = relatedModules[section] || []; |
|
related.forEach(moduleName => { |
|
const importFn = (treeShakeOptimizations.sections as any)[moduleName]; |
|
if (importFn && !this.preloadedModules.has(moduleName)) { |
|
importFn().then(() => { |
|
this.preloadedModules.add(moduleName); |
|
console.log(`🧠 Smart preloaded: ${moduleName} (related to ${section})`); |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
getStats() { |
|
return { |
|
preloadedModules: Array.from(this.preloadedModules), |
|
userInteractions: Object.fromEntries(this.userInteractions), |
|
totalPreloaded: this.preloadedModules.size |
|
}; |
|
} |
|
} |
|
|
|
|
|
export const injectResourceHints = () => { |
|
if (typeof document === 'undefined') return; |
|
|
|
const head = document.head; |
|
|
|
|
|
const dnsPrefetchUrls = [ |
|
'servicebus2.caixa.gov.br', |
|
'api.allorigins.win', |
|
'brasilapi.com.br' |
|
]; |
|
|
|
dnsPrefetchUrls.forEach(url => { |
|
const link = document.createElement('link'); |
|
link.rel = 'dns-prefetch'; |
|
link.href = `//${url}`; |
|
head.appendChild(link); |
|
}); |
|
|
|
|
|
const preconnectUrls = [ |
|
'https://fonts.googleapis.com', |
|
'https://fonts.gstatic.com' |
|
]; |
|
|
|
preconnectUrls.forEach(url => { |
|
const link = document.createElement('link'); |
|
link.rel = 'preconnect'; |
|
link.href = url; |
|
link.crossOrigin = 'anonymous'; |
|
head.appendChild(link); |
|
}); |
|
|
|
console.log('🔗 Resource hints injected'); |
|
}; |
|
|
|
|
|
export const analyzeBundleSize = () => { |
|
if (typeof window === 'undefined') return; |
|
|
|
window.addEventListener('load', () => { |
|
setTimeout(() => { |
|
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]; |
|
|
|
let totalJSSize = 0; |
|
let totalCSSSize = 0; |
|
let chunkCount = 0; |
|
|
|
resources.forEach(resource => { |
|
if (resource.name.includes('.js')) { |
|
totalJSSize += resource.transferSize || 0; |
|
chunkCount++; |
|
} else if (resource.name.includes('.css')) { |
|
totalCSSSize += resource.transferSize || 0; |
|
} |
|
}); |
|
|
|
const totalSize = totalJSSize + totalCSSSize; |
|
const analysis = { |
|
totalSize: formatBytes(totalSize), |
|
jsSize: formatBytes(totalJSSize), |
|
cssSize: formatBytes(totalCSSSize), |
|
chunkCount, |
|
score: getBundleScore(totalSize) |
|
}; |
|
|
|
console.log('📊 Bundle Analysis:', analysis); |
|
|
|
|
|
if (totalSize > 500 * 1024) { |
|
console.warn('⚠️ Bundle muito grande! Considere mais code splitting.'); |
|
} |
|
|
|
if (chunkCount > 15) { |
|
console.warn('⚠️ Muitos chunks! Considere bundling de módulos relacionados.'); |
|
} |
|
|
|
return analysis; |
|
}, 1000); |
|
}); |
|
}; |
|
|
|
const formatBytes = (bytes: number): string => { |
|
if (bytes === 0) return '0 Bytes'; |
|
const k = 1024; |
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
}; |
|
|
|
const getBundleScore = (bytes: number): string => { |
|
if (bytes < 100 * 1024) return 'Excelente'; |
|
if (bytes < 250 * 1024) return 'Bom'; |
|
if (bytes < 500 * 1024) return 'Aceitável'; |
|
return 'Precisa melhorar'; |
|
}; |
|
|
|
|
|
export const smartPreloader = SmartPreloader.getInstance(); |
|
|