imagemin
npx skills add https://github.com/ishawnwang/imagemin-skill --skill imagemin
Agent 安装分布
Skill 文档
imagemin Skill
Overview
imagemin is a Node.js image compression library that supports JPEG, PNG, GIF, SVG, and WebP
via plugins. v8+ is ESM-only â use import syntax or set "type": "module" in package.json.
For CommonJS projects, pin to imagemin@7.
AI Skill Usage Guide
When to use this skill
- Use this skill whenever the user asks to “compress/optimize images”, “reduce image size”, or “generate an image compression script”.
- Prioritize JPEG, PNG, GIF, and SVG. Only introduce WebP conversion when the user explicitly asks for WebP.
- For one-off local batch jobs, prefer CLI commands; for project integration, prefer code examples.
Plugin and quality presets (recommended defaults)
| Format | Plugin | Recommended quality / options | Notes |
|---|---|---|---|
| JPEG | imagemin-mozjpeg | quality: 75â85 |
Good balance for photos |
| PNG | imagemin-pngquant | quality: [0.65, 0.80], speed: 4 |
Screenshots, icons with alpha |
| GIF | imagemin-gifsicle | optimizationLevel: 2 |
Animations, usually enough |
| SVG | imagemin-svgo | removeViewBox: false |
Preserve viewBox for proper scale |
| WebP | imagemin-webp | quality: 70â80 |
Use only when WebP is requested |
When the user does not specify quality settings, prefer the ranges above and mention in your answer which defaults you are using.
Safety and general rules
- Do not overwrite originals by default: unless the user explicitly asks to replace images in place, write to a separate output directory.
- Avoid re-compressing already compressed results: explain that repeated lossy compression degrades quality with limited extra savings.
- For large batches, avoid processing everything at once: suggest chunking or limiting concurrency to keep memory usage under control.
- For large or long-running services, prefer directory-based or streaming solutions instead of manual one-off commands.
- For CommonJS projects, suggest using
imagemin@7or migrating to ESM with"type": "module".
Tool selection strategy
- If the user wants “a command to compress a folder on my machine”:
- Return a CLI example based on
scripts/compress-images.jsand explain input/output directories and--replacerisks.
- Return a CLI example based on
- If the user wants “to integrate image compression into a Node.js project”:
- Use the code examples below (directory or buffer compression) and adapt paths and quality to the project.
- If the user only asks “what quality should I use / how much size can I save”:
- Use the table above and the typical values in Compression Ratios to give concrete recommendations.
Available Tools
This skill includes a ready-to-use compression script:
scripts/compress-images.js – Command-line tool for batch image compression
Basic usage:
node compress-images.js --input ./images --output ./compressed
node compress-images.js --input ./photos --replace # Replace originals
node compress-images.js --help # Show all options
See scripts/README.md for detailed usage and options.
Installation
npm install imagemin imagemin-mozjpeg imagemin-pngquant imagemin-gifsicle imagemin-svgo imagemin-webp
# or: yarn add / pnpm add
Basic Usage
Compress a directory
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
const files = await imagemin(['src/images/*.{jpg,png}'], {
destination: 'dist/images',
plugins: [
imageminMozjpeg({ quality: 75 }),
imageminPngquant({ quality: [0.65, 0.80] }),
],
});
console.log(`Compressed ${files.length} files`);
Compress a single buffer
import { readFile, writeFile } from 'fs/promises';
const buffer = await readFile('input.jpg');
const compressed = await imagemin.buffer(buffer, {
plugins: [imageminMozjpeg({ quality: 75 })],
});
await writeFile('output.jpg', compressed);
Plugin Reference
| Plugin | Format | Key Options |
|---|---|---|
| imagemin-mozjpeg | JPEG | quality (0â100), progressive |
| imagemin-jpegtran | JPEG | progressive, arithmetic |
| imagemin-pngquant | PNG | quality ([min, max] 0â1), speed |
| imagemin-optipng | PNG | optimizationLevel (0â7) |
| imagemin-gifsicle | GIF | optimizationLevel (1â3), interlaced |
| imagemin-svgo | SVG | plugins (array of SVGO plugins) |
| imagemin-webp | â WebP | quality (0â100), lossless |
Common Patterns
Compress multiple formats at once
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminGifsicle from 'imagemin-gifsicle';
import imageminSvgo from 'imagemin-svgo';
await imagemin(['src/**/*.{jpg,jpeg,png,gif,svg}'], {
destination: 'dist/images',
plugins: [
imageminMozjpeg({ quality: 80 }),
imageminPngquant({ quality: [0.65, 0.80], speed: 4 }),
imageminGifsicle({ optimizationLevel: 2 }),
imageminSvgo({ plugins: [{ name: 'removeViewBox', active: false }] }),
],
});
Report compression savings
import { stat } from 'fs/promises';
for (const file of files) {
const before = (await stat(file.sourcePath)).size;
const after = file.data.length;
const saved = (((before - after) / before) * 100).toFixed(1);
console.log(`${file.sourcePath} â saved ${saved}%`);
}
Generate WebP versions
import imageminWebp from 'imagemin-webp';
await imagemin(['images/*.{jpg,png}'], {
destination: 'build/images',
plugins: [imageminWebp({ quality: 75 })],
});
Output Object Structure
{
data: Buffer, // compressed image data
sourcePath: string, // original file path
destinationPath: string // output file path
}
Performance Optimization
Concurrent Processing
Process multiple images in parallel with controlled concurrency to maximize throughput:
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import pLimit from 'p-limit';
import { readdir } from 'fs/promises';
import { join } from 'path';
const limit = pLimit(4); // Process 4 images concurrently
const inputDir = 'src/images';
const outputDir = 'dist/images';
const files = await readdir(inputDir);
const tasks = files
.filter(f => /\.(jpg|png)$/i.test(f))
.map(file => limit(() =>
imagemin([join(inputDir, file)], {
destination: outputDir,
plugins: [imageminMozjpeg({ quality: 80 })],
})
));
const results = await Promise.all(tasks);
console.log(`Processed ${results.flat().length} images`);
Memory Management
For large batches, process in chunks to avoid memory exhaustion:
function chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
const allFiles = ['img1.jpg', 'img2.jpg', /* ... hundreds more ... */];
const batches = chunk(allFiles, 50); // Process 50 files at a time
for (const batch of batches) {
await imagemin(batch, {
destination: 'dist/images',
plugins: [imageminMozjpeg({ quality: 80 })],
});
console.log(`Completed batch of ${batch.length} files`);
}
Stream Processing for Large Files
Use buffer API for individual large files to avoid loading entire directories:
import { readFile, writeFile } from 'fs/promises';
import { readdir } from 'fs/promises';
const files = await readdir('large-images');
for (const file of files) {
const buffer = await readFile(`large-images/${file}`);
const compressed = await imagemin.buffer(buffer, {
plugins: [imageminMozjpeg({ quality: 75 })],
});
await writeFile(`output/${file}`, compressed);
console.log(`Compressed ${file}`);
}
Batch Processing Strategies
Directory Recursion
Process nested directory structures:
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
// Glob pattern handles nested directories automatically
await imagemin(['src/**/*.{jpg,jpeg,png}'], {
destination: 'dist/images',
plugins: [
imageminMozjpeg({ quality: 80 }),
imageminPngquant({ quality: [0.65, 0.80] }),
],
glob: {
// Preserve directory structure in output
expandDirectories: {
files: ['*'],
extensions: ['jpg', 'jpeg', 'png']
}
}
});
Progress Tracking
Monitor progress for large batches:
import cliProgress from 'cli-progress';
const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
const files = ['img1.jpg', 'img2.jpg', /* ... */];
progressBar.start(files.length, 0);
for (let i = 0; i < files.length; i++) {
await imagemin([files[i]], {
destination: 'output',
plugins: [imageminMozjpeg({ quality: 80 })],
});
progressBar.update(i + 1);
}
progressBar.stop();
Size-Based Filtering
Only compress images above a certain size threshold:
import { stat } from 'fs/promises';
import { readdir } from 'fs/promises';
import { join } from 'path';
const minSize = 100 * 1024; // 100 KB
const files = await readdir('images');
const largeFiles = [];
for (const file of files) {
const filePath = join('images', file);
const stats = await stat(filePath);
if (stats.size > minSize) {
largeFiles.push(filePath);
}
}
await imagemin(largeFiles, {
destination: 'output',
plugins: [imageminMozjpeg({ quality: 80 })],
});
console.log(`Compressed ${largeFiles.length} files over ${minSize / 1024}KB`);
Compression Ratios
Typical Compression Results
Based on real-world usage, here are typical compression ratios you can expect:
| Image Type | Original Size | Compressed Size | Savings | Settings |
|---|---|---|---|---|
| JPEG Photo | 2.4 MB | 850 KB | 64.6% | quality: 80 (mozjpeg) |
| JPEG Photo | 2.4 MB | 450 KB | 81.3% | quality: 65 (mozjpeg) |
| PNG Screenshot | 1.2 MB | 320 KB | 73.3% | quality: [0.65, 0.80] (pngquant) |
| PNG Icon | 45 KB | 12 KB | 73.3% | quality: [0.65, 0.80] (pngquant) |
| GIF Animation | 850 KB | 680 KB | 20.0% | optimizationLevel: 2 (gifsicle) |
| SVG Logo | 28 KB | 8 KB | 71.4% | default (svgo) |
| WebP Conversion | 2.4 MB (JPEG) | 380 KB | 84.2% | quality: 75 (webp) |
Example: Comparing Quality Settings
import { stat } from 'fs/promises';
const qualities = [60, 70, 80, 90];
console.log('Quality | Size | Savings');
console.log('--------|---------|--------');
for (const quality of qualities) {
const result = await imagemin(['photo.jpg'], {
destination: `output-q${quality}`,
plugins: [imageminMozjpeg({ quality })],
});
const originalSize = (await stat('photo.jpg')).size;
const compressedSize = result[0].data.length;
const savings = (((originalSize - compressedSize) / originalSize) * 100).toFixed(1);
console.log(`${quality} | ${(compressedSize / 1024).toFixed(0)} KB | ${savings}%`);
}
// Example output:
// Quality | Size | Savings
// --------|---------|--------
// 60 | 245 KB | 89.8%
// 70 | 380 KB | 84.2%
// 80 | 620 KB | 74.2%
// 90 | 1250 KB | 48.0%
Real-World Batch Results
import { stat } from 'fs/promises';
const files = await imagemin(['photos/*.jpg'], {
destination: 'compressed',
plugins: [imageminMozjpeg({ quality: 80 })],
});
let totalOriginal = 0;
let totalCompressed = 0;
for (const file of files) {
const originalSize = (await stat(file.sourcePath)).size;
const compressedSize = file.data.length;
totalOriginal += originalSize;
totalCompressed += compressedSize;
}
const averageSavings = (((totalOriginal - totalCompressed) / totalOriginal) * 100).toFixed(1);
console.log(`Total: ${(totalOriginal / 1024 / 1024).toFixed(1)} MB â ${(totalCompressed / 1024 / 1024).toFixed(1)} MB`);
console.log(`Average savings: ${averageSavings}%`);