{"_id":"@awesome-compressor/node-compress-image","name":"@awesome-compressor/node-compress-image","dist-tags":{"latest":"0.0.4"},"versions":{"0.0.4":{"name":"@awesome-compressor/node-compress-image","type":"module","version":"0.0.4","description":"Node.js image compressor and optimizer for JPEG, PNG, WebP and AVIF with Sharp, imagemin, Jimp and Tinify","author":{"name":"Simon He"},"license":"MIT","funding":"https://github.com/sponsors/awesome-compressor","homepage":"https://github.com/awesome-compressor/node-compress-image#readme","repository":{"type":"git","url":"git+https://github.com/awesome-compressor/node-compress-image.git"},"bugs":{"url":"https://github.com/awesome-compressor/node-compress-image/issues"},"keywords":["avif","batch-image-compression","core-web-vitals","image-compression","image-compressor","image-optimization","image-optimizer","imagemin","image","jimp","node-image-compression","nodejs","optimize","responsive-images","seo","sharp","tinify","tinypng","typescript","webp"],"sideEffects":false,"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","typesVersions":{"*":{"*":["./dist/*","./dist/index.d.ts"]}},"engines":{"node":">=18.17.0"},"publishConfig":{"access":"public"},"peerDependencies":{"jimp":"^0.22.12","tinify":"^1.8.1"},"peerDependenciesMeta":{"jimp":{"optional":true},"tinify":{"optional":true}},"dependencies":{"@types/node":"^18.19.118","imagemin":"^8.0.1","imagemin-gifsicle":"^7.0.0","imagemin-mozjpeg":"^10.0.0","imagemin-pngquant":"^9.0.2","imagemin-webp":"^8.0.0","sharp":"^0.33.5"},"devDependencies":{"@antfu/eslint-config":"^4.17.0","@sxzz/prettier-config":"^2.2.3","@types/imagemin":"^9.0.1","bumpp":"^8.2.1","eslint":"^9.31.0","esno":"^4.8.0","jimp":"^0.22.12","lint-staged":"^13.3.0","picocolors":"^1.1.1","prettier":"^3.6.2","rimraf":"^3.0.2","tinify":"^1.8.1","tsdown":"^0.9.9","typescript":"^5.8.3","vitest":"^3.2.4"},"lint-staged":{"*":["prettier --write --cache --ignore-unknown"],"*.{vue,js,ts,jsx,tsx,md,json}":"eslint --fix"},"prettier":"@sxzz/prettier-config","scripts":{"build":"tsdown","dev":"npm run build -- --watch src","format":"prettier --cache --write .","lint":"eslint . --cache","lint:fix":"npm run lint --fix","postbuild":"node -e \"const fs = require('node:fs'); for (const file of ['dist/index.d.ts', 'dist/index.d.cts']) { const ref = '/// <reference types=\\\"node\\\" />\\n'; const content = fs.readFileSync(file, 'utf8'); if (!content.startsWith(ref)) fs.writeFileSync(file, ref + content); }\"","release":"bumpp && npm publish","start":"esno src/index.ts","test":"vitest","test:run":"vitest run","test:tools":"vitest run test/tools/","test:tools-watch":"vitest test/tools/","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","example":"esno examples/basic.ts","example:all":"esno examples/all-tools.ts","example:seo":"esno examples/seo.ts"},"_id":"@awesome-compressor/node-compress-image@0.0.4","_integrity":"sha512-MJRhu6m2x9JgF4c7ZIO8oii30aOGLHOQlI3PTWhJiog5igv8sG5Sbzz+oGAPDIrkSJ7jD/NEPr1iRYyKMTKFQQ==","_resolved":"/private/var/folders/z8/w1qvd0cd46z6k8_swl1n33d00000gn/T/5ec17466c24a0c2b3a7f4e8baeed5c5c/awesome-compressor-node-compress-image-0.0.4.tgz","_from":"file:awesome-compressor-node-compress-image-0.0.4.tgz","_nodeVersion":"23.11.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-MJRhu6m2x9JgF4c7ZIO8oii30aOGLHOQlI3PTWhJiog5igv8sG5Sbzz+oGAPDIrkSJ7jD/NEPr1iRYyKMTKFQQ==","shasum":"e2fa3687024772f43ba98b2e247ff6753b487074","tarball":"https://registry.npmjs.org/@awesome-compressor/node-compress-image/-/node-compress-image-0.0.4.tgz","fileCount":16,"unpackedSize":120784,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDi1+bFCnOh2BBPXO/Jdvsu4xWL+FTY2HzeBDqFcW1EmgIgULK2xAeTsEda75x/XzgM9PCcRRpg3Skby2pdKaud4ug="}]},"_npmUser":{"name":"simon_he","email":"13917107469@163.com"},"directories":{},"maintainers":[{"name":"simon_he","email":"13917107469@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/node-compress-image_0.0.4_1776939657428_0.1258432152969038"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-23T10:20:57.312Z","0.0.4":"2026-04-23T10:20:57.600Z","modified":"2026-04-23T10:20:57.917Z"},"maintainers":[{"name":"simon_he","email":"13917107469@163.com"}],"description":"Node.js image compressor and optimizer for JPEG, PNG, WebP and AVIF with Sharp, imagemin, Jimp and Tinify","homepage":"https://github.com/awesome-compressor/node-compress-image#readme","keywords":["avif","batch-image-compression","core-web-vitals","image-compression","image-compressor","image-optimization","image-optimizer","imagemin","image","jimp","node-image-compression","nodejs","optimize","responsive-images","seo","sharp","tinify","tinypng","typescript","webp"],"repository":{"type":"git","url":"git+https://github.com/awesome-compressor/node-compress-image.git"},"author":{"name":"Simon He"},"bugs":{"url":"https://github.com/awesome-compressor/node-compress-image/issues"},"license":"MIT","readme":"# Node Compress Image\n\n一个集成多个Node.js压缩库的通用图像压缩工具，自动选择最优压缩结果。\n\nNode.js image compressor and image optimizer for JPEG, PNG, WebP and AVIF. Suitable for SEO image optimization, Core Web Vitals, responsive images, static site builds, CMS uploads and asset pipelines.\n\n## 特性\n\n- 🚀 **多工具集成**: 支持 Sharp、ImageMin、JIMP、Tinify 四种主流压缩工具\n- 🎯 **智能选择**: 自动比对多个工具的压缩结果，返回最优压缩效果\n- 📊 **详细统计**: 提供压缩时间、压缩率、工具性能等详细统计信息\n- 🔧 **灵活配置**: 支持质量、尺寸等多种压缩选项\n- 📦 **按需安装**: JIMP 和 Tinify 为可选 peer dependency，可以只使用 Sharp 和 ImageMin 核心功能\n- 🔄 **多种输出**: 支持 Buffer、Base64、Blob、File 等多种输出格式\n- 🔍 **向后兼容**: 支持传统参数格式，平滑迁移\n- 🌐 **格式齐全**: 支持 JPEG、PNG、WebP、GIF 等主流图像格式\n- 🧭 **SEO 友好**: 支持 WebP / AVIF 变体、目标体积、批处理缓存和可解释压缩结果\n\n## 适用场景\n\n- 网站首屏图、文章封面、商品图、头像等静态资源优化\n- 为 `<picture>` 生成 AVIF / WebP / JPEG fallback，提高 LCP 和 Core Web Vitals 表现\n- CMS 上传链路中按目标体积压缩图片，避免用户上传超大图\n- 静态站点或文档站构建时批量压缩图片，并通过缓存跳过未变化文件\n- 需要保留版权/拍摄信息时，只让支持元数据的工具参与压缩\n\n## 安装\n\n需要 Node.js 18.17 或更高版本。\n\n```bash\nnpm install @awesome-compressor/node-compress-image\n```\n\nnpm 搜索关键词：`node image compression`、`image optimizer`、`sharp imagemin`、`webp avif`、`seo image optimization`。\n\n### 安装可选工具\n\n```bash\n# 基础工具（已包含在核心依赖中）\n# Sharp - 高性能图像处理\n# ImageMin - 专业图像优化\n\n# 可选工具\nnpm install jimp                # 纯 JavaScript 实现，无系统依赖\nnpm install tinify              # TinyPNG/TinyJPG 官方 API，需要 API 密钥\n```\n\n## 快速开始\n\n```typescript\nimport fs from 'node:fs'\nimport {\n  compress,\n  compressDetailed,\n  compressFiles,\n  compressVariants,\n  compressWithStats,\n  getCapabilities,\n} from '@awesome-compressor/node-compress-image'\n\n// 基础使用\nconst imageBuffer = fs.readFileSync('input.jpg')\nconst compressedBuffer = await compress(imageBuffer, { quality: 0.8 })\nfs.writeFileSync('output.jpg', compressedBuffer)\n\n// 获取详细统计信息\nconst stats = await compressWithStats(imageBuffer, { quality: 0.8 })\nconsole.log(`最佳工具: ${stats.bestTool}`)\nconsole.log(`压缩率: ${stats.compressionRatio.toFixed(1)}%`)\nconsole.log(`总耗时: ${stats.totalDuration}ms`)\n```\n\n## API\n\n### compress(file, options)\n\n主压缩函数，支持多种重载形式。\n\n#### 参数\n\n- `file`: `Buffer | FileInterface | BlobInterface` - 输入的图像文件\n- `options`: `CompressOptions` - 压缩选项\n\n#### CompressOptions\n\n```typescript\ninterface ToolConfig {\n  name: string\n  key?: string\n  [key: string]: any\n}\n\ninterface CompressOptions<T extends 'buffer' | 'base64' | 'blob' | 'file' = 'buffer' | 'base64' | 'blob' | 'file'> {\n  quality?: number // 压缩质量 0-1，默认 0.6\n  targetWidth?: number // 目标宽度\n  targetHeight?: number // 目标高度\n  maxWidth?: number // 最大宽度\n  maxHeight?: number // 最大高度\n  preserveExif?: boolean // 尝试保留EXIF信息，默认 false；目前仅 Sharp 生效\n  metadata?: 'strip' | 'exif' | 'all' // 元数据策略，默认 'strip'\n  format?: 'same' | 'auto' | 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' // 输出格式\n  targetBytes?: number // 目标输出体积\n  strategy?: 'smallest' | 'balanced' | 'preserveFormat' | 'targetSize' // 结果选择策略\n  returnAllResults?: boolean // 是否返回所有工具结果，默认 false\n  type?: T // 返回类型，默认 'buffer'\n  toolConfigs?: ToolConfig[] // 工具特定配置，例如 Tinify API key\n}\n\ntype CompressionStatsOptions = Omit<CompressOptions, 'returnAllResults' | 'type'>\n```\n\n#### 示例\n\n```typescript\n// 基础压缩\nconst compressed = await compress(imageBuffer, { quality: 0.8 })\n\n// 调整尺寸\nconst resized = await compress(imageBuffer, {\n  quality: 0.8,\n  maxWidth: 800,\n  maxHeight: 600\n})\n\n// 返回Base64格式\nconst base64 = await compress(imageBuffer, {\n  quality: 0.8,\n  type: 'base64'\n})\n\n// 获取所有工具的压缩结果\nconst allResults = await compress(imageBuffer, {\n  quality: 0.8,\n  returnAllResults: true\n})\n\nconsole.log('最佳结果:', allResults.bestResult)\nconsole.log('最佳工具:', allResults.bestTool)\nconsole.log('所有结果:', allResults.allResults)\n\n// 转换输出格式\nconst webp = await compress(imageBuffer, {\n  quality: 0.8,\n  format: 'webp'\n})\n\n// 生成多格式变体\nconst variants = await compressVariants(imageBuffer, {\n  quality: 0.8,\n  formats: ['webp', 'avif']\n})\n```\n\n## SEO 图片优化\n\n图片 SEO 不只是压缩体积，还包括稳定的输出格式、合理尺寸、现代格式 fallback、文件名语义和可缓存构建结果。这个库负责图片二进制优化，`alt` 文案、页面结构和 CDN 缓存策略仍应在业务层处理。\n\n### 生成 `<picture>` 多格式资源\n\n```typescript\nimport fs from 'node:fs/promises'\nimport { compressDetailed, compressVariants } from '@awesome-compressor/node-compress-image'\n\nconst input = await fs.readFile('./assets/hero-original.jpg')\nconst baseName = 'modern-office-dashboard'\n\nconst fallback = await compressDetailed(input, {\n  quality: 0.82,\n  maxWidth: 1200,\n  format: 'jpeg',\n  targetBytes: 220 * 1024,\n  metadata: 'strip'\n})\n\nconst variants = await compressVariants(input, {\n  quality: 0.82,\n  maxWidth: 1200,\n  formats: ['avif', 'webp'],\n  type: 'buffer',\n  metadata: 'strip'\n})\n\nawait fs.mkdir('./public/images', { recursive: true })\nawait fs.writeFile(`./public/images/${baseName}.jpg`, fallback.output)\n\nfor (const variant of variants) {\n  await fs.writeFile(`./public/images/${baseName}.${variant.format}`, variant.result.output)\n}\n\nconst picture = `\n<picture>\n  <source srcset=\"/images/${baseName}.avif\" type=\"image/avif\">\n  <source srcset=\"/images/${baseName}.webp\" type=\"image/webp\">\n  <img\n    src=\"/images/${baseName}.jpg\"\n    width=\"${fallback.metadata.output.width ?? 1200}\"\n    height=\"${fallback.metadata.output.height ?? 630}\"\n    alt=\"Modern office dashboard analytics\"\n    loading=\"lazy\"\n    decoding=\"async\"\n  >\n</picture>`\n\nconsole.log(picture)\n```\n\n### 按目标体积处理 CMS 上传图\n\n```typescript\nimport { compressDetailed } from '@awesome-compressor/node-compress-image'\n\nconst result = await compressDetailed(uploadedImageBuffer, {\n  quality: 0.9,\n  maxWidth: 1600,\n  targetBytes: 300 * 1024,\n  strategy: 'targetSize',\n  format: 'auto',\n  metadata: 'strip'\n})\n\nconsole.log(result.format)\nconsole.log(result.compressedSize)\nconsole.log(result.selectedReason)\nconsole.log(result.warnings)\n```\n\n### 静态站点构建时批量压缩\n\n```typescript\nimport { glob } from 'node:fs/promises'\nimport { compressFiles } from '@awesome-compressor/node-compress-image'\n\nconst inputFiles = []\n\nfor await (const file of glob('./content/**/*.{jpg,jpeg,png,webp}')) {\n  inputFiles.push(file)\n}\n\nconst results = await compressFiles(inputFiles, {\n  outputDir: './public/images',\n  cacheFile: './public/images/.compress-cache.json',\n  quality: 0.82,\n  maxWidth: 1600,\n  format: 'webp',\n  concurrency: 4,\n  metadata: 'strip'\n})\n\nconsole.table(results.map(result => ({\n  file: result.inputPath,\n  output: result.outputPath,\n  skipped: result.skipped,\n  size: result.compressedSize,\n  reason: result.selectedReason\n})))\n```\n\n### SEO 图片优化检查清单\n\n- 优先为大图生成 AVIF / WebP，同时保留 JPEG 或 PNG fallback。\n- 输出图片宽高应匹配页面实际展示尺寸，避免浏览器下载后再缩小。\n- 对首屏图控制目标体积，例如 `targetBytes: 200 * 1024`。\n- 文件名使用可读语义，例如 `product-dashboard-analytics.webp`，不要只用哈希。\n- 页面层必须提供准确 `alt`，库不会自动生成文案。\n- 默认使用 `metadata: 'strip'` 减少体积；需要版权或拍摄信息时使用 `metadata: 'exif'` 或 `metadata: 'all'`。\n- 批处理构建应启用 `cacheFile`，避免 CI/CD 每次重复压缩未变化图片。\n\n### 能力探测\n\n```typescript\nconst capabilities = await getCapabilities()\n\nconsole.log(capabilities.inputFormats)\nconsole.log(capabilities.outputFormats)\nconsole.log(capabilities.tools)\n```\n\n`getCapabilities()` 会返回当前环境里各工具是否安装、是否完成配置、支持哪些输入/输出格式、是否支持质量、尺寸和元数据能力。Tinify 只有在安装依赖并提供 API key 后才会标记为 configured。\n\n### quality 语义\n\n`quality` 是 0 到 1 的统一入口，超出范围会抛错。各工具会按自身能力映射：\n\n| 工具 | 映射方式 |\n|------|----------|\n| Sharp | JPEG / PNG / WebP 映射为 1-100；`preserveExif` 仅通过 Sharp 生效 |\n| ImageMin | JPEG 使用 mozjpeg quality，PNG 使用 pngquant quality，WebP 使用 webp quality，GIF 映射为颜色数 |\n| JIMP | 映射为 JIMP quality，主要影响 JPEG 输出 |\n| Tinify | TinyPNG API 自动决策，`quality` 不保证直接生效 |\n\n未识别的输入格式会直接抛错，不会再默认当作 JPEG 处理。\n\n### 输出格式\n\n`format` 控制输出格式：\n\n| 值 | 说明 |\n|----|------|\n| `same` | 保持工具输出格式，默认值 |\n| `jpeg` / `png` / `webp` / `avif` / `gif` | 强制输出指定格式 |\n| `auto` | 在原格式、WebP、AVIF 中选择体积最小的结果 |\n\n`compressVariants()` 用于一次生成多个格式变体，适合 Web 服务同时产出 WebP / AVIF fallback。\n\n### 尺寸参数\n\n`targetWidth`、`targetHeight`、`maxWidth`、`maxHeight` 必须是正整数，非法值会抛错，避免不同压缩引擎各自处理出不一致的结果。\n\n### 选项校验\n\n`preserveExif` 和 `returnAllResults` 必须是布尔值，`toolConfigs` 必须是数组。`compressWithStats()` 不支持 `type` 或 `returnAllResults`，因为它固定返回统计对象。\n\n### 结果选择策略\n\n默认策略是从成功的工具结果中选择文件体积最小的一项。失败的工具不会参与最佳结果选择，但在 `returnAllResults: true` 时会保留在 `allResults` 中并标记 `success: false`。\n\n如果所有工具都失败，库会返回原始输入，`returnAllResults` 和 `compressWithStats` 的 `bestTool` 会标记为 `original`。如果最佳结果与原图大小接近，并且 `quality > 0.85`，库也会保留原图，避免高质量场景下产生几乎无收益的重编码结果。\n\n可选策略：\n\n| 策略 | 说明 |\n|------|------|\n| `smallest` | 选择体积最小的成功结果 |\n| `balanced` | 在最小体积 10% 范围内选择最快结果 |\n| `preserveFormat` | 优先选择保持原格式的最小结果 |\n| `targetSize` | 配合 `targetBytes`，优先选择不超过目标体积且最接近目标的结果 |\n\n### 目标体积\n\n```typescript\nconst result = await compressDetailed(imageBuffer, {\n  quality: 0.9,\n  targetBytes: 200 * 1024\n})\n\nconsole.log(result.selectedReason)\n```\n\n设置 `targetBytes` 后，库会对支持质量参数的工具尝试多轮质量搜索；如果没有结果满足目标体积，会返回当前可得到的最小结果，并在 `selectedReason` 中说明原因。\n\n### compressDetailed(file, options)\n\n返回完整、可解释的压缩结果：\n\n```typescript\nconst detailed = await compressDetailed(imageBuffer, {\n  quality: 0.8,\n  strategy: 'balanced',\n  metadata: 'strip'\n})\n\nconsole.log(detailed.output)\nconsole.log(detailed.bestTool)\nconsole.log(detailed.format)\nconsole.log(detailed.selectedReason)\nconsole.log(detailed.metadata.output)\nconsole.log(detailed.warnings)\n```\n\n### compressWithStats(file, options)\n\n带详细统计信息的压缩函数。它固定返回 `CompressionStats`，其中 `compressedFile` 始终是 `BlobInterface`；不要传 `type`。\n\n```typescript\nconst stats = await compressWithStats(imageBuffer, { quality: 0.8 })\n\nconsole.log('压缩统计:')\nconsole.log(`最佳工具: ${stats.bestTool}`)\nconsole.log(`原始大小: ${stats.originalSize} bytes`)\nconsole.log(`压缩大小: ${stats.compressedSize} bytes`)\nconsole.log(`压缩率: ${stats.compressionRatio.toFixed(1)}%`)\nconsole.log(`总耗时: ${stats.totalDuration}ms`)\n\nconsole.log('各工具性能:')\nstats.toolsUsed.forEach((tool) => {\n  console.log(`${tool.tool}: ${tool.size} bytes, ${tool.duration}ms, ${tool.compressionRatio.toFixed(1)}%`)\n})\n```\n\n## 支持的工具\n\n| 工具 | 描述 | 优势 | 自动选择中参与的格式 |\n|------|------|------|----------|\n| Sharp | 高性能图像处理库 | 速度快，质量高 | JPEG, PNG, WebP |\n| ImageMin | 专业图像优化工具 | 插件生态丰富，支持多格式优化 | JPEG, PNG, WebP, GIF |\n| JIMP | 纯JavaScript图像处理 | 无二进制依赖 | JPEG |\n| Tinify | TinyPNG/TinyJPG官方API | 智能有损压缩，压缩率高 | JPEG, PNG, WebP |\n\n### Tinify 配置\n\nTinify 需要API密钥才能使用。你可以通过以下方式配置：\n\n1. **通过环境变量**:\n```bash\nexport TINIFY_API_KEY=\"your-api-key-here\"\n```\n\n2. **通过参数传递**:\n```typescript\nconst result = await compress(imageBuffer, {\n  quality: 0.8,\n  toolConfigs: [\n    {\n      name: 'tinify',\n      key: 'your-api-key-here'\n    }\n  ]\n})\n```\n\n获取API密钥: [TinyPNG Developer API](https://tinypng.com/developers)\n\n### 元数据策略\n\n`metadata` 支持：\n\n| 值 | 说明 |\n|----|------|\n| `strip` | 默认，剥离元数据 |\n| `exif` | 尽量保留 EXIF，目前通过 Sharp 实现 |\n| `all` | 尽量保留 EXIF / ICC / XMP / IPTC，目前通过 Sharp 实现 |\n\n`preserveExif` 仍然兼容旧用法，内部等价于 `metadata: 'exif'`。启用元数据保留时，库会只使用支持该能力的工具参与压缩选择。\n\n### 批处理与缓存\n\n```typescript\nconst results = await compressFiles(['./input/a.jpg', './input/b.png'], {\n  outputDir: './dist/images',\n  cacheFile: './dist/images/.compress-cache.json',\n  quality: 0.8,\n  format: 'webp',\n  concurrency: 4\n})\n\nconsole.log(results)\n```\n\n`compressFiles()` 会写入 `outputDir`，并可通过 `cacheFile` 跳过输入内容和关键选项都未变化的文件。\n\n## 工具选择策略\n\n库会根据图像类型自动选择最适合的工具组合：\n\n- **PNG**: Sharp → ImageMin → Tinify\n- **JPEG**: Sharp → ImageMin → JIMP → Tinify\n- **WebP**: Sharp → ImageMin → Tinify\n- **GIF**: ImageMin\n\n## 性能优势\n\n- **并行处理**: 多个工具同时运行，提高效率\n- **智能选择**: 自动选择压缩效果最佳的结果\n- **轻量级**: JIMP 和 Tinify 按需安装，减小默认安装体积\n- **失败恢复**: 某个工具失败时自动使用其他工具\n- **灵活配置**: 支持工具特定的配置参数\n\n## :coffee:\n\n[buy me a cup of coffee](https://github.com/Simon-He95/sponsor)\n\n## License\n\n[MIT](./license)\n\n## Sponsors\n\n<p align=\"center\">\n  <a href=\"https://cdn.jsdelivr.net/gh/Simon-He95/sponsor/sponsors.svg\">\n    <img src=\"https://cdn.jsdelivr.net/gh/Simon-He95/sponsor/sponsors.png\"/>\n  </a>\n</p>\n","readmeFilename":"README.md","_rev":"1-d984bcd8331f5e54afd47c416acb40b7"}