CLI — плейсхолдеры для готовой вёрстки
npx vue-image-kit placeholders добавляет плейсхолдер каждому <VImage>, у которого его ещё нет: читает реальное изображение, вычисляет HazeHash, BlurHash, ThumbHash или доминирующий цвет изображения вместе с исходным размером и передаёт результат через манифест плейсхолдеров либо прямо в шаблон.
Рецепты интеграции показывают готовые конфигурации на все случаи — с пояснением, для чего каждая и когда её применять.
npx vue-image-kit placeholders --dry-run # предпросмотр
npx vue-image-kit placeholdersНужен sharp в dev-зависимостях — а для --mode thumbhash ещё и thumbhash:
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не найдёт. Так же обрабатываются все изображения, если регистрация манифеста в проекте не найдена.
<!-- до -->
<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 и удалённые попадают в манифест, если он зарегистрирован, а локальные импорты — или все источники, если манифеста нет, — записываются в сам литерал:
<!-- до -->
<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.
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 добавляет в манифест каждое изображение папки — упоминает его какой-нибудь шаблон или нет:
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 в исходной папке):
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Зарегистрируйте его один раз:
import { VImageKitPlugin } from '@macrulez/vue-image-kit'
import placeholders from './image-placeholders'
app.use(VImageKitPlugin, { placeholders })// 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, порциями:
npx vue-image-kit placeholders --remote --hosts res.cloudinary.com --limit 200Файл конфигурации
// 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.