Ottimizzazione delle immagini

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 sharp

I siti che rinunciano all’ottimizzazione delle immagini non hanno mai bisogno di installarla.

Scrivi un semplice <img>

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

export default () =>
  html({ lang: 'it' },
    body(
      img({ src: '/images/hero.png', alt: 'Alba sul porto' }),
    ),
  )

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

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/**'],
  },
}

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

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

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