114 lines
4.8 KiB
JavaScript
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));
|