Ottimizzazione delle immagini
In questa pagina
Butta un’immagine a piena risoluzione in src/ o public/, puntaci un <img>, e sitelo la ridimensiona, la converte in un formato moderno e riscrive il tag con un srcset. Niente da importare, nessun componente da imparare.
Attivarla
export default {
images: true,
}La codifica è affidata a sharp, una peer dependency opzionale — installala accanto a sitelo quando attivi le immagini:
npm install -D sharpI siti che rinunciano all’ottimizzazione delle immagini non hanno mai bisogno di installarla.
Scrivi un semplice <img>
export default () => `
<html lang="it">
<body>
<img src="/images/hero.png" alt="Alba sul porto">
</body>
</html>
`import { html, body, img } from 'javascript-to-html'
export default () =>
html({ lang: 'it' },
body(
img({ src: '/images/hero.png', alt: 'Alba sul porto' }),
),
)export default function Home() {
return (
<html lang="it">
<body>
<img src="/images/hero.png" alt="Alba sul porto" />
</body>
</html>
)
}Una sorgente da 3000×2000 esce dall’altra parte così:
<img src="/assets/img/hero.a1b2c3d4-3000.webp"
alt="Alba sul 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">Uno schermo largo fino a 400px riceve il file da 400, fino a 800 quello da 800, fino a 1200 quello da 1200, e qualunque cosa più larga i 3000 pieni — il browser sceglie il primo gradino che copre il viewport (raddoppiato su uno schermo 2×).
Cosa ottieni
- Ridimensionamento che non ingrandisce mai — tutte le larghezze configurate inferiori a quella della sorgente, più la larghezza della sorgente stessa in cima. Una sorgente da 750px con
widths: [400, 800, 1200]produce 400 e 750, e si ferma lì. - Formati moderni — un solo formato dà un semplice
<img srcset>; due o più lo avvolgono in un<picture>con un ripiego nel formato originale. - Nessuno spostamento del layout —
widtheheightvengono compilati dall’immagine reale, piùloading="lazy"edecoding="async". - Lo stesso markup in sviluppo —
sitelo(sviluppo) riscrive le pagine e serve le varianti su richiesta, così vedi in anteprima ciò che pubblichi. - Una cache condivisa — le varianti sono indicizzate per hash del contenuto in
node_modules/.sitelo/images, così lo sviluppo e le ricostruzioni non codificano mai due volte la stessa immagine.
La riscrittura gira sull’HTML costruito, quindi copre allo stesso modo le immagini da src/ e da public/.
Una volta riscritto un tag, niente punta più all’originale, che quindi viene eliminato dalla build per impostazione predefinita. Se ne vanno solo i file davvero non referenziati: ogni file HTML, CSS, JS, XML e JSON della build viene scandagliato, quindi un originale collegato da un <a href>, da un og:image, da un allegato RSS, da una url() CSS o da un tag data-no-optimize resta. Gli URL montati a runtime in uno script, o renderizzati da un’island server, sono quelli che non può vedere — imposta prune: false se ne hai.
Opzioni
export default {
images: {
widths: [400, 800, 1200],
formats: ['avif', 'webp'],
quality: { avif: 55, webp: 78, jpeg: 82 },
exclude: ['**/og/**'],
},
}widths— predefinito[400, 800, 1200]; la larghezza della sorgente corona sempre la scalaformats— predefinito['webp'];avif,webp,jpeg,pngquality— predefinito{ avif: 55, webp: 78, jpeg: 82 }; qualità dell’encoder per formato (png usa la compressione, non la qualità)sizes— l’attributosizes; unsizessul tag vince sempredimensions— predefinitotrue; aggiungewidth/heightlazy— predefinitotrue; aggiungeloadingedecodingexclude— glob o RegExp di URL di immagini da lasciare stareassetsDir— predefinito'assets/img'cacheDir— predefinito'node_modules/.sitelo/images'remote— predefinitofalse; ottimizza le immaginihttps://in fase di buildprune— predefinitotrue; elimina gli originali che nulla nella build referenzia piùdev— predefinitotrue; mettifalseper servire in sviluppo gli originali intatticoncurrency— codifiche in parallelo, predefinito CPU − 1 (massimo 8); 1 quando remote è attivo
Due formati ti danno
AVIF è più piccolo ma più giovane di WebP, quindi elencarli entrambi lascia scegliere al browser e tiene un ripiego per quelli vecchi:
<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>Una sola dimensione, per nome
Miniature di un carosello, avatar, una griglia di schede — certe immagini si mostrano sempre e solo a una dimensione, e un’intera scala è sprecata per loro. I nomi dei file delle varianti portano un hash del contenuto, quindi non puoi scriverli a mano; indica invece la larghezza nell’URL e il tag esce con quell’unico file:
<img src="/images/hero.png?w=400" alt="Alba sul porto">
<img src="/images/hero.png?w=400&format=jpeg" alt="Alba sul porto">
<img src="/images/hero.png?w=200&h=200" alt="Alba sul porto">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Alba sul porto">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Alba sul porto">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Alba sul porto"><img src="/assets/img/hero.9f8e7d6c-400.webp"
alt="Alba sul porto"
width="400" height="267"
loading="lazy" decoding="async">w— la larghezza in pixel. Va bene qualunque larghezza, non solo quelle inwidths; una sorgente più piccola mantiene la propria dimensione invece di essere ingrandita.h— l’altezza in pixel. Da sola, la larghezza segue le proporzioni; insieme awl’immagine viene scalata per riempire quel riquadro e ritagliata su di esso —?w=200&h=200è una miniatura quadrata.fit— come viene riempito un riquadrow×h:cover(predefinito) lo riempie e ritaglia;containscala l’intera immagine perché ci stia dentro, senza ritagli e senza bordi —width/heightdicono cosa è uscito.background— riempie un risultatocontainfino al riquadro esatto, e implicafit=contain. Esadecimale senza il#(fff,1a1a1a,ffffff80), un nome di colore CSS, oppuretransparent— che richiede un formato con canale alfa; su jpeg viene nero.position— quale parte tiene un ritagliocover. Centrato per impostazione predefinita; un bordo o un angolo (top,left,right-bottom, …), oppure le strategie di sharp sensibili al contenutoentropy/attention.format—avif,webp,jpegopng. Da solo mantiene l’intera scala, in quell’unico formato.
Con più formats configurati, una larghezza fissata ti dà comunque un <picture>, con un file per ogni <source>. I nomi sono quelli usati da vite-imagetools. Gli URL remoti non vengono mai analizzati — la loro query string appartiene all’origine — e con images disattivato gli host statici ignorano la query e servono l’originale.
Lasciare che sia l’immagine a scegliere il ritaglio
Un bordo o un angolo sono prevedibili, ma un carosello di foto assortite raramente ha il soggetto due volte nello stesso posto. entropy e attention chiedono a sharp di guardare ogni immagine e spostare la finestra di ritaglio dove trova qualcosa che vale la pena tenere:
<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Alba sul porto">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="Il team">entropytiene la regione con più dettaglio — bordi, texture, variazione di colore. Cielo piatto, muri vuoti e sfondi uniformi se ne vanno per primi. Buono per paesaggi, prodotti, tutto ciò in cui “affollato” significa “interessante”.attentionsposta il ritaglio verso colori saturi, luminanza ad alta frequenza e tonalità di pelle — un’euristica su dove guarderebbe una persona. Buono per le foto di persone: tende a trovare volti e soggetti.
Sono entrambe semplici euristiche — nessun modello, nessun addestramento — quindi trattale come un default ragionevole per molte immagini più che come una garanzia per una sola. Per un’immagine di apertura a cui tieni, indica tu il bordo. La scelta dipende solo dall’immagine, quindi è deterministica e va in cache come qualunque altra variante.
Tirarsi fuori
I tag che hanno già un srcset, che stanno dentro un <picture>, o che puntano a un SVG, a una GIF animata o a un URL remoto vengono lasciati intatti. Per tutto il resto, dillo:
<img src="/images/exact.png" alt="Pixel art" data-no-optimize>Le immagini per le schede social e le favicon vivono in <meta> e <link>, che questo non riscrive mai — mantengono il loro URL fisso.
Immagini remote
I contenuti importati da un CMS spesso puntano al server di qualcun altro. Attiva remote e quelle immagini vengono scaricate, ottimizzate e servite dal tuo dominio:
export default {
images: {
remote: true,
},
}Uno scaricamento fallito lascia il tag esattamente com’era, con un avviso — un’origine capricciosa non fa mai fallire la tua build. prune poi rimuove gli originali locali che niente referenzia più.