Otimização de imagens
Nesta página
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 sharpSites que não otimizam imagens nunca precisam de o instalar.
Escreve um <img> normal
export default () => `
<html lang="pt">
<body>
<img src="/images/hero.png" alt="Nascer do sol sobre o porto">
</body>
</html>
`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' }),
),
)export default function Home() {
return (
<html lang="pt">
<body>
<img src="/images/hero.png" alt="Nascer do sol sobre o porto" />
</body>
</html>
)
}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
- Redimensionamento que nunca amplia — cada largura configurada abaixo da da fonte, e por cima a largura da própria fonte. Uma fonte de 750px com
widths: [400, 800, 1200]produz 400 e 750, e fica por aí. - Formatos modernos — com um só formato obténs um
<img srcset>normal; com dois ou mais, tudo é envolvido num<picture>com alternativa no formato original. - Sem saltos de layout —
widtheheightsão preenchidos a partir da imagem real, maisloading="lazy"edecoding="async". - A mesma marcação em desenvolvimento —
sitelo(dev) reescreve as páginas e serve as variantes a pedido, por isso vês em pré-visualização o que vais publicar. - Uma cache partilhada — as variantes são indexadas por hash de conteúdo em
node_modules/.sitelo/images, de modo que nem o desenvolvimento nem as recompilações codificam a mesma imagem duas vezes.
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/**'],
},
}widths— por omissão[400, 800, 1200]; a largura da própria fonte fica sempre no topo da escadaformats— por omissão['webp'];avif,webp,jpeg,pngquality— por omissão{ avif: 55, webp: 78, jpeg: 82 }; qualidade do codificador por formato (o png usa compressão, não qualidade)sizes— o atributosizes; umsizesposto na etiqueta ganha sempredimensions— por omissãotrue; acrescentawidth/heightlazy— por omissãotrue; acrescentaloadingedecodingexclude— globs ou RegExp de URLs de imagem a não tocarassetsDir— por omissão'assets/img'cacheDir— por omissão'node_modules/.sitelo/images'remote— por omissãofalse; otimiza imagenshttps://na compilaçãoprune— por omissãofalse; apaga os originais que já nada no build referenciadev— por omissãotrue; põefalsepara servir os originais intactos em desenvolvimentoconcurrency— codificações em paralelo, por omissão CPUs − 1 (máx. 8); 1 quando o remote está ativo
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">w— a largura em píxeis. Qualquer largura serve, não só as dewidths; uma fonte mais pequena mantém o seu próprio tamanho em vez de ser ampliada.h— a altura em píxeis. Sozinha, a largura segue a proporção; comwtambém, a imagem é escalada para preencher essa caixa e recortada —?w=200&h=200é uma miniatura quadrada.fit— como uma caixaw×hé cumprida:cover(por omissão) preenche-a e recorta;containescala a imagem inteira para caber lá dentro, sem recorte nem margens —width/heightdizem o que saiu.background— preenche um resultadocontainaté à caixa exata e implicafit=contain. Hex sem#(fff,1a1a1a,ffffff80), um nome de cor CSS outransparent— que precisa de um formato com alfa; em jpeg sai preto.position— que parte um recortecovermantém. Centrado por omissão; uma margem ou canto (top,left,right-bottom, …), ou as estratégias do sharp guiadas pelo conteúdo,entropy/attention.format—avif,webp,jpegoupng. Sozinho, mantém a escada completa, nesse único formato.
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">entropymantém a região com mais detalhe — contornos, textura, variação de cor. Céu liso, paredes vazias e fundos planos vão primeiro. Bom para paisagens, produtos, tudo onde «cheio» significa «interessante».attentioninclina o recorte para cores saturadas, luminância de alta frequência e tons de pele — uma heurística de para onde uma pessoa olharia. Bom para fotos de pessoas: tende a encontrar rostos e assuntos.
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.