Otimização de imagens

Deixa uma imagem em tamanho real em src/ ou public/, aponta-lhe um <img>, e o sitelo redimensiona-a, converte-a para um formato moderno e reescreve a etiqueta com um srcset. Nada para importar, nenhum componente para aprender.

Ativar

export default {
  images: true,
}

A codificação é feita pelo sharp, uma dependência de pares opcional — instala-o ao lado do sitelo quando ativares as imagens:

npm install -D sharp

Sites que não otimizam imagens nunca precisam de o instalar.

Escreve um <img> normal

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

export default () =>
  html({ lang: 'pt' },
    body(
      img({ src: '/images/hero.png', alt: 'Nascer do sol sobre o porto' }),
    ),
  )

Uma origem de 3000×2000 sai assim:

<img src="/assets/img/hero.a1b2c3d4-3000.webp"
     alt="Nascer do sol sobre o porto"
     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">

Um ecrã até 400px de largura recebe o ficheiro de 400, até 800 o de 800, até 1200 o de 1200, e qualquer um mais largo o 3000 completo — o browser escolhe o primeiro degrau que cobre o viewport (a dobrar num ecrã 2×).

O que obténs

A reescrita corre sobre o HTML já compilado, por isso abrange tanto as imagens de src/ como as de public/.

Depois de a tag ser reescrita, nada aponta para o original, por isso é removido do build por omissão. Só vão os ficheiros realmente sem referências: cada HTML, CSS, JS, XML e JSON do build é percorrido, pelo que um original ligado a partir de um <a href>, um og:image, um enclosure de RSS, um url() de CSS ou uma tag data-no-optimize fica. URLs montados em tempo de execução num script, ou renderizados por uma ilha de servidor, são os que não consegue ver — define prune: false se os tiveres.

Opções

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

Dois formatos dão <picture>

O AVIF é mais pequeno mas mais recente que o WebP, por isso listar ambos deixa o navegador escolher e mantém uma alternativa para os antigos:

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

Um tamanho, pelo nome

Miniaturas de um carrossel, avatares, uma grelha de cartões — algumas imagens só aparecem num único tamanho, e uma escada inteira é desperdiçada nelas. Os nomes das variantes levam um hash do conteúdo, por isso não os consegues escrever à mão; indica a largura no URL e a tag sai com esse único ficheiro:

<img src="/images/hero.png?w=400" alt="Nascer do sol sobre o porto">
<img src="/images/hero.png?w=400&format=jpeg" alt="Nascer do sol sobre o porto">
<img src="/images/hero.png?w=200&h=200" alt="Nascer do sol sobre o porto">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Nascer do sol sobre o porto">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Nascer do sol sobre o porto">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Nascer do sol sobre o porto">
<img src="/assets/img/hero.9f8e7d6c-400.webp"
     alt="Nascer do sol sobre o porto"
     width="400" height="267"
     loading="lazy" decoding="async">

Com vários formats configurados, uma largura fixa continua a dar um <picture>, com um ficheiro por <source>. Os nomes são os do vite-imagetools. URLs remotos nunca são analisados — a query string pertence à origem — e, com images desligado, os hosts estáticos ignoram a query e servem o original.

Deixar a imagem escolher o recorte

Uma margem ou um canto é previsível, mas num carrossel de fotos variadas o assunto raramente está duas vezes no mesmo sítio. entropy e attention pedem ao sharp que olhe para cada imagem e desloque a janela de recorte para onde encontrar algo que valha a pena manter:

<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Nascer do sol sobre o porto">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="A equipa">

Ambas são heurísticas simples — sem modelo, sem treino — por isso encara-as como um bom valor por omissão para muitas imagens e não como garantia para uma. Para uma imagem principal que te importe, indica a margem. A escolha depende só da imagem, por isso é determinista e fica em cache como qualquer outra variante.

Como excluir imagens

As etiquetas que já têm srcset, que estão dentro de um <picture>, ou que apontam para um SVG, um GIF animado ou um URL remoto ficam intactas. Para as restantes, di-lo explicitamente:

<img src="/images/exact.png" alt="Pixel art" data-no-optimize>

As imagens de cartões sociais e os favicons vivem em <meta> e <link>, que isto nunca reescreve — mantêm o URL fixo.

Imagens remotas

O conteúdo importado de um CMS aponta muitas vezes para o servidor de outra pessoa. Ativa remote e essas imagens são descarregadas, otimizadas e servidas a partir do teu próprio domínio:

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

Uma descarga falhada deixa a etiqueta exatamente como estava, com um aviso — uma origem instável nunca faz falhar a tua compilação. Depois, prune remove os originais locais que já ninguém referencia.