K30 / scripts /optimize-images.js
Raí Santos
oi
20c5151
#!/usr/bin/env node
/**
* Image Optimization Script
*
* Provides recommendations for image optimization
* Note: Requires external tools for actual optimization
*/
const fs = require('fs');
const path = require('path');
function getFileSize(filePath) {
try {
const stats = fs.statSync(filePath);
return stats.size;
} catch {
return 0;
}
}
function formatSize(bytes) {
return (bytes / 1024).toFixed(2) + ' KB';
}
function analyzeImages(dir, extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg']) {
let images = [];
try {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
images = images.concat(analyzeImages(fullPath, extensions));
} else if (stat.isFile()) {
const ext = path.extname(item).toLowerCase();
if (extensions.includes(ext)) {
images.push({
path: fullPath.replace(process.cwd(), ''),
name: item,
size: stat.size,
ext: ext
});
}
}
}
} catch (error) {
console.error(`Error analyzing directory ${dir}:`, error.message);
}
return images;
}
console.log('🖼️ Image Optimization Analysis\n');
console.log('='.repeat(60));
const publicDir = path.join(__dirname, '..', 'public');
const iconsDir = path.join(publicDir, 'icons');
// Analyze images
const images = analyzeImages(publicDir);
if (images.length === 0) {
console.log('\nNo images found to optimize.');
process.exit(0);
}
console.log(`\nFound ${images.length} images\n`);
// Group by type
const byType = {};
images.forEach(img => {
if (!byType[img.ext]) {
byType[img.ext] = [];
}
byType[img.ext].push(img);
});
// Report by type
let totalSize = 0;
let potentialSavings = 0;
for (const [ext, imgs] of Object.entries(byType)) {
console.log(`\n${ext.toUpperCase()} Images:`);
console.log('-'.repeat(60));
const typeSize = imgs.reduce((sum, img) => sum + img.size, 0);
totalSize += typeSize;
// Calculate potential savings based on type
let savings = 0;
let recommendation = '';
switch (ext) {
case '.png':
savings = typeSize * 0.4; // ~40% savings with compression
recommendation = 'Use pngquant or TinyPNG. Consider WebP format.';
break;
case '.jpg':
case '.jpeg':
savings = typeSize * 0.3; // ~30% savings
recommendation = 'Use mozjpeg or jpegoptim. Consider WebP format.';
break;
case '.svg':
savings = typeSize * 0.2; // ~20% savings
recommendation = 'Use SVGO to optimize.';
break;
case '.gif':
savings = typeSize * 0.5; // ~50% savings
recommendation = 'Convert to WebP or use gifsicle.';
break;
default:
savings = 0;
recommendation = 'Already optimized format.';
}
potentialSavings += savings;
// Show largest images
imgs.sort((a, b) => b.size - a.size);
imgs.slice(0, 5).forEach(img => {
console.log(` ${img.name.padEnd(30)} ${formatSize(img.size).padStart(12)}`);
});
if (imgs.length > 5) {
console.log(` ... and ${imgs.length - 5} more`);
}
console.log(` Total: ${formatSize(typeSize)}`);
console.log(` 💡 ${recommendation}`);
console.log(` Potential savings: ${formatSize(savings)}`);
}
console.log('\n\n📊 Summary');
console.log('='.repeat(60));
console.log(` Total images: ${images.length}`);
console.log(` Total size: ${formatSize(totalSize)}`);
console.log(` Potential savings: ${formatSize(potentialSavings)} (${((potentialSavings/totalSize)*100).toFixed(1)}%)`);
console.log('\n\n💡 Optimization Recommendations');
console.log('='.repeat(60));
console.log('\n1. Install optimization tools:');
console.log(' npm install -g svgo');
console.log(' # For PNG: brew install pngquant (macOS) or apt-get install pngquant (Linux)');
console.log(' # For JPEG: brew install mozjpeg (macOS) or apt-get install mozjpeg (Linux)');
console.log('\n2. Optimize SVG icons:');
console.log(' svgo -f public/icons -o public/icons-optimized');
console.log('\n3. Convert to WebP (best compression):');
console.log(' # For each image:');
console.log(' # cwebp input.png -q 80 -o output.webp');
console.log('\n4. Use modern image formats:');
console.log(' • WebP: Best compression, wide support');
console.log(' • AVIF: Better than WebP, limited support');
console.log(' • Provide fallbacks for older browsers');
console.log('\n5. Implement responsive images:');
console.log(' <picture>');
console.log(' <source srcset="image.webp" type="image/webp">');
console.log(' <source srcset="image.jpg" type="image/jpeg">');
console.log(' <img src="image.jpg" alt="...">');
console.log(' </picture>');
console.log('\n6. Use lazy loading:');
console.log(' <img src="..." loading="lazy" alt="...">');
console.log('\n\n🎯 Priority Actions:');
console.log('='.repeat(60));
// Find large images that need optimization
const largeImages = images.filter(img => img.size > 100 * 1024).sort((a, b) => b.size - a.size);
if (largeImages.length > 0) {
console.log('\n⚠️ Large images that need optimization (>100KB):');
largeImages.slice(0, 10).forEach((img, i) => {
console.log(` ${i + 1}. ${img.name} (${formatSize(img.size)})`);
});
} else {
console.log('\n✅ No large images found! All images are optimized.');
}
// Check for SVG optimization opportunities
const svgImages = images.filter(img => img.ext === '.svg');
if (svgImages.length > 0) {
console.log(`\n📝 ${svgImages.length} SVG files can be optimized with SVGO`);
}
console.log('\n' + '='.repeat(60));
console.log('✅ Analysis complete!\n');