bayarea-cc/scripts/optimize-images.cjs

114 lines
4.8 KiB
JavaScript

const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const PUBLIC_DIR = path.join(__dirname, '../public');
const WEBP_QUALITY = 80;
const AVIF_QUALITY = 80;
const PNG_QUALITY = 80;
async function optimizeImages() {
console.log('Starting image optimization...\n');
// Get all PNG files in public directory
const files = fs.readdirSync(PUBLIC_DIR)
.filter(file => file.endsWith('.png'))
.map(file => path.join(PUBLIC_DIR, file));
if (files.length === 0) {
console.log('No PNG files found in public directory');
return;
}
console.log(`Found ${files.length} PNG files to optimize\n`);
let totalOriginalSize = 0;
let totalWebpSize = 0;
let totalAvifSize = 0;
let totalCompressedPngSize = 0;
for (const filePath of files) {
const filename = path.basename(filePath);
const filenameWithoutExt = path.basename(filePath, '.png');
const originalSize = fs.statSync(filePath).size;
totalOriginalSize += originalSize;
console.log(`Processing: ${filename} (${(originalSize / 1024 / 1024).toFixed(2)} MB)`);
try {
// Generate WebP version
const webpPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}.webp`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.webp({ quality: WEBP_QUALITY })
.toFile(webpPath);
const webpSize = fs.statSync(webpPath).size;
totalWebpSize += webpSize;
console.log(` ✓ WebP: ${(webpSize / 1024).toFixed(2)} KB (${((1 - webpSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Generate AVIF version
const avifPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}.avif`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.avif({ quality: AVIF_QUALITY })
.toFile(avifPath);
const avifSize = fs.statSync(avifPath).size;
totalAvifSize += avifSize;
console.log(` ✓ AVIF: ${(avifSize / 1024).toFixed(2)} KB (${((1 - avifSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Compress original PNG
const compressedPngPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}_compressed.png`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.png({
quality: PNG_QUALITY,
compressionLevel: 9,
palette: true
})
.toFile(compressedPngPath);
const compressedPngSize = fs.statSync(compressedPngPath).size;
totalCompressedPngSize += compressedPngSize;
console.log(` ✓ Compressed PNG: ${(compressedPngSize / 1024).toFixed(2)} KB (${((1 - compressedPngSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Replace original with compressed version
fs.unlinkSync(filePath);
fs.renameSync(compressedPngPath, filePath);
console.log(` ✓ Original PNG replaced with compressed version\n`);
} catch (error) {
console.error(` ✗ Error processing ${filename}:`, error.message);
}
}
// Summary
console.log('\n' + '='.repeat(60));
console.log('OPTIMIZATION SUMMARY');
console.log('='.repeat(60));
console.log(`Total original PNG size: ${(totalOriginalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`Total WebP size: ${(totalWebpSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalWebpSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log(`Total AVIF size: ${(totalAvifSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalAvifSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log(`Total compressed PNG size: ${(totalCompressedPngSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalCompressedPngSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log('\nAverage file sizes:');
console.log(` WebP: ${(totalWebpSize / files.length / 1024).toFixed(2)} KB`);
console.log(` AVIF: ${(totalAvifSize / files.length / 1024).toFixed(2)} KB`);
console.log(` Compressed PNG: ${(totalCompressedPngSize / files.length / 1024).toFixed(2)} KB`);
console.log('='.repeat(60));
// Check if targets are met
const avgWebpSize = totalWebpSize / files.length / 1024;
const avgAvifSize = totalAvifSize / files.length / 1024;
const totalSizeAfter = (totalWebpSize + totalAvifSize + totalCompressedPngSize) / 1024 / 1024;
console.log('\nTarget Achievement:');
console.log(` WebP avg < 200KB: ${avgWebpSize < 200 ? '✓ PASS' : '✗ FAIL'} (${avgWebpSize.toFixed(2)} KB)`);
console.log(` AVIF avg < 150KB: ${avgAvifSize < 150 ? '✓ PASS' : '✗ FAIL'} (${avgAvifSize.toFixed(2)} KB)`);
console.log(` Total size < 2MB: ${totalSizeAfter < 2 ? '✓ PASS' : '✗ FAIL'} (${totalSizeAfter.toFixed(2)} MB)`);
}
optimizeImages()
.then(() => console.log('\nOptimization complete!'))
.catch(error => console.error('Optimization failed:', error));