CLI — Generate Images
Resize images, convert to WebP/AVIF, generate LQIP and BlurHash, write a TypeScript manifest — all in one command.
Requires sharp as a dev dependency:
npm install sharp --save-devBasic usage:
npx vue-image-kit generate \
--input ./src/images \
--output ./public/images \
--widths 400,800,1200 \
--formats jpg,webp,avif \
--manifest ./src/assets/images.tsPrints a per-image report as it works — source path/format/dimensions/size, then every output variant with its own path/format/dimensions/size ((existing) for a file --skip-existing kept, (dry-run — not written) under --dry-run) — followed by a batch total: images/files processed, total input vs. output size, and how much the smallest available format saves vs. the original on average (deliberately not "total output vs. total input" — with several widths × formats generated per image, total output is naturally many times one original's size, which would misleadingly read as "this made it worse"):
[vue-image-kit] Processing 1 image(s)…
[vue-image-kit] photo1
Input ./src/images/photo1.jpg
jpg · 1200×800 · 245.3 KB
Output
./public/images/photo1-400.jpg jpg 400×267 52.1 KB
./public/images/photo1-800.jpg jpg 800×533 118.4 KB
./public/images/photo1.jpg jpg 1200×800 198.2 KB
./public/images/photo1.webp webp 1200×800 112.9 KB
./public/images/photo1.avif avif 1200×800 79.6 KB
[vue-image-kit] Done. 1 image(s) → 5 file(s).
Input: 1 image(s), 245.3 KB total
Output: 5 file(s), 561.2 KB total
Smallest available format saves ~79% vs. original, on average
[vue-image-kit] Manifest written to ./src/assets/images.tsAll options:
| Flag | Default | Description |
|---|---|---|
--input <dir> | ./src/images | Source directory |
--output <dir> | ./public/images | Output directory |
--widths <list> | 400,800,1200 | Comma-separated output widths |
--formats <list> | jpg,webp,avif | Output formats |
--quality <json> | {"jpg":85,"webp":80,"avif":65} | Quality per format |
--template <str> | {name}-{width}.{ext} | Filename template ({name}, {width}, {ext}) |
--manifest <path> | — | Write images.ts manifest to this path |
--public-path <str> | /images | URL prefix used in manifest paths |
--lqip / --no-lqip | enabled | Generate base64 LQIP placeholder |
--blurhash / --no-blurhash | enabled | Generate BlurHash string |
--thumbhash / --no-thumbhash | disabled | Generate ThumbHash string (requires thumbhash dev dep) |
--clean | — | Remove output dir before generating |
--dry-run | — | Preview without writing files |
--skip-existing | — | Skip already-generated files |
--concurrency <n> | 4 | Parallel workers |
--watch | — | Watch input dir and regenerate on change |
--incremental / --no-incremental | auto | Skip reprocessing a source whose mtime (or, if that changed, content hash) matches the last run. Auto-enabled under --watch (and by the Vite plugin during vite dev) unless set explicitly either way — a one-shot generate stays off by default. See Incremental generation below |
Config file — create vue-image-kit.config.js in your project root to avoid repeating flags:
// vue-image-kit.config.js
export default {
input: './photos',
output: './public/images',
widths: [480, 960, 1440],
formats: ['jpg', 'webp'],
manifest: './src/assets/images.ts',
publicPath: '/images',
}SVG and animated GIF are handled differently from raster formats — they're detected by input extension, not by --formats:
- SVG is copied through untouched (no rasterizing — it's already resolution-independent). The manifest entry gets
srcpointing at the copy;webp/avif/placeholder/blurhash/thumbhashare empty strings. - Animated GIF is copied through as the guaranteed-compatible fallback (
src), and — whenwebpis in--formats— re-encoded to animated WebP (webpfield) for a real size win. AVIF is skipped: animated-AVIF support acrosssharp/libavif builds is too inconsistent to promise. LQIP/BlurHash/ThumbHash placeholders are still generated from the first frame.
Incremental generation
--watch and the Vite plugin's buildStart/handleHotUpdate both call the same generate() the CLI does — by default, every single-file change means re-scanning and reprocessing every source image, not just the one that changed. incremental mode fixes that:
npx vue-image-kit generate --watch --incremental # already the default under --watchFor each source, it checks a persisted record from the previous run: mtime unchanged → skip entirely, no read, no sharp call. Mtime changed (e.g. a git checkout touched every file) → falls back to a content hash before deciding — an unmodified file survives a checkout without triggering a needless reprocess. The record — a JSON manifest at <output>/.vik-incremental.json — also stores each skipped image's full metadata, so the batch report/--manifest output stays complete even for images that weren't touched this run.
Changing widths/formats/quality/template/publicPath/lqip/ blurhash/thumbhash between runs invalidates everything at once (logged as Config changed since last run) — not per-file diffing, since a config change can affect any or all outputs. --clean also invalidates everything, naturally: it deletes output, and the manifest lives inside it. No effect under --dry-run (nothing is written, so there's nothing valid to compare against next time).
Defaults: off for a one-shot generate (a single run gains nothing from caching), on automatically under --watch and during vite dev (vite build stays off — a production artifact shouldn't risk a stale cache). An explicit --incremental/--no-incremental (CLI flag, config file, or Vite plugin option) always overrides the automatic default either way.