133 lines
4.7 KiB
JavaScript
133 lines
4.7 KiB
JavaScript
import sharp from 'sharp';
|
||
import { promises as fs } from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
import { glob } from 'glob';
|
||
import os from 'os';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
|
||
const sourceDir = 'public/images';
|
||
const outputDir = 'dist/images';
|
||
|
||
// 统计数据
|
||
let totalFiles = 0;
|
||
let totalOriginalSize = 0;
|
||
let totalOptimizedSize = 0;
|
||
let successCount = 0;
|
||
let failCount = 0;
|
||
let startTime = Date.now();
|
||
|
||
// 文件大小阈值(字节)
|
||
const SMALL_FILE_THRESHOLD = 5 * 1024; // 5KB
|
||
|
||
async function optimizeImage(inputPath) {
|
||
const relativePath = path.relative(sourceDir, inputPath);
|
||
const outputPath = path.join(outputDir, relativePath);
|
||
const outputDirPath = path.dirname(outputPath);
|
||
|
||
await fs.mkdir(outputDirPath, { recursive: true });
|
||
|
||
try {
|
||
const inputStats = await fs.stat(inputPath);
|
||
const isSmallFile = inputStats.size < SMALL_FILE_THRESHOLD;
|
||
|
||
// 根据文件大小选择不同的压缩策略
|
||
let sharpInstance = sharp(inputPath);
|
||
|
||
if (isSmallFile) {
|
||
// 小文件使用无损压缩策略
|
||
await sharpInstance
|
||
.png({
|
||
compressionLevel: 9,
|
||
effort: 10,
|
||
palette: false // 不使用调色板模式
|
||
})
|
||
.toFile(outputPath);
|
||
} else {
|
||
// 大文件使用有损压缩策略
|
||
await sharpInstance
|
||
.png({
|
||
quality: 80,
|
||
compressionLevel: 9,
|
||
palette: true,
|
||
colors: 256,
|
||
dither: 0.5,
|
||
effort: 10
|
||
})
|
||
.toFile(outputPath);
|
||
}
|
||
|
||
const outputStats = await fs.stat(outputPath);
|
||
const savings = ((inputStats.size - outputStats.size) / inputStats.size * 100).toFixed(2);
|
||
|
||
// 如果优化后文件更大,使用原始文件
|
||
if (outputStats.size > inputStats.size) {
|
||
await fs.copyFile(inputPath, outputPath);
|
||
console.log(`⚠️ ${relativePath} (使用原始文件)`);
|
||
} else {
|
||
console.log(`✓ ${relativePath}`);
|
||
}
|
||
|
||
// 更新统计数据
|
||
totalOriginalSize += inputStats.size;
|
||
totalOptimizedSize += Math.min(inputStats.size, outputStats.size);
|
||
successCount++;
|
||
|
||
console.log(` 原始大小: ${(inputStats.size / 1024).toFixed(2)}KB`);
|
||
console.log(` 优化大小: ${(Math.min(inputStats.size, outputStats.size) / 1024).toFixed(2)}KB`);
|
||
console.log(` 节省: ${((inputStats.size - Math.min(inputStats.size, outputStats.size)) / inputStats.size * 100).toFixed(2)}%\n`);
|
||
} catch (error) {
|
||
console.error(`✗ 处理 ${relativePath} 时出错:`, error);
|
||
failCount++;
|
||
}
|
||
}
|
||
|
||
function printReport() {
|
||
const endTime = Date.now();
|
||
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
||
const totalSavings = ((totalOriginalSize - totalOptimizedSize) / totalOriginalSize * 100).toFixed(2);
|
||
|
||
console.log('\n========== 优化结果报告 ==========');
|
||
console.log(`处理总文件数: ${totalFiles}`);
|
||
console.log(`成功处理: ${successCount} 个文件`);
|
||
console.log(`处理失败: ${failCount} 个文件`);
|
||
console.log(`原始总大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
|
||
console.log(`优化后总大小: ${(totalOptimizedSize / 1024 / 1024).toFixed(2)}MB`);
|
||
console.log(`总体积减少: ${totalSavings}%`);
|
||
console.log(`处理耗时: ${duration}秒`);
|
||
console.log(`CPU核心数: ${os.cpus().length}`);
|
||
console.log('================================\n');
|
||
}
|
||
|
||
async function processImages() {
|
||
try {
|
||
// 清空输出目录
|
||
await fs.rm(outputDir, { recursive: true, force: true });
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
|
||
// 获取所有PNG文件
|
||
const files = await glob(path.join(sourceDir, '**/*.png'));
|
||
totalFiles = files.length;
|
||
|
||
console.log(`找到 ${files.length} 个PNG文件需要优化\n`);
|
||
|
||
// 使用工作池并行处理图片,限制并发数为CPU核心数
|
||
const concurrency = os.cpus().length;
|
||
const chunks = [];
|
||
for (let i = 0; i < files.length; i += concurrency) {
|
||
chunks.push(files.slice(i, i + concurrency));
|
||
}
|
||
|
||
for (const chunk of chunks) {
|
||
await Promise.all(chunk.map(file => optimizeImage(file)));
|
||
}
|
||
|
||
printReport();
|
||
} catch (error) {
|
||
console.error('处理过程中发生错误:', error);
|
||
}
|
||
}
|
||
|
||
processImages();
|