Оптимизация изображений

Положите полноразмерное изображение в src/ или public/, направьте на него <img> — и sitelo уменьшит его, переведёт в современный формат и перепишет тег с srcset. Ничего импортировать не нужно, никакого компонента изучать не придётся.

Включение

export default {
  images: true,
}

Кодированием занимается sharp — необязательная peer-зависимость; установите её вместе с sitelo, когда включаете изображения:

npm install -D sharp

Сайтам без оптимизации изображений устанавливать её не нужно вовсе.

Пишите обычный <img>

import { html, body, img } from 'javascript-to-html'

export default () =>
  html({ lang: 'ru' },
    body(
      img({ src: '/images/hero.png', alt: 'Рассвет над гаванью' }),
    ),
  )

Исходник 3000×2000 выходит вот таким:

<img src="/assets/img/hero.a1b2c3d4-3000.webp"
     alt="Рассвет над гаванью"
     sizes="100vw"
     width="3000" height="2000"
     loading="lazy" decoding="async"
     srcset="/assets/img/hero.9f8e7d6c-400.webp 400w,
             /assets/img/hero.5b4a3c2d-800.webp 800w,
             /assets/img/hero.7c6d5e4f-1200.webp 1200w,
             /assets/img/hero.a1b2c3d4-3000.webp 3000w">

Экран шириной до 400px получает файл 400, до 800 — 800, до 1200 — 1200, а всё, что шире, — полные 3000: браузер берёт первую ступень, покрывающую окно (на экране 2× — вдвое больше).

Что вы получаете

Переписывание идёт по собранному HTML, поэтому охватывает изображения и из src/, и из public/.

После переписывания тега на оригинал уже ничто не ссылается, поэтому по умолчанию он удаляется из сборки. Уходят только действительно неиспользуемые файлы: просматривается каждый HTML, CSS, JS, XML и JSON в сборке, так что оригинал, на который ссылается <a href>, og:image, RSS-вложение, CSS-url() или тег data-no-optimize, остаётся. URL, собираемые в скрипте на лету или отрисованные серверным островом, ему не видны — поставьте prune: false, если такие есть.

Параметры

export default {
  images: {
    widths: [400, 800, 1200],
    formats: ['avif', 'webp'],
    quality: { avif: 55, webp: 78, jpeg: 82 },
    exclude: ['**/og/**'],
  },
}

Два формата дают <picture>

AVIF меньше, но моложе WebP, поэтому указание обоих позволяет браузеру выбрать и сохраняет запасной вариант для старых браузеров:

<picture>
  <source type="image/avif" srcset="/assets/img/hero.*-400.avif 400w, ..." sizes="...">
  <source type="image/webp" srcset="/assets/img/hero.*-400.webp 400w, ..." sizes="...">
  <img src="/assets/img/hero.*-3000.png" alt="..." srcset="..." width="3000" height="2000">
</picture>

Один размер, по имени

Миниатюры карусели, аватары, сетка карточек — некоторые изображения показываются только в одном размере, и целая лестница для них лишняя. Имена файлов вариантов содержат хеш содержимого, так что написать их вручную нельзя; вместо этого укажите ширину в URL, и тег выйдет ровно с этим одним файлом:

<img src="/images/hero.png?w=400" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=400&format=jpeg" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Рассвет над гаванью">
<img src="/assets/img/hero.9f8e7d6c-400.webp"
     alt="Рассвет над гаванью"
     width="400" height="267"
     loading="lazy" decoding="async">

Если настроено несколько formats, закреплённая ширина по-прежнему даёт <picture> — по одному файлу на <source>. Имена те же, что у vite-imagetools. Внешние URL никогда не разбираются — их строка запроса принадлежит источнику, — а при выключенном images статические хосты игнорируют запрос и отдают оригинал.

Пусть картинка сама выберет обрезку

Край или угол предсказуемы, но в карусели разных фотографий объект редко дважды оказывается в одном месте. entropy и attention просят sharp посмотреть на каждую картинку и сдвинуть окно обрезки туда, где найдётся что-то стоящее:

<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Рассвет над гаванью">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="Команда">

Обе — простые эвристики, без модели и обучения, так что это разумное значение по умолчанию для многих изображений, а не гарантия для одного. Для важного главного изображения назовите край. Выбор зависит только от картинки, поэтому он детерминирован и кэшируется как любой другой вариант.

Как исключить изображение

Теги, у которых уже есть srcset, которые находятся внутри <picture> или указывают на SVG, анимированный GIF либо внешний URL, остаются нетронутыми. Для всего остального скажите об этом явно:

<img src="/images/exact.png" alt="Пиксель-арт" data-no-optimize>

Изображения для соцсетей и фавиконки живут в <meta> и <link>, которые здесь никогда не переписываются — их URL остаётся неизменным.

Внешние изображения

Контент, импортированный из CMS, часто указывает на чужой сервер. Включите remote — и такие изображения будут скачаны, оптимизированы и отданы с вашего домена:

export default {
  images: {
    remote: true,
  },
}

Неудачная загрузка оставляет тег ровно таким, каким он был, с предупреждением — нестабильный источник никогда не обрушит вашу сборку. Затем prune убирает локальные оригиналы, на которые больше ничего не ссылается.