/**
* This script optimizes images in the public directory,
* converting them to WebP format and resizing as needed.
*/
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const glob = promisify(require('glob').glob);
const sharp = require('sharp');
const { exec } = require('child_process');
// Configuration
const PUBLIC_DIR = path.join(__dirname, '../public');
const SIZES = [400, 800, 1200, 1600]; // Responsive sizes
const QUALITY = 80; // WebP quality
async function optimizeImages() {
try {
console.log('๐ Finding images to optimize...');
// Find all image files
const imageFiles = await glob('**/*.{jpg,jpeg,png,gif}', {
cwd: PUBLIC_DIR,
ignore: [
'optimized-images/**', // Skip already optimized images
'scripts/**',
'*.webp', // Skip webp files
'node_modules/**',
'.next/**'
],
nodir: true
});
console.log(`๐ท Found ${imageFiles.length} images to process`);
if (imageFiles.length === 0) {
console.log('โ
No images to optimize!');
return;
}
// Create optimized-images directory if it doesn't exist
const optimizedDir = path.join(PUBLIC_DIR, 'optimized-images');
if (!fs.existsSync(optimizedDir)) {
fs.mkdirSync(optimizedDir, { recursive: true });
}
// Process each image
for (const file of imageFiles) {
const inputPath = path.join(PUBLIC_DIR, file);
const fileInfo = path.parse(file);
// Create optimized versions
const webpPath = path.join(PUBLIC_DIR, fileInfo.dir, `${fileInfo.name}.webp`);
console.log(`๐ Processing: ${file} -> WebP`);
// Get image dimensions
const metadata = await sharp(inputPath).metadata();
const { width } = metadata;
// Convert to WebP with maximum quality
await sharp(inputPath)
.webp({ quality: QUALITY, effort: 6 })
.toFile(webpPath);
// Create responsive sizes if original is large enough
if (width > 800) {
for (const size of SIZES) {
if (width > size) {
const resizedWebpPath = path.join(
PUBLIC_DIR,
fileInfo.dir,
`${fileInfo.name}-${size}.webp`
);
await sharp(inputPath)
.resize(size)
.webp({ quality: QUALITY, effort: 6 })
.toFile(resizedWebpPath);
console.log(`โ
Created: ${fileInfo.name}-${size}.webp`);
}
}
}
// Save a backup to optimized-images
const backupPath = path.join(optimizedDir, path.basename(file));
fs.copyFileSync(inputPath, backupPath);
console.log(`โ
Optimized: ${file}`);
}
console.log('๐ Optimization complete!');
// Print size comparison
const originalSize = await calculateDirectorySize(PUBLIC_DIR, ['**/*.{jpg,jpeg,png,gif}']);
const webpSize = await calculateDirectorySize(PUBLIC_DIR, ['**/*.webp']);
const savings = originalSize - webpSize;
console.log(`
๐ Optimization Results:
-----------------------
Original images: ${formatBytes(originalSize)}
WebP images: ${formatBytes(webpSize)}
Savings: ${formatBytes(savings)} (${Math.round((savings / originalSize) * 100)}%)
`);
} catch (error) {
console.error('โ Error optimizing images:', error);
process.exit(1);
}
}
async function calculateDirectorySize(dir, patterns) {
let totalSize = 0;
for (const pattern of patterns) {
const files = await glob(pattern, {
cwd: dir,
nodir: true
});
for (const file of files) {
const stats = fs.statSync(path.join(dir, file));
totalSize += stats.size;
}
}
return totalSize;
}
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Run the script
optimizeImages();