CLI — Generate Images
Resize images, convert to WebP/AVIF, generate LQIP and HazeHash/BlurHash, write a TypeScript manifest — all in one command.
generate is the CLI's default command. The CLI also has scan — a report of how images are used across the project — and placeholders, which fills in placeholders for <VImage> usages already in your templates. npx vue-image-kit --help lists all three; each takes --help for its own options.
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:
--input <dir>· default:./src/images. Source directory.--output <dir>· default:./public/images. Output directory.--widths <list>· default:400,800,1200. Comma-separated output widths.--formats <list>· default:jpg,webp,avif. Output formats.--quality <json>· default:{"jpg":85,"webp":80,"avif":65}. Quality per format.--template <str>· default:{name}-{width}.{ext}. Filename template ({name},{width},{ext}).--manifest <path>— writeimages.tsmanifest to this path.--public-path <str>· default:/images. URL prefix used in manifest paths.--lqip/--no-lqip· default: enabled. Generate base64 LQIP placeholder.--hazehash/--no-hazehash· default: enabled when thehazehashpackage is installed, disabled otherwise. Generate a HazeHash string (the preferred placeholder; requires thehazehashpackage).--blurhash/--no-blurhash· default: enabled. Generate BlurHash string.--thumbhash/--no-thumbhash· default: disabled. Generate ThumbHash string (requiresthumbhashdev dep).--clean— remove output dir before generating.--dry-run— preview without writing files.--skip-existing— skip already-generated files.--concurrency <n>· default:4. Parallel workers.--watch— watch input dir and regenerate on change.--incremental/--no-incremental· default: 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 duringvite dev) unless set explicitly either way — a one-shotgeneratestays 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/hazehash/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/ hazehash/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.