Node.js
await sharp(inputPath).webp({ lossless: false }).toFile(outputPath);
fs.unlinkSync(inputPath);
console.log(`Space saved: ${formatSize(sizeSaved)}`);
for (const codeFile of codeFiles) {
updateFileReferences(codeFile, replacements);
}PNG to WebP
convertToWebp.mjs · Node.js
PNG to WebP
Converts PNGs under a folder to WebP with sharp, reports space saved, and rewrites imports across the repo.
import readline from 'node:readline';import path from 'node:path';import fs from 'node:fs';import sharp from 'sharp';import { fdir } from 'fdir';const rl = readline.createInterface({ input: process.stdin, output: process.stdout,});function prompt(question) { return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())));}function getDirSize(dirPath) { let total = 0; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { total += getDirSize(fullPath); } else { total += fs.statSync(fullPath).size; } } return total;}function formatSize(bytes) { const kb = bytes / 1024; const mb = kb / 1024; return mb >= 1 ? `${mb.toFixed(2)} MB` : `${kb.toFixed(2)} KB`;}async function convertToWebp(inputPath) { const outputPath = inputPath.replace(/\.png$/i, '.webp'); try { await sharp(inputPath).webp({ lossless: false }).toFile(outputPath); fs.unlinkSync(inputPath); console.log(`✅ Converted and removed: ${inputPath}`); return outputPath; } catch (error) { console.error(`❌ Conversion failed for ${inputPath}:`, error.message); return null; }}function findCodeFiles(rootDir) { return new fdir() .withFullPaths() .filter(f => /\.(js|ts|jsx|tsx|vue|html|css|scss)$/i.test(f) && !/node_modules|\.git|dist|\.nuxt|\.output/.test(f), ) .crawl(rootDir) .sync();}function updateFileReferences(codeFile, replacements) { let content = fs.readFileSync(codeFile, 'utf-8'); let changed = false; const updatedRefs = []; for (const { original, updated } of replacements) { const escapedOriginal = original.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); const regex = new RegExp(escapedOriginal, 'g'); if (regex.test(content)) { content = content.replace(regex, updated); changed = true; updatedRefs.push({ from: original, to: updated }); } } if (changed) { fs.writeFileSync(codeFile, content, 'utf-8'); console.log(`🔁 Updated: ${codeFile}`); updatedRefs.forEach(({ from, to }) => { console.log(` → ${from} → ${to}`); }); }}async function main() { const imageRoot = await prompt('Enter the image directory (e.g., ./public/images): '); const resolvedImageRoot = path.resolve(imageRoot); const projectRoot = process.cwd(); if (!fs.existsSync(resolvedImageRoot) || !fs.statSync(resolvedImageRoot).isDirectory()) { console.error('❌ Invalid image directory.'); rl.close(); return; } const allImages = new fdir() .withFullPaths() .filter(f => /\.png$/i.test(f) && !/node_modules|\.git|dist|\.nuxt|\.output/.test(f)) .crawl(resolvedImageRoot) .sync(); if (allImages.length === 0) { console.log('No .png images found.'); rl.close(); return; } const sizeBefore = getDirSize(resolvedImageRoot); const replacements = []; for (const imgPath of allImages) { const newPath = await convertToWebp(imgPath); if (newPath) { const relFromPublic = path.relative(path.join(projectRoot, imageRoot), imgPath).replace(/\\/g, '/'); const altPaths = [ `${imageRoot}/${relFromPublic}`, `/${imageRoot}/${relFromPublic}`, `/images/${relFromPublic}`, `images/${relFromPublic}`, relFromPublic, relFromPublic.replace(/^images\//, ''), ]; for (const alt of altPaths) { replacements.push({ original: alt.replace(/\.png$/i, '.png'), updated: alt.replace(/\.png$/i, '.webp'), }); } } } const sizeAfter = getDirSize(resolvedImageRoot); const sizeSaved = sizeBefore - sizeAfter; console.log(`\n📦 Directory size before: ${formatSize(sizeBefore)}`); console.log(`📦 Directory size after: ${formatSize(sizeAfter)}`); console.log(`💡 Space saved: ${formatSize(sizeSaved)}\n`); if (replacements.length === 0) { console.log('No images were converted. Nothing to update.'); rl.close(); return; } const codeFiles = findCodeFiles(projectRoot); for (const codeFile of codeFiles) { updateFileReferences(codeFile, replacements); } rl.close();}main();Part of the utils collection on GitHub.