75 lines
2.2 KiB
JavaScript
75 lines
2.2 KiB
JavaScript
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!');
|