Skip to content

CLI — плейсхолдеры для готовой вёрстки ​

npx vue-image-kit placeholders добавляет плейсхолдер каждому <VImage>, у которого его ещё нет: читает реальное изображение, вычисляет HazeHash, BlurHash, ThumbHash или доминирующий цвет изображения вместе с исходным размером и передаёт результат через манифест плейсхолдеров либо прямо в шаблон.

Рецепты интеграции показывают готовые конфигурации на все случаи — с пояснением, для чего каждая и когда её применять.

bash
npx vue-image-kit placeholders --dry-run   # предпросмотр
npx vue-image-kit placeholders

Нужен sharp в dev-зависимостях — а для --mode thumbhash ещё и thumbhash:

bash
npm install -D sharp

Какие изображения обрабатываются ​

Команда сканирует проект и берёт каждый <VImage>, у которого нет ни hazehash, ни blurhash, ни thumbhash, ни placeholder, ни placeholderColor, ни image, ни placeholderMode со значением color/shimmer, а изображение можно определить:

  • Локальные импорты и пути из public/ — читаются с диска.
  • CDN и удалённые URL — скачиваются только с --remote: в проекте таких могут быть тысячи. Изображение с CDN запрашивается в уменьшенной версии (128px по ширине) через его адаптер, а исходный размер читается из первых 64 КБ оригинального файла.

Пропускаются и перечисляются в сводке: динамический src, URI data:, несуществующие файлы и использования, передающие пропсы через v-bind. Использования, у которых плейсхолдер уже есть, тоже пропускаются, если не указан --replace.

Что вычисляется ​

  • --mode hazehash (по умолчанию, если установлен пакет hazehash) — HazeHash, строка в 7–48 байт.
  • --mode blurhash (по умолчанию в остальных случаях) — BlurHash.
  • --mode thumbhash — ThumbHash.
  • --mode color — только доминирующий цвет: самый частый цвет уменьшенного изображения, без учёта прозрачных пикселей.

Записи манифеста всегда содержат ещё и доминирующий цвет и исходные ширину/высоту; в шаблон попадает только значение выбранного режима (и размеры, если их там нет). SVG всегда получает только цвет и размеры.

Как доставляется результат ​

Способ выбирается для каждого использования отдельно:

  • Манифест — изображения из public/, CDN и удалённые попадают в манифест плейсхолдеров, если в проекте он зарегистрирован. <VImage> находит свою запись во время выполнения по src — в шаблоны ничего не добавляется.
  • Шаблон — значения пишутся прямо в тег <VImage> как пропсы. Так обрабатываются локальные импорты: их src во время выполнения — захешированный URL сборки, который поиск по src не найдёт. Так же обрабатываются все изображения, если регистрация манифеста в проекте не найдена.
vue
<!-- до -->
<VImage src="/images/hero.jpg" alt="Hero" />

<!-- после, записано в шаблон -->
<VImage
  src="/images/hero.jpg"
  alt="Hero"
  :width="1200"
  :height="800"
  blurhash="LJ9+E%}}$^I_^Z=:xUNMIwI^R.s+"
/>

Пропсы вставляются после последнего существующего атрибута — на новых строках с тем же отступом, если тег уже занимает несколько строк. :width/:height добавляются, только если в теге нет ни одного из них. С --mode color в шаблон пишется placeholder-color="#…".

Исходные файлы с незакоммиченными изменениями в git никогда не правятся — они перечисляются в выводе, чтобы сначала можно было закоммитить или убрать изменения в stash. --force-write отключает эту проверку, --no-write вообще не трогает исходники, а --dry-run выводит все правки, ничего не записывая.

Источники art direction ​

Записи литерала :sources="{ … }" тоже обрабатываются — независимо от собственного src изображения, поэтому <VImage> с динамическим src всё равно может получить плейсхолдеры для своих sources. Запись — это путь или URL (mobile: '/m.jpg') либо объект с src (tablet: { src: tabletImage, width: 800, height: 400 }). Каждая запись определяется и доставляется по тем же правилам, что и изображение: источники из public/, с CDN и удалённые попадают в манифест, если он зарегистрирован, а локальные импорты — или все источники, если манифеста нет, — записываются в сам литерал:

vue
<!-- до -->
<VImage
  src="/desktop.jpg"
  :sources="{ tablet: '/tablet.jpg', mobile: { src: mobileImage, width: 400, height: 600 } }"
/>

<!-- после, записано в шаблон -->
<VImage
  src="/desktop.jpg"
  :sources="{
    tablet: {
      src: '/tablet.jpg',
      width: 800,
      height: 400,
      blurhash: 'LKO2?U%2Tw=w]~RBVZRi};RPxuwH',
    },
    mobile: { src: mobileImage, width: 400, height: 600, blurhash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj' },
  }"
/>

Простая строка превращается в объект { src, … }. width/height добавляются, только если у записи нет ни одного из них. <VImage> показывает плейсхолдер записи, пока активен её брейкпоинт, — см. Плейсхолдеры на брейкпоинт.

  • Запись, у которой плейсхолдер уже есть, не трогается, и --replace этого не меняет.
  • Запись в виде простого объекта { avif, webp, fallback } пропускается — плейсхолдер она нести не может; используйте { src: { avif, webp, fallback } }.
  • Объект sources, попавший в шаблон через переменную, может пополнить манифест, но на месте никогда не правится; без зарегистрированного манифеста такие записи попадают в сводку как не подлежащие правке.

Замена существующих плейсхолдеров ​

С --replace переделываются и использования, у которых плейсхолдер уже есть: их статические атрибуты hazehash, blurhash, thumbhash, placeholder, placeholder-color и placeholder-mode удаляются, а их место занимает плейсхолдер выбранного --mode.

bash
npx vue-image-kit placeholders --replace --dry-run
    src/components/Blog.vue:31  - thumbhash  + blurhash="L2M^#R=1fQ=1]UjtfQjtfQfQfQfQ"
    src/components/Card.vue:14  - placeholder-color  - placeholder-mode  + :width="400" :height="300" blurhash="LJ9+E%}}$^I_^Z=:xUNMIwI^R.s+"
    src/components/Hero.vue:9   - placeholder-color  (value → manifest)
  • Новое значение доставляется по тем же правилам, что описаны выше: в шаблон или — для изображения из public/, с CDN или удалённого при зарегистрированном манифесте — в манифест, а старый атрибут только удаляется (иначе он перекрыл бы запись манифеста).
  • Существующие width/height сохраняются, недостающие добавляются как обычно.
  • Плейсхолдер, привязанный к выражению (:thumbhash="item.hash"), и :image не трогаются и перечисляются в сводке — эти данные приходят из вашего кода или сборки.
  • src использования по-прежнему должен определяться статически: плейсхолдер изображения с динамическим src пересчитать нельзя.
  • --no-write полностью отключает замены, а файлы с незакоммиченными изменениями не правятся без --force-write.

Папки и URL ​

Использование, у которого src собирается во время выполнения, сканированием не найти. Вместо этого --dir добавляет в манифест каждое изображение папки — упоминает его какой-нибудь шаблон или нет:

bash
npx vue-image-kit placeholders --dir public/images --dir src/img=/assets/img
  • Папка сканируется рекурсивно. Каждый файл записывается под тем URL, по которому он отдаётся: по пути от публичной папки, если папка внутри неё, и <urlPrefix>/<путь от папки> для --dir <папка>=<urlPrefix>.
  • Папка вне публичной без префикса записывается по пути в проекте (/src/img/a.jpg), который совпадает только с URL dev-сервера; команда предупредит об этом.
  • --url <url> (можно повторять) добавляет одно удалённое или CDN-изображение и скачивает его без --remote — само указание адреса и есть согласие.
  • Записи из папок и URL объединяются с найденными через использования, используют тот же кеш и учитывают --mode и настройки хешей.

Тот же манифест может собирать плагин Vite при каждой сборке, без команды — см. Манифест плейсхолдеров из папок.

v-lazy-img и useBackgroundImage() ​

v-lazy-img и useBackgroundImage() тоже читают манифест, поэтому команда заполняет его для их использований со статическим src — только через манифест, в шаблон ничего не пишется. Для этого нужен зарегистрированный манифест и src из public/, с CDN или удалённый; использования со своим placeholder, локальным импортом или динамическим src пропускаются и попадают в сводку.

Настройка хешей ​

  • --components <XxY> · по умолчанию: 4x3. Число компонент BlurHash по каждой оси, от 1 до 9.
  • --sample <px> · по умолчанию: 100. Размер, до которого изображение уменьшается перед расчётом хеша, до 256.
  • --color <strategy> · по умолчанию: dominant. dominant берёт самый частый цвет, average — среднее по непрозрачным пикселям.

При изменении любого из них кеш сбрасывается. В файле конфигурации они лежат в tuning: { components: [x, y], sample, color }.

Регистрация манифеста ​

По умолчанию манифест записывается в src/image-placeholders.ts (в проекте Nuxt — в image-placeholders.ts в исходной папке):

ts
import type { PlaceholderManifest } from '@macrulez/vue-image-kit'

// Generated by `vue-image-kit placeholders` — re-run the command instead of editing by hand.
const placeholders: PlaceholderManifest = {
  "/images/hero.jpg": {"blurhash":"LJ9+E%}}$^I_^Z=:xUNMIwI^R.s+","color":"#1e6ec7","width":1200,"height":800},
  "/logo.svg": {"color":"#7c3aed","width":120,"height":60},
}

export default placeholders

Зарегистрируйте его один раз:

ts
import { VImageKitPlugin } from '@macrulez/vue-image-kit'
import placeholders from './image-placeholders'

app.use(VImageKitPlugin, { placeholders })
ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@macrulez/vue-image-kit/nuxt'],
  vueImageKit: { placeholders: './image-placeholders.ts' },
})

Команда распознаёт оба варианта — а также app.provide(PLACEHOLDERS_KEY, …) — при сканировании проекта. Как <VImage> использует запись, описано в разделе Плейсхолдеры.

Вывод ​

[vue-image-kit] placeholders — mode: blurhash
  Images: 1 computed (0 local, 1 remote) · 4 from cache · 0 failed
  Manifest: 4 usage(s) → src/image-placeholders.ts (written)
  Source edits: 1 usage(s) in 1 file(s)
    src/App.vue
  Skipped:
       1 usage(s) have a dynamic src
       1 usage(s) already have a placeholder
       1 usage(s) point at a file that does not exist

Команда завершается с кодом 1, если какое-то изображение не удалось обработать (например, удалённый URL вернул 404); остальные всё равно обрабатываются.

Кеш ​

Результаты кешируются в node_modules/.cache/vue-image-kit/placeholders.json. Локальный файл пересчитывается, только когда меняется время его изменения или размер; скачанное изображение используется повторно до --refresh-remote. --no-cache пересчитывает всё. Записи манифеста для удалённых изображений сохраняются в нём и при последующих запусках без --remote. Файл манифеста можно переформатировать форматтером или поправить вручную — при повторном чтении команда разбирает его как код.

Опции ​

  • --root, --include, --exclude, --public-dir, --alias, --package, --no-vite-config — те же опции поиска файлов, что у scan.
  • --manifest <path> · по умолчанию: src/image-placeholders.ts. Файл манифеста, .ts или .json.
  • --mode <mode> · по умолчанию: hazehash, если пакет установлен, иначе blurhash. hazehash, blurhash, thumbhash или color.
  • --budget <bytes> · по умолчанию: 28. Размер строки HazeHash в байтах, 7–48.
  • --dir <path> — также посчитать каждое изображение папки, можно повторять; --dir <path>=<urlPrefix> для папки, которая отдаётся с другого URL. См. Папки и URL.
  • --url <url> — также посчитать удалённое или CDN-изображение, можно повторять; --remote не нужен.
  • --components, --sample, --color — см. Настройка хешей.
  • --remote — также скачивать изображения с CDN и удалённые.
  • --hosts <list> — вместе с --remote: только эти хосты (через запятую; поддомены включаются).
  • --limit <n> — вместе с --remote: скачать не больше стольких изображений за один запуск.
  • --concurrency <n> · по умолчанию: 4. Сколько изображений обрабатывается параллельно.
  • --timeout <ms> · по умолчанию: 15000. Таймаут одного запроса для удалённых изображений.
  • --max-bytes <n> · по умолчанию: 15728640 (15 МБ). Удалённые изображения больше этого размера пропускаются.
  • --dry-run — показать, что изменится, ничего не записывая.
  • --no-write — никогда не править исходные файлы; только записать манифест.
  • --force-write — править исходные файлы, даже если в них есть незакоммиченные изменения.
  • --no-cache — игнорировать кеш и пересчитать всё.
  • --refresh-remote — заново скачать удалённые изображения, даже если они есть в кеше.
  • --replace — также переделать использования, у которых уже есть статический плейсхолдер; см. Замена существующих плейсхолдеров.

Пример — удалённые изображения с одного CDN, порциями:

bash
npx vue-image-kit placeholders --remote --hosts res.cloudinary.com --limit 200

Файл конфигурации ​

js
// vue-image-kit.config.js
export default {
  placeholders: {
    manifest: './src/image-placeholders.json',
    mode: 'color',
    remote: true,
    hosts: ['res.cloudinary.com'],
    limit: 500,
    replace: false,
    dirs: ['public/images', { dir: 'src/img', urlPrefix: '/assets/img' }],
    urls: ['https://cdn.example.com/hero.jpg'],
    tuning: { components: [4, 3], sample: 100, color: 'dominant' },
  },
}

Секция placeholders принимает также все ключи секции scan.