UNPKG

minify-pic-cli

Version:

一个简单易用的图片批量压缩命令行工具,支持 PNG、JPEG、GIF 格式,适合前端和设计师快速优化图片体积。

163 lines (145 loc) 5.41 kB
#!/usr/bin/env node const fs = require("fs"); const path = require("path"); const sharp = require("sharp"); const readline = require("readline"); const { Command } = require("commander"); // 默认配置参数 const DEFAULT_CONFIG = { targetDir: process.cwd(), // 需要压缩的目录(可包含子目录) outputDir: path.join(process.cwd(), "output"), // 输出目录 quality: 80, // 压缩质量 0 - 100 gifColours: 128, // GIF调色板最大数量 blackDirs: ["no"], // 排除的子文件夹名称 }; // 询问用户是否继续 function askUserToContinue() { return new Promise((resolve) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); console.log(`当前目录路径为: ${process.cwd()}`); rl.question("是否需要压缩当前目录的所有图片?Y/N:", (answer) => { rl.close(); resolve(answer.trim().toLowerCase() === "y"); }); }); } // 获取文件大小(带单位,自动转换MB/KB) function getFileSizeWithUnit(filePath) { const stats = fs.statSync(filePath); const sizeInBytes = stats.size; const sizeInMB = sizeInBytes / 1024 / 1024; if (sizeInMB >= 1) { return sizeInMB.toFixed(2) + "MB"; } else { const sizeInKB = sizeInBytes / 1024; if (sizeInKB < 0.01 && sizeInBytes > 0) { return "<0.01KB"; } return sizeInKB.toFixed(2) + "KB"; } } // 压缩单张图片 async function compressImage(filePath, config, baseDir = config.targetDir) { const beforeSize = getFileSizeWithUnit(filePath); sharp.cache(false); const ext = path.extname(filePath).toLowerCase(); let sharpInstance; switch (ext) { case ".png": sharpInstance = sharp(filePath).png({ quality: config.quality }); break; case ".jpg": case ".jpeg": sharpInstance = sharp(filePath).jpeg({ quality: config.quality }); break; case ".gif": sharpInstance = sharp(filePath, { animated: true, limitInputPixels: false, }).gif({ colours: config.gifColours }); break; default: throw new Error(`不支持的文件类型: ${ext}`); } // 保持目录结构 const relativePath = path.relative(baseDir, filePath); const outputFilePath = path.join(config.outputDir, relativePath); const outputFileDir = path.dirname(outputFilePath); try { if (!fs.existsSync(outputFileDir)) { fs.mkdirSync(outputFileDir, { recursive: true }); } const buffer = await sharpInstance.toBuffer(); fs.writeFileSync(outputFilePath, buffer); fs.chmodSync(outputFilePath, 0o646); const afterSize = getFileSizeWithUnit(outputFilePath); console.log( `压缩完成 [大小变化: ${beforeSize} ---->>>> ${afterSize}] ${outputFilePath}` ); } catch (error) { console.error(`压缩出错: ${outputFilePath}`, error); } } // 递归压缩目录下所有图片 async function compressFiles(dir, config, baseDir = config.targetDir) { const files = fs.readdirSync(dir); for (const file of files) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); // 跳过 output 目录 if (filePath === config.outputDir) { console.log(`跳过的目录: ${filePath}(为输出目录)`); continue; } if (stat.isDirectory()) { if (!config.blackDirs.includes(file)) { await compressFiles(filePath, config, baseDir); } else { console.log(`跳过的目录: ${filePath}`); } } else if (/\.(png|jpe?g|gif)$/i.test(filePath)) { await compressImage(filePath, config, baseDir); } } } // commander命令行包装 const program = new Command(); program .name("mpic") .description("图片批量压缩工具") .option("-d, --dir <dir>", "需要压缩的目录", DEFAULT_CONFIG.targetDir) .option("-o, --output <output>", "输出目录", DEFAULT_CONFIG.outputDir) .option("-q, --quality <quality>", "压缩质量(0-100)", String(DEFAULT_CONFIG.quality)) .option("-g, --gif-colours <colours>", "GIF调色板最大数量(2-256)", String(DEFAULT_CONFIG.gifColours)) .option("-b, --black-dirs <dirs>", "排除的子文件夹名称(逗号分隔)", val => val.split(","), DEFAULT_CONFIG.blackDirs) .version(require('./package.json').version, '-v, --version', '显示版本号') .action(async (options) => { // 合并配置 const config = { targetDir: path.resolve(process.cwd(), options.dir), outputDir: path.resolve(process.cwd(), options.output), quality: parseInt(options.quality, 10), gifColours: parseInt(options.gifColours, 10), blackDirs: Array.isArray(options.blackDirs) ? options.blackDirs : [options.blackDirs], }; let shouldContinue = options.yes; if (!shouldContinue) { shouldContinue = await askUserToContinue(); } if (!shouldContinue) { console.log("已取消操作。"); process.exit(0); } const inputDir = config.targetDir; compressFiles(inputDir, config, inputDir) .then(() => { console.log("压缩任务全部完成,已输出至", config.outputDir); }) .catch((err) => { console.error("压缩文件时出错:", err); }); }); program.parse(process.argv);