const sharp = require('sharp') const fs = require('fs') const path = require('path') const IMG_DIR = path.join(__dirname, 'src', 'img') const QUALITY = 60 // JPG 质量 const MAX_WIDTH = 800 // 最长边限制 const MAX_FILE_KB = 80 // 单张上限 KB —— 强制压到 80KB 以下 /** * 压缩单个文件(强制覆盖) */ async function compressFile(inputPath) { const ext = path.extname(inputPath).toLowerCase() const dir = path.dirname(inputPath) const base = path.basename(inputPath, ext) const finalPath = path.join(dir, base + '.jpg') const isAlreadyJpg = ext === '.jpg' || ext === '.jpeg' const stat = fs.statSync(inputPath) const currentKb = stat.size / 1024 // 如果已经 .jpg、体积已小于上限、且最长边不超,跳过 if (isAlreadyJpg && currentKb <= MAX_FILE_KB) { const meta = await sharp(inputPath).metadata() const longEdge = Math.max(meta.width || 0, meta.height || 0) if (longEdge <= MAX_WIDTH) { console.log(`skip ${inputPath} (${currentKb.toFixed(1)}KB, ${longEdge}px)`) return { from: inputPath, to: inputPath, size: stat.size } } } // 重建 pipeline const meta = await sharp(inputPath).metadata() const longEdge = Math.max(meta.width || 0, meta.height || 0) const resizeOpts = longEdge > MAX_WIDTH ? { width: MAX_WIDTH, height: MAX_WIDTH, fit: 'inside' } : undefined let pipeline = sharp(inputPath).jpeg({ quality: QUALITY, progressive: true, force: true }) if (resizeOpts) pipeline = pipeline.resize(resizeOpts) const tmpPath = path.join(dir, base + '_tmp.jpg') await pipeline.toFile(tmpPath) fs.unlinkSync(inputPath) fs.renameSync(tmpPath, finalPath) const newSize = fs.statSync(finalPath).size console.log( `${isAlreadyJpg ? 'compress' : 'convert'} ${base}${ext} (${currentKb.toFixed(1)}KB, ${longEdge}px → ${(newSize / 1024).toFixed(1)}KB)` ) return { from: inputPath, to: finalPath, size: newSize } } /** * 递归收集所有图片 */ function collectImages(dir) { let results = [] const entries = fs.readdirSync(dir, { withFileTypes: true }) for (const entry of entries) { const full = path.join(dir, entry.name) if (entry.isDirectory()) { results = results.concat(collectImages(full)) } else if (/\.(png|jpe?g)$/i.test(entry.name)) { results.push(full) } } return results } /** * 主函数 */ async function main() { console.log(`scanning ${IMG_DIR}...\n`) const images = collectImages(IMG_DIR) console.log(`found ${images.length} images\n`) let totalBefore = 0 let totalAfter = 0 for (const img of images) { totalBefore += fs.statSync(img).size const result = await compressFile(img) totalAfter += result.size } console.log(`\n✅ done`) console.log(` before: ${(totalBefore / 1024 / 1024).toFixed(2)} MB`) console.log(` after: ${(totalAfter / 1024 / 1024).toFixed(2)} MB`) console.log(` saved: ${((totalBefore - totalAfter) / 1024 / 1024).toFixed(2)} MB`) } main().catch(err => { console.error(err) process.exit(1) })