vkashti / scripts / optimize-images.js
optimize-images.js
Raw
/**
 * 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();