Nette Assets

Stanchi di gestire a mano i file statici nelle vostre applicazioni web? Dimenticate i percorsi scritti a mano, l'invalidazione della cache e le preoccupazioni sul versionamento dei file. Nette Assets trasforma il modo in cui lavorate con immagini, fogli di stile, script e altre risorse statiche.

  • Il versionamento intelligente garantisce che i browser carichino sempre i file più recenti
  • Rilevamento automatico dei tipi di file e delle dimensioni
  • Integrazione fluida con Latte grazie a tag intuitivi
  • Architettura flessibile che supporta filesystem, CDN e Vite
  • Caricamento pigro per prestazioni ottimali

Perché Nette Assets?

Lavorare con i file statici significa spesso codice ripetitivo e soggetto a errori. Costruite gli URL a mano, aggiungete i parametri di versione per invalidare la cache e trattate in modo diverso i vari tipi di file. Il che porta a codice come questo:

<img src="/images/logo.png?v=1699123456" width="200" height="100" alt="Logo">
<link rel="stylesheet" href="/css/style.css?v=2">

Con Nette Assets tutta questa complessità sparisce:

{* tutto automatizzato: URL, versionamento, dimensioni *}
<img n:asset="images/logo.png">
<link n:asset="css/style.css">

{* oppure solo *}
{asset 'css/style.css'}

Ecco fatto! La libreria automaticamente:

  • aggiunge i parametri di versione in base all'ora di modifica del file
  • rileva le dimensioni delle immagini e le inserisce nell'HTML
  • genera l'elemento HTML corretto per ogni tipo di file
  • gestisce sia l'ambiente di sviluppo sia quello di produzione

Installazione

Installate Nette Assets con Composer:

composer require nette/assets

Richiede PHP 8.1 o superiore e funziona perfettamente con il Nette Framework, ma si può usare anche da solo.

Primi passi

Nette Assets funziona subito senza alcuna configurazione. Mettete i vostri file statici nella directory www/assets/ e cominciate a usarli:

{* mostra un'immagine con le dimensioni automatiche *}
{asset 'logo.png'}

{* include un foglio di stile con il versionamento *}
{asset 'style.css'}

{* carica uno script *}
{asset 'app.js'}

Per un maggiore controllo sull'HTML generato usate l'attributo n:asset oppure la funzione asset().

Come funziona

Nette Assets è costruito attorno a tre concetti fondamentali che lo rendono potente e allo stesso tempo semplice da usare:

Asset: i vostri file resi intelligenti

Un asset rappresenta qualsiasi file statico della vostra applicazione. Ogni file diventa un oggetto con utili proprietà readonly:

$image = $assets->getAsset('photo.jpg');
echo $image->url;      // '/assets/photo.jpg?v=1699123456'
echo $image->file;     // '/var/www/assets/photo.jpg' (percorso locale, oppure null)
echo $image->width;    // 1920
echo $image->height;   // 1080
echo $image->mimeType; // 'image/jpeg'

Tipi di file diversi offrono proprietà diverse:

  • Immagini: larghezza, altezza, testo alternativo, caricamento pigro
  • Script: tipo di modulo, hash di integrità, crossorigin
  • Fogli di stile: media query, integrità
  • Audio/video: durata, dimensioni (solo video)
  • Font: preloading corretto con CORS

La libreria rileva automaticamente i tipi di file e crea la classe di asset appropriata.

Mapper: da dove vengono i file

Un mapper sa come trovare i file e creare gli URL per essi. Potete avere più mapper per scopi diversi: file locali, CDN, storage cloud o strumenti di build (ognuno ha un nome). Il FilesystemMapper integrato si occupa dei file locali, mentre ViteMapper si integra con i moderni strumenti di build.

I mapper si definiscono nella configurazione.

Registry: la vostra interfaccia principale

Il registry gestisce tutti i mapper e offre l'API principale:

// fatevi iniettare il registry nel vostro servizio
public function __construct(
	private Nette\Assets\Registry $assets
) {}
// ottenete gli asset dai vari mapper
$logo = $this->assets->getAsset('images:logo.png'); // mapper 'images'
$app = $this->assets->getAsset('app:main.js'); // mapper 'app'
$style = $this->assets->getAsset('style.css'); // usa il mapper predefinito

Il registry sceglie automaticamente il mapper giusto e mette in cache i risultati per le prestazioni.

Lavorare con gli asset in PHP

Il Registry offre due metodi per ottenere gli asset:

// lancia Nette\Assets\AssetNotFoundException se il file non esiste
$logo = $assets->getAsset('logo.png');

// restituisce null se il file non esiste
$banner = $assets->tryGetAsset('banner.jpg');
if ($banner) {
	echo $banner->url;
}

Indicare i mapper

Potete scegliere esplicitamente quale mapper usare:

// usa il mapper predefinito
$file = $assets->getAsset('document.pdf');

// usa un mapper specifico con il prefisso
$image = $assets->getAsset('images:photo.jpg');

// usa un mapper specifico con la sintassi ad array
$script = $assets->getAsset(['scripts', 'app.js']);

Proprietà e tipi degli asset

Ogni tipo di asset offre le proprietà readonly pertinenti:

// proprietà di un'immagine
$image = $assets->getAsset('photo.jpg');
echo $image->width;     // 1920
echo $image->height;    // 1080
echo $image->mimeType;  // 'image/jpeg'

// proprietà di uno script
$script = $assets->getAsset('app.js');
echo $script->type;     // null ('module' per i punti di ingresso di Vite)

// proprietà di un audio
$audio = $assets->getAsset('song.mp3');
echo $audio->duration;  // durata in secondi

// tutti gli asset si possono convertire in stringa (restituisce l'URL)
$url = (string) $assets->getAsset('document.pdf');

Proprietà come le dimensioni o la durata vengono caricate pigramente solo quando vi si accede, il che mantiene la libreria veloce.

Per un'analisi statica precisa installate l'estensione nette/phpstan-rules. PHPStan conosce allora il tipo concreto di ogni asset, quindi getAsset('photo.jpg') viene inteso come ImageAsset e l'accesso a ->width non provoca alcun errore.

Usare gli asset nei template Latte

Nette Assets offre un'integrazione intuitiva con Latte tramite tag e funzioni.

{asset}

Il tag {asset} renderizza elementi HTML completi:

{* renderizza: <img src="/assets/hero.jpg?v=123" width="1920" height="1080"> *}
{asset 'hero.jpg'}

{* renderizza: <script src="/assets/app.js?v=456"></script> *}
{asset 'app.js'}

{* renderizza: <link rel="stylesheet" href="/assets/style.css?v=789"> *}
{asset 'style.css'}

Il tag automaticamente:

  • rileva il tipo di asset e genera l'HTML appropriato
  • include il versionamento per invalidare la cache
  • aggiunge le dimensioni per le immagini
  • imposta gli attributi corretti (type, media ecc.)

Quando si usa dentro un attributo HTML oppure dentro gli elementi <style> e <script>, stampa solo l'URL:

<div style="background-image: url({asset 'bg.jpg'})">
<img srcset="{asset 'logo@2x.png'} 2x">

n:asset

Per il pieno controllo sugli attributi HTML:

{* l'attributo n:asset riempie src, dimensioni ecc. *}
<img n:asset="product.jpg" alt="Product" class="rounded">

{* funziona con qualsiasi elemento pertinente *}
<script n:asset="analytics.js" defer></script>
<link n:asset="print.css" media="print">
<audio n:asset="podcast.mp3" controls></audio>

Usate variabili e mapper:

{* le variabili funzionano naturalmente *}
<img n:asset="$product->image">

{* indicate il mapper con le parentesi graffe *}
<img n:asset="images:{$product->image}">

{* indicate il mapper con la notazione ad array *}
<img n:asset="[images, $product->image]">

n:asset funziona anche su <a>, dove riempie href, e su <link>, dove crea un hint di preload:

<a n:asset="hero.jpg">Scarica l'immagine</a>

Notate che le varianti <a> e <link> funzionano solo per gli asset renderizzabili (un'immagine, uno script e così via), mai per un GenericAsset come un PDF.

Per le immagini basta impostare solo width (oppure solo height) e l'altra dimensione viene calcolata automaticamente per mantenere le proporzioni:

{* height viene completato dalle proporzioni *}
<img n:asset="product.jpg" width="200">

asset()

Per la massima flessibilità usate la funzione asset():

{var $logo = asset('logo.png')}
<img src={$logo} width={$logo->width} height={$logo->height}>

{* oppure direttamente *}
<img src={asset('logo.png')} alt="Logo">

Asset facoltativi

Gestite con eleganza gli asset mancanti con {asset?}, n:asset? e tryAsset():

{* tag facoltativo: non renderizza nulla se l'asset manca *}
{asset? 'optional-banner.jpg'}

{* attributo facoltativo: viene saltato se l'asset manca *}
<img n:asset?="user-avatar.jpg" alt="Avatar" class="avatar">

{* con ripiego *}
{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')}
<img n:asset=$avatar alt="Avatar">

{preload}

Migliorate le prestazioni di caricamento della pagina:

{* nella vostra sezione <head> *}
{preload 'critical.css'}
{preload 'important-font.woff2'}
{preload 'hero-image.jpg'}

Genera i link di preload appropriati:

<link rel="preload" href="/assets/critical.css?v=123" as="style">
<link rel="preload" href="/assets/important-font.woff2?v=456" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.jpg?v=789" as="image" type="image/jpeg">

Quando la vostra risposta imposta un header Content-Security-Policy con un nonce, Nette aggiunge automaticamente l'attributo nonce corrispondente a ogni elemento <script>, <link> e <style> generato, così non vengono bloccati dalla policy di sicurezza del browser.

Funzionalità avanzate

Rilevamento automatico dell'estensione

Gestite automaticamente più formati:

assets:
	mapping:
		images:
			path: img
			extension: [webp, jpg, png]  # prova nell'ordine

Ora potete richiederli senza estensione:

{* trova automaticamente logo.webp, logo.jpg oppure logo.png *}
{asset 'images:logo'}

Perfetto per il miglioramento progressivo con i formati moderni.

Versionamento intelligente

I file vengono versionati automaticamente in base all'ora di modifica:

{asset 'style.css'}
{* output: <link rel="stylesheet" href="/assets/style.css?v=1699123456"> *}

Quando aggiornate il file, il timestamp cambia e costringe il browser ad aggiornare la cache.

Governate il versionamento per singolo asset:

// disattiva il versionamento per un asset specifico
$asset = $assets->getAsset('style.css', ['version' => false]);
{* in Latte *}
{asset 'style.css', version: false}

La stessa sintassi riferimento, chiave: valore passa le opzioni anche a n:asset e a {preload}:

<img n:asset="photo.jpg, version: false">
{preload 'style.css', version: false}

Asset di tipo font

I font ricevono un trattamento particolare con il CORS corretto:

{* preload corretto con crossorigin *}
{preload 'fonts:OpenSans-Regular.woff2'}

{* uso nel CSS *}
<style>
@font-face {
	font-family: 'Open Sans';
	src: url('{asset 'fonts:OpenSans-Regular.woff2'}') format('woff2');
	font-display: swap;
}
</style>

Mapper personalizzati

Create mapper personalizzati per esigenze particolari, come lo storage cloud o la generazione dinamica:

use Nette\Assets\Mapper;
use Nette\Assets\Asset;
use Nette\Assets\Helpers;

class CloudStorageMapper implements Mapper
{
	public function __construct(
		private CloudClient $client,
		private string $bucket,
	) {}

	public function getAsset(string $reference, array $options = []): Asset
	{
		if (!$this->client->exists($this->bucket, $reference)) {
			throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found");
		}

		$url = $this->client->getPublicUrl($this->bucket, $reference);
		return Helpers::createAssetFromUrl($url);
	}
}

Registratelo nella configurazione:

assets:
	mapping:
		cloud: CloudStorageMapper(@cloudClient, 'my-bucket')

Usatelo come qualsiasi altro mapper:

{asset 'cloud:user-uploads/photo.jpg'}

Il metodo Helpers::createAssetFromUrl() crea automaticamente il tipo di asset corretto in base all'estensione del file.

I tipi di asset che implementano l'interfaccia Nette\Assets\HtmlRenderable (immagini, script, stili e così via) possono essere renderizzati da {asset} come elemento HTML completo. Gli altri tipi di file diventano un GenericAsset (per esempio un PDF), che non si può renderizzare come elemento HTML ma offre comunque un URL (e altri metadati). Provare a renderizzare un asset del genere come elemento HTML lancia Nette\InvalidArgumentException; potete comunque usarne l'URL dentro un attributo.

Letture consigliate

versione: 1.x