| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- 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)
|