mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
166 lines • 6.88 kB
JavaScript
/**
* Bundle Performance Analyzer
* Analyzes JavaScript bundle sizes and optimization opportunities
*/
import fs from 'fs-extra';
import * as path from 'path';
export class BundleAnalyzer {
projectRoot;
constructor(projectRoot) {
this.projectRoot = projectRoot;
}
async analyze(issues) {
try {
// Look for webpack build output
const distPath = path.join(this.projectRoot, 'dist');
const buildPath = path.join(this.projectRoot, 'build');
let bundlePath = '';
if (await fs.pathExists(distPath)) {
bundlePath = distPath;
}
else if (await fs.pathExists(buildPath)) {
bundlePath = buildPath;
}
else {
return undefined;
}
const files = await fs.readdir(bundlePath);
const jsFiles = files.filter(f => f.endsWith('.js'));
const chunkSizes = {};
const largestAssets = [];
let totalSize = 0;
for (const file of jsFiles) {
const filePath = path.join(bundlePath, file);
const stats = await fs.stat(filePath);
const size = stats.size;
chunkSizes[file] = size;
totalSize += size;
largestAssets.push({ name: file, size });
// Check for oversized bundles
if (size > 1024 * 1024) { // 1MB
issues.push({
file: `${bundlePath}/${file}`,
type: 'large_bundle',
message: `Bundle is large (${(size / 1024 / 1024).toFixed(1)}MB)`,
impact: 'high',
recommendation: 'Consider code splitting or tree shaking'
});
}
}
// Sort largest assets
largestAssets.sort((a, b) => b.size - a.size);
// Analyze for duplicated modules
const duplicatedModules = await this.findDuplicatedModules(bundlePath, jsFiles);
// Generate suggestions
const suggestions = this.generateBundleSuggestions(totalSize, jsFiles.length, largestAssets, duplicatedModules);
// Estimate gzipped size
const gzippedSize = await this.estimateGzippedSize(bundlePath, jsFiles);
return {
totalSize,
gzippedSize,
chunkSizes,
largestAssets: largestAssets.slice(0, 10),
duplicatedModules,
suggestions
};
}
catch (error) {
return undefined;
}
}
async findDuplicatedModules(bundlePath, jsFiles) {
const moduleSignatures = new Map();
const duplicates = [];
for (const file of jsFiles) {
try {
const filePath = path.join(bundlePath, file);
const content = await fs.readFile(filePath, 'utf-8');
// Look for common module patterns
const modulePatterns = [
/\/\*\*\*\/ \"([\w-]+)\"/g, // Webpack module comments
/define\("([\w-]+)",/g, // AMD modules
/exports\["([\w-]+)"\]/g // CommonJS exports
];
for (const pattern of modulePatterns) {
let match;
while ((match = pattern.exec(content)) !== null) {
const moduleName = match[1];
if (!moduleSignatures.has(moduleName)) {
moduleSignatures.set(moduleName, []);
}
moduleSignatures.get(moduleName).push(file);
}
}
}
catch (error) {
// Skip file if can't read
}
}
// Find modules that appear in multiple files
for (const [moduleName, files] of moduleSignatures) {
if (files.length > 1) {
duplicates.push(`${moduleName} (in ${files.join(', ')})`);
}
}
return duplicates;
}
generateBundleSuggestions(totalSize, fileCount, largestAssets, duplicatedModules) {
const suggestions = [];
// Size-based suggestions
if (totalSize > 5 * 1024 * 1024) { // 5MB total
suggestions.push('Total bundle size is large - consider aggressive code splitting');
}
if (totalSize > 10 * 1024 * 1024) { // 10MB total
suggestions.push('Bundle size is excessive - review dependencies and enable tree shaking');
}
// File count suggestions
if (fileCount === 1) {
suggestions.push('Single bundle detected - consider code splitting for better caching');
}
if (fileCount > 20) {
suggestions.push('Many bundle chunks - consider consolidating for fewer HTTP requests');
}
// Largest asset suggestions
if (largestAssets.length > 0 && largestAssets[0].size > 2 * 1024 * 1024) {
suggestions.push(`Largest bundle (${largestAssets[0].name}) is over 2MB - split critical path`);
}
// Duplication suggestions
if (duplicatedModules.length > 0) {
suggestions.push(`Found ${duplicatedModules.length} duplicated modules - configure webpack optimization.splitChunks`);
}
// Common library suggestions
const hasReact = largestAssets.some(a => a.name.includes('react'));
const hasVendor = largestAssets.some(a => a.name.includes('vendor'));
if (!hasVendor && fileCount > 1) {
suggestions.push('Consider extracting vendor libraries into separate chunk');
}
if (hasReact) {
suggestions.push('Ensure React is in production mode for smaller bundle size');
}
return suggestions;
}
async estimateGzippedSize(bundlePath, jsFiles) {
let totalGzippedSize = 0;
for (const file of jsFiles) {
try {
const filePath = path.join(bundlePath, file);
const stats = await fs.stat(filePath);
// Check if .gz version exists
const gzPath = filePath + '.gz';
if (await fs.pathExists(gzPath)) {
const gzStats = await fs.stat(gzPath);
totalGzippedSize += gzStats.size;
}
else {
// Estimate gzip size as 30% of original
totalGzippedSize += Math.floor(stats.size * 0.3);
}
}
catch (error) {
// Skip file if can't read
}
}
return totalGzippedSize;
}
}
//# sourceMappingURL=BundleAnalyzer.js.map