import sharp from 'sharp'; import { readdir, stat, rename } from 'fs/promises'; import { join, extname, basename, dirname } from 'path'; const IMG_DIR = 'public/images'; const MAX_WIDTH = 1920; const QUALITY = 80; const EXTENSIONS = new Set(['.jpg', '.jpeg', '.png']); async function getImages(dir) { const entries = await readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { files.push(...await getImages(fullPath)); } else if (EXTENSIONS.has(extname(entry.name).toLowerCase())) { files.push(fullPath); } } return files; } async function optimizeImage(filePath) { const ext = extname(filePath).toLowerCase(); const dir = dirname(filePath); const name = basename(filePath, ext); const webpPath = join(dir, `${name}.webp`); const before = (await stat(filePath)).size; try { const image = sharp(filePath); const metadata = await image.metadata(); // Resize if wider than MAX_WIDTH const resizeOpts = metadata.width > MAX_WIDTH ? { width: MAX_WIDTH } : {}; // Create WebP version await image .resize(resizeOpts) .webp({ quality: QUALITY }) .toFile(webpPath); // Also optimize the original JPEG in-place (for fallback) const optimizedJpeg = await sharp(filePath) .resize(resizeOpts) .jpeg({ quality: QUALITY, mozjpeg: true }) .toBuffer(); // Write optimized JPEG back const { writeFile } = await import('fs/promises'); await writeFile(filePath, optimizedJpeg); const afterJpeg = (await stat(filePath)).size; const afterWebp = (await stat(webpPath)).size; const saved = ((before - afterWebp) / before * 100).toFixed(0); console.log(`✓ ${filePath} — ${(before/1024).toFixed(0)}KB → ${(afterWebp/1024).toFixed(0)}KB WebP (${saved}% saved)`); } catch (err) { console.error(`✗ ${filePath}: ${err.message}`); } } const images = await getImages(IMG_DIR); console.log(`Found ${images.length} images to optimize...\n`); // Process in batches of 8 for memory const BATCH = 8; for (let i = 0; i < images.length; i += BATCH) { await Promise.all(images.slice(i, i + BATCH).map(optimizeImage)); } console.log('\nDone!');