Skip to content

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:

bash
npm install sharp --save-dev

Basic usage:

bash
npx vue-image-kit generate \
  --input ./src/images \
  --output ./public/images \
  --widths 400,800,1200 \
  --formats jpg,webp,avif \
  --manifest ./src/assets/images.ts

Prints 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.ts

All options:

FlagDefaultDescription
--input <dir>./src/imagesSource directory
--output <dir>./public/imagesOutput directory
--widths <list>400,800,1200Comma-separated output widths
--formats <list>jpg,webp,avifOutput 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>/imagesURL prefix used in manifest paths
--lqip / --no-lqipenabledGenerate base64 LQIP placeholder
--blurhash / --no-blurhashenabledGenerate BlurHash string
--thumbhash / --no-thumbhashdisabledGenerate ThumbHash string (requires thumbhash dev dep)
--cleanRemove output dir before generating
--dry-runPreview without writing files
--skip-existingSkip already-generated files
--concurrency <n>4Parallel workers
--watchWatch input dir and regenerate on change
--incremental / --no-incrementalautoSkip 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:

js
// 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 src pointing at the copy; webp/avif/placeholder/blurhash/thumbhash are empty strings.
  • Animated GIF is copied through as the guaranteed-compatible fallback (src), and — when webp is in --formats — re-encoded to animated WebP (webp field) for a real size win. AVIF is skipped: animated-AVIF support across sharp/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:

bash
npx vue-image-kit generate --watch --incremental   # already the default under --watch

For 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.