import sharp from 'sharp' import { readdir, stat, rename, unlink } from 'fs/promises' import { join, extname } from 'path' const IMAGE_DIR = 'src/assets/images' const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg'] const SIZE_THRESHOLD = 500 * 1024 // 500KB // 计算文件大小(KB) function formatSize(bytes) { return (bytes / 1024).toFixed(2) + ' KB' } // 压缩单个图片 - 更激进的压缩策略 async function compressImage(fileName) { const ext = extname(fileName).toLowerCase() const inputPath = join(IMAGE_DIR, fileName) const tempPath = join(IMAGE_DIR, 'temp_' + fileName) try { const inputStats = await stat(inputPath) const inputSize = inputStats.size // 如果文件小于 500KB,跳过 if (inputSize < SIZE_THRESHOLD) { console.log(`⏭️ ${fileName} (${formatSize(inputSize)}) - 跳过`) return null } let pipeline = sharp(inputPath) // 首先调整尺寸(如果图片太大) const metadata = await pipeline.metadata() const maxWidth = 1920 const maxHeight = 1080 if (metadata.width > maxWidth || metadata.height > maxHeight) { pipeline = pipeline.resize(maxWidth, maxHeight, { fit: 'inside', withoutEnlargement: true }) } // 根据图片类型设置更激进的压缩参数 if (ext === '.png') { // PNG 转为 PNG 但使用更激进的设置 pipeline = pipeline.png({ compressionLevel: 9, palette: true, quality: 60, dither: 1 }) } else if (ext === '.jpg' || ext === '.jpeg') { pipeline = pipeline.jpeg({ quality: 70, mozjpeg: true, progressive: true }) } // 执行压缩到临时文件 await pipeline.toFile(tempPath) const outputStats = await stat(tempPath) const outputSize = outputStats.size // 如果压缩后仍然很大,考虑转为 WebP if (outputSize > SIZE_THRESHOLD && ext === '.png') { console.log(`🔄 ${fileName} - 转为 WebP 格式`) await pipeline.webp({ quality: 75 }).toFile(tempPath) const webpStats = await stat(tempPath) console.log(` 原始:${formatSize(inputSize)} → WebP: ${formatSize(webpStats.size)}`) } // 删除原文件并重命名临时文件 await unlink(inputPath) await rename(tempPath, inputPath) const finalStats = await stat(inputPath) const finalSize = finalStats.size const savings = ((inputSize - finalSize) / inputSize * 100).toFixed(1) console.log(`✅ ${fileName}`) console.log(` 原始:${formatSize(inputSize)} → 压缩后:${formatSize(finalSize)} (节省 ${savings}%)`) return { inputSize, outputSize: finalSize, savings } } catch (error) { console.error(`❌ 压缩失败 ${fileName}:`, error.message) // 清理临时文件(如果存在) try { await unlink(tempPath) } catch (e) { // 忽略 } return null } } // 主函数 async function main() { console.log('🚀 开始优化大图片(>500KB)...\n') const files = await readdir(IMAGE_DIR) const imageFiles = files.filter(file => IMAGE_EXTENSIONS.includes(extname(file).toLowerCase()) ) if (imageFiles.length === 0) { console.log('⚠️ 未找到图片') return } // 先找出所有大于 500KB 的图片 const largeImages = [] for (const file of imageFiles) { const stats = await stat(join(IMAGE_DIR, file)) if (stats.size > SIZE_THRESHOLD) { largeImages.push({ name: file, size: stats.size }) } } if (largeImages.length === 0) { console.log('✅ 所有图片都已小于 500KB,无需进一步优化!') return } console.log(`📁 找到 ${largeImages.length} 张大图片需要优化:\n`) largeImages.forEach(img => { console.log(` - ${img.name}: ${formatSize(img.size)}`) }) console.log() let totalInput = 0 let totalOutput = 0 let successCount = 0 for (const file of imageFiles) { const result = await compressImage(file) if (result) { totalInput += result.inputSize totalOutput += result.outputSize successCount++ console.log() } } if (successCount === 0) { console.log('\n✅ 没有需要优化的大图片') return } const totalSavings = ((totalInput - totalOutput) / totalInput * 100).toFixed(1) console.log('='.repeat(50)) console.log(`📊 优化完成!`) console.log(` 成功:${successCount} 张`) console.log(` 原始总大小:${formatSize(totalInput)}`) console.log(` 优化后总大小:${formatSize(totalOutput)}`) console.log(` 总计节省:${totalSavings}% (${formatSize(totalInput - totalOutput)})`) console.log('='.repeat(50)) } // 运行 main().catch(console.error)