Nette Assets

Fatigué de gérer manuellement les fichiers statiques de vos applications web ? Oubliez les chemins écrits en dur, l'invalidation du cache et les soucis de versionnage des fichiers. Nette Assets transforme votre façon de travailler avec les images, les feuilles de style, les scripts et les autres ressources statiques.

  • Le versionnage intelligent garantit que les navigateurs chargent toujours les fichiers les plus récents
  • La détection automatique des types de fichiers et de leurs dimensions
  • L'intégration transparente à Latte avec des balises intuitives
  • Une architecture souple prenant en charge les systèmes de fichiers, les CDN et Vite
  • Le chargement paresseux pour des performances optimales

Pourquoi Nette Assets ?

Travailler avec des fichiers statiques signifie souvent écrire du code répétitif et propice aux erreurs. Vous composez les URL à la main, ajoutez des paramètres de version pour invalider le cache et traitez différemment chaque type de fichier. Cela donne du code comme celui-ci :

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

Avec Nette Assets, toute cette complexité disparaît :

{* Tout est automatisé - URL, versionnage, dimensions *}
<img n:asset="images/logo.png">
<link n:asset="css/style.css">

{* Ou simplement *}
{asset 'css/style.css'}

C'est tout ! La bibliothèque, automatiquement :

  • ajoute des paramètres de version d'après la date de modification du fichier
  • détecte les dimensions des images et les inclut dans le HTML
  • génère l'élément HTML correct pour chaque type de fichier
  • gère aussi bien l'environnement de développement que celui de production

Installation

Installez Nette Assets à l'aide de Composer :

composer require nette/assets

Elle nécessite PHP 8.1 ou plus récent et fonctionne parfaitement avec Nette Framework, mais peut aussi être utilisée seule.

Premiers pas

Nette Assets fonctionne immédiatement, sans aucune configuration. Placez vos fichiers statiques dans le répertoire www/assets/ et commencez à les utiliser :

{* Affiche une image avec ses dimensions automatiques *}
{asset 'logo.png'}

{* Inclut une feuille de style avec versionnage *}
{asset 'style.css'}

{* Charge un script *}
{asset 'app.js'}

Pour plus de contrôle sur le HTML généré, utilisez l'attribut n:asset ou la fonction asset().

Comment ça marche

Nette Assets s'articule autour de trois concepts fondamentaux qui la rendent puissante tout en restant simple à utiliser :

Assets – vos fichiers devenus intelligents

Un asset représente n'importe quel fichier statique de votre application. Chaque fichier devient un objet doté de propriétés en lecture seule utiles :

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

Les différents types de fichiers offrent des propriétés différentes :

  • Images : largeur, hauteur, texte alternatif, chargement paresseux
  • Scripts : type de module, hachages d'intégrité, crossorigin
  • Feuilles de style : media queries, intégrité
  • Audio/vidéo : durée, dimensions (vidéo seulement)
  • Polices : préchargement correct avec CORS

La bibliothèque détecte automatiquement les types de fichiers et crée la classe d'asset appropriée.

Mappers – d'où viennent les fichiers

Un mapper sait comment trouver les fichiers et créer leurs URL. Vous pouvez avoir plusieurs mappers à des fins différentes – fichiers locaux, CDN, stockage cloud ou outils de build (chacun porte un nom). Le FilesystemMapper intégré s'occupe des fichiers locaux, tandis que ViteMapper s'intègre aux outils de build modernes.

Les mappers sont définis dans la configuration.

Registry – votre interface principale

Le registre gère tous les mappers et fournit l'API principale :

// Injectez le registre dans votre service
public function __construct(
	private Nette\Assets\Registry $assets
) {}
// Récupère des assets depuis différents mappers
$logo = $this->assets->getAsset('images:logo.png'); // mapper 'images'
$app = $this->assets->getAsset('app:main.js'); // mapper 'app'
$style = $this->assets->getAsset('style.css'); // utilise le mapper par défaut

Le registre choisit automatiquement le bon mapper et met les résultats en cache pour les performances.

Travailler avec les assets en PHP

Le registre fournit deux méthodes pour récupérer les assets :

// Lève Nette\Assets\AssetNotFoundException si le fichier n'existe pas
$logo = $assets->getAsset('logo.png');

// Renvoie null si le fichier n'existe pas
$banner = $assets->tryGetAsset('banner.jpg');
if ($banner) {
	echo $banner->url;
}

Indiquer les mappers

Vous pouvez choisir explicitement le mapper à utiliser :

// Utilise le mapper par défaut
$file = $assets->getAsset('document.pdf');

// Utilise un mapper précis avec un préfixe
$image = $assets->getAsset('images:photo.jpg');

// Utilise un mapper précis avec la syntaxe tableau
$script = $assets->getAsset(['scripts', 'app.js']);

Propriétés et types des assets

Chaque type d'asset fournit les propriétés en lecture seule qui le concernent :

// Propriétés d'une image
$image = $assets->getAsset('photo.jpg');
echo $image->width;     // 1920
echo $image->height;    // 1080
echo $image->mimeType;  // 'image/jpeg'

// Propriétés d'un script
$script = $assets->getAsset('app.js');
echo $script->type;     // null ('module' pour les points d'entrée Vite)

// Propriétés d'un fichier audio
$audio = $assets->getAsset('song.mp3');
echo $audio->duration;  // durée en secondes

// Tous les assets peuvent être convertis en chaîne (ce qui renvoie l'URL)
$url = (string) $assets->getAsset('document.pdf');

Les propriétés comme les dimensions ou la durée ne sont chargées paresseusement qu'au moment de leur accès, ce qui garde la bibliothèque rapide.

Pour une analyse statique précise, installez l'extension nette/phpstan-rules. PHPStan connaît alors le type concret de chaque asset : getAsset('photo.jpg') est compris comme un ImageAsset et l'accès à ->width ne provoque aucune erreur.

Utiliser les assets dans les templates Latte

Nette Assets offre une intégration intuitive à Latte avec des balises et des fonctions.

{asset}

La balise {asset} rend des éléments HTML complets :

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

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

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

La balise, automatiquement :

  • détecte le type d'asset et génère le HTML approprié
  • inclut le versionnage pour invalider le cache
  • ajoute les dimensions des images
  • pose les bons attributs (type, media, etc.)

Utilisée à l'intérieur d'attributs HTML ou dans les éléments <style> et <script>, elle n'affiche que l'URL :

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

n:asset

Pour le contrôle total des attributs HTML :

{* L'attribut n:asset remplit src, les dimensions, etc. *}
<img n:asset="product.jpg" alt="Produit" class="rounded">

{* Fonctionne avec tout élément pertinent *}
<script n:asset="analytics.js" defer></script>
<link n:asset="print.css" media="print">
<audio n:asset="podcast.mp3" controls></audio>

Utilisez des variables et des mappers :

{* Les variables fonctionnent naturellement *}
<img n:asset="$product->image">

{* Indiquez le mapper avec des accolades *}
<img n:asset="images:{$product->image}">

{* Indiquez le mapper avec la notation tableau *}
<img n:asset="[images, $product->image]">

n:asset fonctionne aussi sur <a>, où il remplit href, et sur <link>, où il crée un indice de préchargement :

<a n:asset="hero.jpg">Télécharger l'image</a>

Notez que les variantes <a> et <link> ne fonctionnent que pour les assets rendables (une image, un script, etc.), jamais pour un GenericAsset comme un PDF.

Pour les images, il suffit d'indiquer seulement width (ou seulement height) et l'autre dimension est calculée automatiquement pour conserver les proportions :

{* height est complétée d'après les proportions *}
<img n:asset="product.jpg" width="200">

asset()

Pour une souplesse maximale, utilisez la fonction asset() :

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

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

Assets facultatifs

Gérez élégamment les assets manquants avec {asset?}, n:asset? et tryAsset() :

{* Balise facultative - ne rend rien si l'asset manque *}
{asset? 'optional-banner.jpg'}

{* Attribut facultatif - passé si l'asset manque *}
<img n:asset?="user-avatar.jpg" alt="Avatar" class="avatar">

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

{preload}

Améliorez les performances de chargement de la page :

{* Dans votre section <head> *}
{preload 'critical.css'}
{preload 'important-font.woff2'}
{preload 'hero-image.jpg'}

Génère les liens de préchargement appropriés :

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

Lorsque votre réponse envoie un en-tête Content-Security-Policy avec un nonce, Nette ajoute automatiquement l'attribut nonce correspondant à chaque élément <script>, <link> et <style> généré, afin qu'ils ne soient pas bloqués par la politique de sécurité du navigateur.

Fonctionnalités avancées

Détection automatique de l'extension

Gérez plusieurs formats automatiquement :

assets:
	mapping:
		images:
			path: img
			extension: [webp, jpg, png]  # Essayer dans cet ordre

Vous pouvez désormais demander le fichier sans extension :

{* Trouve automatiquement logo.webp, logo.jpg ou logo.png *}
{asset 'images:logo'}

Parfait pour l'amélioration progressive avec les formats modernes.

Versionnage intelligent

Les fichiers sont automatiquement versionnés d'après leur date de modification :

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

Quand vous mettez le fichier à jour, l'horodatage change, ce qui force le rafraîchissement du cache du navigateur.

Contrôlez le versionnage asset par asset :

// Désactive le versionnage pour un asset précis
$asset = $assets->getAsset('style.css', ['version' => false]);
{* Dans Latte *}
{asset 'style.css', version: false}

La même syntaxe référence, clé: valeur passe aussi des options à n:asset et {preload} :

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

Assets de police

Les polices reçoivent un traitement particulier avec le bon CORS :

{* Préchargement correct avec crossorigin *}
{preload 'fonts:OpenSans-Regular.woff2'}

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

Mappers personnalisés

Créez des mappers personnalisés pour des besoins particuliers comme le stockage cloud ou la génération dynamique :

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);
	}
}

Enregistrez-le dans la configuration :

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

Utilisez-le comme n'importe quel autre mapper :

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

La méthode Helpers::createAssetFromUrl() crée automatiquement le bon type d'asset d'après l'extension du fichier.

Les types d'assets qui implémentent l'interface Nette\Assets\HtmlRenderable (images, scripts, styles, etc.) peuvent être rendus par {asset} comme un élément HTML complet. Les autres types de fichiers deviennent un GenericAsset (par exemple un PDF), qui ne peut pas être rendu comme élément HTML mais fournit tout de même une URL (et d'autres métadonnées). Tenter de rendre un tel asset comme élément HTML lève une Nette\InvalidArgumentException ; vous pouvez malgré tout utiliser son URL à l'intérieur d'un attribut.

Pour aller plus loin

version: 1.x