Skip to content

CLI — генерация изображений

Изменение размера изображений, конвертация в WebP/AVIF, генерация LQIP и BlurHash, запись TypeScript-манифеста — всё одной командой.

Требует sharp как dev-зависимость:

bash
npm install sharp --save-dev

Базовое использование:

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

Выводит отчёт по каждому изображению по мере работы — путь/формат/размеры/размер файла исходника, затем каждый выходной вариант со своим путём/форматом/размерами/размером файла ((existing) для файла, оставленного --skip-existing, (dry-run — not written) при --dry-run) — затем итог по пакету: количество обработанных изображений/файлов, общий входной и выходной размер, и насколько наименьший доступный формат экономит по сравнению с оригиналом в среднем (намеренно не «общий выход против общего входа» — при нескольких ширинах × форматах на изображение общий выход естественным образом в разы больше размера одного оригинала, что вводило бы в заблуждение, читаясь как «стало хуже»):

[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

Все опции:

ФлагПо умолчаниюОписание
--input <dir>./src/imagesДиректория-источник
--output <dir>./public/imagesВыходная директория
--widths <list>400,800,1200Выходные ширины через запятую
--formats <list>jpg,webp,avifВыходные форматы
--quality <json>{"jpg":85,"webp":80,"avif":65}Качество по формату
--template <str>{name}-{width}.{ext}Шаблон имени файла ({name}, {width}, {ext})
--manifest <path>Записать манифест images.ts по этому пути
--public-path <str>/imagesURL-префикс, используемый в путях манифеста
--lqip / --no-lqipвключеноГенерировать base64 LQIP-плейсхолдер
--blurhash / --no-blurhashвключеноГенерировать строку BlurHash
--thumbhash / --no-thumbhashвыключеноГенерировать строку ThumbHash (требует dev-зависимость thumbhash)
--cleanУдалить выходную директорию перед генерацией
--dry-runПредпросмотр без записи файлов
--skip-existingПропускать уже сгенерированные файлы
--concurrency <n>4Количество параллельных воркеров
--watchОтслеживать входную директорию и перегенерировать при изменении
--incremental / --no-incrementalавтоПропускать переобработку источника, если его mtime (или, если он изменился, хэш содержимого) совпадает с предыдущим запуском. Автоматически включается под --watch (и плагином Vite во время vite dev), если не задано явно тем или иным способом — разовый generate по умолчанию выключен. См. Инкрементальная генерация ниже

Файл конфигурации — создайте vue-image-kit.config.js в корне проекта, чтобы не повторять флаги:

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 и анимированный GIF обрабатываются иначе, чем растровые форматы — они определяются по расширению входного файла, а не по --formats:

  • SVG копируется без изменений (без растеризации — он уже разрешение-независим). Запись манифеста получает src, указывающий на копию; webp/avif/placeholder/blurhash/thumbhash — пустые строки.
  • Анимированный GIF копируется как гарантированно совместимый fallback (src), и — когда webp в --formats — перекодируется в анимированный WebP (поле webp) для реальной экономии размера. AVIF пропускается: поддержка анимированного AVIF в сборках sharp/libavif слишком непоследовательна, чтобы на неё полагаться. Плейсхолдеры LQIP/BlurHash/ThumbHash всё равно генерируются из первого кадра.

Инкрементальная генерация

--watch и buildStart/handleHotUpdate плагина Vite вызывают одну и ту же функцию generate(), что и CLI, — по умолчанию каждое изменение одного файла означает повторное сканирование и обработку всех исходных изображений, а не только того, что изменилось. Режим incremental это исправляет:

bash
npx vue-image-kit generate --watch --incremental   # уже по умолчанию под --watch

Для каждого источника проверяется сохранённая запись из предыдущего запуска: mtime не изменился → полностью пропускается, без чтения, без вызова sharp. Mtime изменился (например, git checkout затронул все файлы) → используется хэш содержимого перед принятием решения — неизменённый файл переживает checkout, не вызывая ненужную переобработку. Запись — JSON-манифест по пути <output>/.vik-incremental.json — также хранит полные метаданные каждого пропущенного изображения, так что отчёт по пакету/вывод --manifest остаётся полным даже для изображений, не тронутых в этом запуске.

Изменение widths/formats/quality/template/publicPath/lqip/ blurhash/thumbhash между запусками инвалидирует всё сразу (логируется как Config changed since last run) — не файл-за-файлом, поскольку изменение конфига может повлиять на любой или все выходы. --clean тоже инвалидирует всё, естественным образом: удаляет output, а манифест живёт внутри неё. Нет эффекта под --dry-run (ничего не записывается, поэтому нет ничего валидного для сравнения в следующий раз).

По умолчанию: выключено для разового generate (один запуск ничего не выигрывает от кэширования), автоматически включается под --watch и во время vite dev (vite build остаётся выключенным — продакшен-артефакт не должен рисковать устаревшим кэшем). Явный --incremental/--no-incremental (флаг CLI, файл конфигурации или опция плагина Vite) всегда переопределяет автоматическое значение по умолчанию в любую сторону.