|
|
|
|
|
|
|
|
|
|
|
export interface BundleAnalysis { |
|
totalSize: number; |
|
chunks: Array<{ |
|
name: string; |
|
size: number; |
|
modules: string[]; |
|
}>; |
|
duplicatedDependencies: string[]; |
|
suggestions: string[]; |
|
} |
|
|
|
|
|
export class TreeShakingDetector { |
|
private unusedExports = new Set<string>(); |
|
private unusedImports = new Set<string>(); |
|
|
|
analyzeModule(moduleCode: string, imports: string[], exports: string[]): void { |
|
|
|
const usedImports = this.findUsedImports(moduleCode, imports); |
|
const usedExports = this.findUsedExports(moduleCode, exports); |
|
|
|
imports.forEach(imp => { |
|
if (!usedImports.includes(imp)) { |
|
this.unusedImports.add(imp); |
|
} |
|
}); |
|
|
|
exports.forEach(exp => { |
|
if (!usedExports.includes(exp)) { |
|
this.unusedExports.add(exp); |
|
} |
|
}); |
|
} |
|
|
|
private findUsedImports(code: string, imports: string[]): string[] { |
|
return imports.filter(imp => code.includes(imp)); |
|
} |
|
|
|
private findUsedExports(code: string, exports: string[]): string[] { |
|
return exports.filter(exp => code.includes(exp)); |
|
} |
|
|
|
getUnusedImports(): string[] { |
|
return Array.from(this.unusedImports); |
|
} |
|
|
|
getUnusedExports(): string[] { |
|
return Array.from(this.unusedExports); |
|
} |
|
|
|
getSuggestions(): string[] { |
|
const suggestions: string[] = []; |
|
|
|
if (this.unusedImports.size > 0) { |
|
suggestions.push(`Remover ${this.unusedImports.size} imports não utilizados`); |
|
} |
|
|
|
if (this.unusedExports.size > 0) { |
|
suggestions.push(`Remover ${this.unusedExports.size} exports não utilizados`); |
|
} |
|
|
|
return suggestions; |
|
} |
|
} |
|
|
|
|
|
export class CodeSplitter { |
|
private routes: Map<string, string[]> = new Map(); |
|
private components: Map<string, number> = new Map(); |
|
|
|
registerRoute(path: string, components: string[]): void { |
|
this.routes.set(path, components); |
|
|
|
|
|
components.forEach(comp => { |
|
this.components.set(comp, (this.components.get(comp) || 0) + 1); |
|
}); |
|
} |
|
|
|
getCommonComponents(): string[] { |
|
return Array.from(this.components.entries()) |
|
.filter(([_, count]) => count > 1) |
|
.map(([comp]) => comp); |
|
} |
|
|
|
generateSplitSuggestions(): Record<string, string[]> { |
|
const suggestions: Record<string, string[]> = { |
|
'vendor': ['react', 'react-dom', 'axios'], |
|
'common': this.getCommonComponents(), |
|
'routes': [] |
|
}; |
|
|
|
|
|
this.routes.forEach((components, route) => { |
|
if (components.length > 3) { |
|
suggestions.routes.push(`Split route ${route}: ${components.join(', ')}`); |
|
} |
|
}); |
|
|
|
return suggestions; |
|
} |
|
} |
|
|
|
|
|
export class IntelligentPreloader { |
|
private preloadQueue: Array<() => Promise<any>> = []; |
|
private preloadCache = new Set<string>(); |
|
private isPreloading = false; |
|
|
|
|
|
register(key: string, loader: () => Promise<any>): void { |
|
if (!this.preloadCache.has(key)) { |
|
this.preloadQueue.push(async () => { |
|
try { |
|
await loader(); |
|
this.preloadCache.add(key); |
|
} catch (error) { |
|
console.warn(`Preload failed for ${key}:`, error); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
async preloadByPriority(priorities: string[]): Promise<void> { |
|
if (this.isPreloading) return; |
|
|
|
this.isPreloading = true; |
|
|
|
try { |
|
|
|
if ('requestIdleCallback' in window) { |
|
await this.preloadWithIdleCallback(priorities); |
|
} else { |
|
await this.preloadWithTimeout(priorities); |
|
} |
|
} finally { |
|
this.isPreloading = false; |
|
} |
|
} |
|
|
|
private async preloadWithIdleCallback(priorities: string[]): Promise<void> { |
|
return new Promise((resolve) => { |
|
const processQueue = async () => { |
|
if (this.preloadQueue.length === 0) { |
|
resolve(); |
|
return; |
|
} |
|
|
|
const loader = this.preloadQueue.shift()!; |
|
await loader(); |
|
|
|
if (this.preloadQueue.length > 0) { |
|
(window as any).requestIdleCallback(processQueue, { timeout: 5000 }); |
|
} else { |
|
resolve(); |
|
} |
|
}; |
|
|
|
(window as any).requestIdleCallback(processQueue, { timeout: 5000 }); |
|
}); |
|
} |
|
|
|
private async preloadWithTimeout(priorities: string[]): Promise<void> { |
|
for (const loader of this.preloadQueue) { |
|
await new Promise(resolve => setTimeout(resolve, 10)); |
|
await loader(); |
|
} |
|
this.preloadQueue = []; |
|
} |
|
|
|
|
|
preloadOnHover(element: HTMLElement, key: string, loader: () => Promise<any>): () => void { |
|
let timeoutId: NodeJS.Timeout; |
|
|
|
const handleMouseEnter = () => { |
|
timeoutId = setTimeout(() => { |
|
this.register(key, loader); |
|
this.preloadByPriority([key]); |
|
}, 150); |
|
}; |
|
|
|
const handleMouseLeave = () => { |
|
if (timeoutId) clearTimeout(timeoutId); |
|
}; |
|
|
|
element.addEventListener('mouseenter', handleMouseEnter); |
|
element.addEventListener('mouseleave', handleMouseLeave); |
|
|
|
|
|
return () => { |
|
element.removeEventListener('mouseenter', handleMouseEnter); |
|
element.removeEventListener('mouseleave', handleMouseLeave); |
|
if (timeoutId) clearTimeout(timeoutId); |
|
}; |
|
} |
|
|
|
getPreloadStatus(): { cached: number; pending: number } { |
|
return { |
|
cached: this.preloadCache.size, |
|
pending: this.preloadQueue.length |
|
}; |
|
} |
|
} |
|
|
|
|
|
export class ResourceHintsManager { |
|
private addedHints = new Set<string>(); |
|
|
|
|
|
preloadResource(href: string, as: string, crossorigin?: boolean): void { |
|
const key = `preload-${href}`; |
|
if (this.addedHints.has(key)) return; |
|
|
|
const link = document.createElement('link'); |
|
link.rel = 'preload'; |
|
link.href = href; |
|
link.as = as; |
|
if (crossorigin) link.crossOrigin = 'anonymous'; |
|
|
|
document.head.appendChild(link); |
|
this.addedHints.add(key); |
|
} |
|
|
|
|
|
prefetchResource(href: string): void { |
|
const key = `prefetch-${href}`; |
|
if (this.addedHints.has(key)) return; |
|
|
|
const link = document.createElement('link'); |
|
link.rel = 'prefetch'; |
|
link.href = href; |
|
|
|
document.head.appendChild(link); |
|
this.addedHints.add(key); |
|
} |
|
|
|
|
|
preconnectDomain(domain: string): void { |
|
const key = `preconnect-${domain}`; |
|
if (this.addedHints.has(key)) return; |
|
|
|
const link = document.createElement('link'); |
|
link.rel = 'preconnect'; |
|
link.href = domain; |
|
|
|
document.head.appendChild(link); |
|
this.addedHints.add(key); |
|
} |
|
|
|
|
|
dnsPrefetch(domain: string): void { |
|
const key = `dns-prefetch-${domain}`; |
|
if (this.addedHints.has(key)) return; |
|
|
|
const link = document.createElement('link'); |
|
link.rel = 'dns-prefetch'; |
|
link.href = domain; |
|
|
|
document.head.appendChild(link); |
|
this.addedHints.add(key); |
|
} |
|
|
|
|
|
setupApiHints(): void { |
|
|
|
this.dnsPrefetch('//servicebus2.caixa.gov.br'); |
|
|
|
|
|
this.preconnectDomain('https://servicebus2.caixa.gov.br'); |
|
} |
|
|
|
getHintsStatus(): { total: number; types: Record<string, number> } { |
|
const types: Record<string, number> = {}; |
|
|
|
this.addedHints.forEach(hint => { |
|
const type = hint.split('-')[0]; |
|
types[type] = (types[type] || 0) + 1; |
|
}); |
|
|
|
return { |
|
total: this.addedHints.size, |
|
types |
|
}; |
|
} |
|
} |
|
|
|
|
|
export const treeshakingDetector = new TreeShakingDetector(); |
|
export const codeSplitter = new CodeSplitter(); |
|
export const intelligentPreloader = new IntelligentPreloader(); |
|
export const resourceHints = new ResourceHintsManager(); |
|
|
|
|
|
export function setupBundleOptimizations(): void { |
|
|
|
resourceHints.setupApiHints(); |
|
|
|
|
|
intelligentPreloader.register('DualGameViewer', () => import('../components/DualGameViewer')); |
|
intelligentPreloader.register('EnhancedLotomaniaGrid', () => import('../components/EnhancedLotomaniaGrid')); |
|
|
|
|
|
codeSplitter.registerRoute('/', ['DualGameViewer', 'EnhancedLotomaniaGrid']); |
|
codeSplitter.registerRoute('/statistics', ['Statistics', 'Charts']); |
|
codeSplitter.registerRoute('/analysis', ['ResultsAnalysis', 'ProbabilityCalculator']); |
|
} |
|
|
|
|
|
export function useBundleOptimizations() { |
|
return { |
|
preload: (key: string, loader: () => Promise<any>) => intelligentPreloader.register(key, loader), |
|
getPreloadStatus: () => intelligentPreloader.getPreloadStatus(), |
|
getSplitSuggestions: () => codeSplitter.generateSplitSuggestions(), |
|
getHintsStatus: () => resourceHints.getHintsStatus() |
|
}; |
|
} |
|
|