AssetMapper : le frontend pour ceux qui détestent (vraiment) le frontend
Vous voulez sacrifier une chèvre à la pleine lune en espérant que votre npm install se termine un jour ? Vous avez plus de lignes dans votre webpack.config.js que dans votre application elle-même ? AssetMapper arrive pour sauver cette chèvre et vous libérer du build de vos assets.
importmap
, le GPS natif de vos modules JavaScript
Avant de plonger dans AssetMapper de Symfony, il est essentiel de comprendre la technologie navigateur sur laquelle il repose :
importmap : on parle de JavaScript mais ce sera bref, ne vous inquiétez pas.
Pendant des années, le JavaScript moderne a posé un problème aux navigateurs. Nous écrivions des imports comme ceci :
import { Modal } from 'bootstrap';
import { Application } from '@hotwired/stimulus';
Le navigateur ne comprenait pas ces chemins. bootstrap
ou @hotwired/stimulus
, ça ne correspond à aucun fichier sur le serveur. Pour résoudre cela, nous avons dû utiliser des outils à notre grand désespoir complexes appelés bundlers (WebPack, Rollup, etc.), et pleurer des larmes de sang en hurlant toutes les malédictions possibles sur des erreurs obscures qui survenaient pendant la compilation. Leur travail consistait à lire tous ces import
, à trouver les fichiers correspondants dans un dossier node_modules
, et à tout empaqueter dans un (ou plusieurs) gros fichier app.js
que le navigateur pouvait enfin comprendre (et pour une une fois, on parle pas de Safari). C'était l'étape de build, souvent lente et complexe à configurer.
Et voilà qu'arrive 'importmap
et on en pleure de joie. C'est une fonctionnalité native du navigateur, standardisée (depuis mars 2023), qui résout ce problème sans aucun outil de build.
Concrètement, c'est une simple balise <script type="importmap">
dans votre HTML qui contient un objet JSON. Cet objet agit comme un GPS ou un simple "chercher-remplacer" pour le moteur JavaScript du navigateur.
<script type="importmap">
{
"imports": {
"bootstrap": "/assets/vendor/bootstrap.js",
"@hotwired/stimulus": "/assets/vendor/stimulus.js",
"app": "/assets/app.js"
}
}
</script>
<script type="module" src="/assets/app.js"></script>
Maintenant, quand votre fichier app.js
(ou un autre module) exécute import { Modal } from 'bootstrap';
, le navigateur consulte son importmap
et se dit : "Ah ! bootstrap
signifie en fait que je dois aller chercher le fichier /assets/vendor/bootstrap.js
."
En résumé, 'importmap
permet d'utiliser les imports de modules ES6 modernes directement dans le navigateur, sans étape de "build" ni node_modules
.
C'est sur cette fondation simple et puissante que Symfony a bâti son composant AssetMapper.
AssetMapper : Le frontend moderne de Symfony, sans la complexité de Node.js
Si importmap
est le moteur, AssetMapper est le véhicule complet, luxueux et parfaitement intégré à Symfony. C'est une philosophie qui prend le contre-pied de la complexité des outils frontends modernes (comme Webpack Encore) pour revenir à une simplicité radicale : zéro étape de build, et au bûcher Node.js
AssetMapper s'occupe de deux choses principales :
- Mapper et Versionner : Il trouve vos fichiers (CSS, JS, images...) et les rend accessibles publiquement avec un _hash_de version pour une gestion parfaite du cache (ex:
app.css?v=a1b2c3d4
). -Générer l'Importmap : Il génère automatiquement le<script type="importmap">
pour vous, en mappant vos propres fichiers (commeapp.js
) et les dépendances tierces que vous installez.
Comment ça marche concrètement ?
Hop hop, on installe notre bundle :
composer require symfony/asset-mapper
La recipe Symfony Flex configure tout pour vous et on dit merci :
- Elle crée un dossier
assets/
pour votre code (ex:assets/app.js
,assets/styles/app.css
). - Elle crée un fichier
importmap.php
pour gérer vos dépendances JS. - Elle ajoute la fonction
{{ importmap('app') }}
dans votre layoutbase.html.twig
.
La magie JS avec importmap.php
**
Sashay away package.json
et npm install
: pour ajouter une bibliothèque JS comme Bootstrap, il suffit une simple commande Symfony :
bin/console importmap:require bootstrap
Cette commande fait deux choses :
- Elle ajoute
'bootstrap'
à votre fichierimportmap.php
. - Elle télécharge le fichier JS de Bootstrap (et ses dépendances, comme Popper.js) depuis un CDN (jsDelivr) et le sauvegarde dans
assets/vendor/
.
Désormais, dans votre assets/app.js
, vous pouvez écrire import { Modal } from 'bootstrap';
et, grâce à AssetMapper qui génère l'importmap, le navigateur saura exactement où trouver le fichier.
Le CSS et les Images avec asset()
Pour tout ce qui n'est pas un module JS (CSS, images, polices...), vous utilisez la fonction Twig asset()
que vous connaissez déjà :
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">
<img src="{{ asset('images/logo.png') }}">
En coulisses, AssetMapper intercepte cet appel. Il trouve le fichier (ex: assets/styles/app.css
), calcule son contenu pour créer un hash (ex: a1b2c3d4
), et renvoie un chemin public versionné : /assets/styles/app-a1b2c3d4.css
.
Si vous modifiez une seule ligne de votre CSS, le hash changera, le nom du fichier changera, et le navigateur sera forcé de télécharger la nouvelle version. C'est ce qu'on appelle le "cache busting", et c'est géré pour vous automatiquement.
Comment on configure tout ça ?
C'est très simple :
framework:
asset_mapper:
# Le chemin où AssetMapper doit chercher vos fichiers
paths:
- assets/
# Mode de développement : soyez strict !
missing_import_mode: strict
# Configuration spécifique à la production
when@prod:
framework:
asset_mapper:
# Mode de production : soyez tolérant
missing_import_mode: warn
# Optimisation : pré-compressez tout !
precompress:
format: 'gzip'
paths: [assets/]
C'est la directive la plus fondamentale. Vous dites à AssetMapper : "Mon code source frontend se trouve dans le dossier assets/
". Quand vous demandez {{ asset('styles/app.css') }}
, il sait qu'il doit chercher le fichier physique assets/styles/app.css
.
missing_import_mode: strict
(en développement)
C'est votre garde-fou. En mode strict
, AssetMapper analyse vos fichiers JavaScript à la recherche d'import
. S'il tombe sur import 'un-module-oublie';
et qu'il ne trouve ce module ni dans votre importmap.php
ni dans vos fichiers locaux, il lèvera une erreur fatale, animal (si tu n'as pas la réf c'est que tu es trop jeune).
Pourquoi est ce qu'on aime avoir une erreur ? Cela vous empêche de déployer du code cassé. Vous savez immédiatement lors du rechargement de votre page que vous avez fait une faute de frappe ou que vous avez oublié de lancer bin/console importmap:require un-module-oublie
.
when@prod:
(en production)
Ici on change notre stratégie :
missing_import_mode: warn
En production, on ne veut jamais qu'une erreur d'asset (même si elle ne devrait pas arriver grâce au mode strict en dev) ne fasse planter toute la page. En passant en mode warn, vous dites à Symfony : "Si tu trouves un import manquant, ne lève pas d'erreur. Écris simplement un avertissement dans les logs (var/log/prod.log) et continue." La fonctionnalité JS cassée échouera silencieusement dans le navigateur, mais votre site restera accessible. C'est une mesure de robustesse.precompress: format: 'gzip'
C'est la cerise sur le gâteau de la performance. Quand un navigateur demandeapp.js
, votre serveur web (Nginx, Apache...) doit le compresser en Gzip "à la volée" avant de l'envoyer. Cela consomme du CPU à chaque requête. Avec cette option, lors du déploiement (via la commande asset-map:compile), AssetMapper va non seulement copier vos fichiers dans public/assets, mais il va aussi créer une version pré-compressée à côté (ex: app-a1b2c3d4.js.gz). Votre serveur web (s'il est configuré pour, ex: gzip_static on; sur Nginx) vous dit merci car il n'a plus rien à compresser. Il voit la requête, prend le fichier .gz qui existe déjà, et l'envoie directement.
Le Déploiement en production : l'étape finale
En développement, Symfony sert vos assets dynamiquement. En production, ce serait trop lent.
Votre déploiement doit donc inclure cette commande :
bin/console asset-map:compile
Cette commande effectue le build (qui n'en est pas vraiment un) :
- Elle scanne tous vos
paths
(le dossierassets/
). - Elle copie tous les fichiers nécessaires (votre code, mais aussi les fichiers de
assets/vendor/
) danspublic/assets/
. - Elle leur donne leur nom versionné final (ex:
app-a1b2c3d4.js
). - Grâce à votre config, elle génère les versions
.gz
de ces fichiers.
Une fois cette commande exécutée, votre serveur web sert des fichiers statiques, versionnés et pré-compressés depuis public/assets/
. C'est le scénario le plus rapide possible.
Le bonus performance : minifier ses assets
Si vous avez suivi, vous avez peut-être remarqué un "manque". AssetMapper sert vos fichiers tels quels. Votre assets/app.js
plein de commentaires et d'espaces blancs est servi tel quel en production comme un torchon. Comment le minifier ?
C'est là qu'intervient le sensiolabs/minify-bundle. C'est un bundle "compagnon" parfait pour AssetMapper.
Installation
composer require sensiolabs/minify-bundle
Paramétrage
sensiolabs_minify:
asset_mapper:
# Exclude already minified assets
ignore_paths:
- '*.min.js'
- '*.min.css'
minify:
# Use local binary if available
# binary_path: 'auto'
# Specify local binary path
# binary_path: '/usr/bin/minify'
# Download binary from GitHub
download_binary: true
when@test:
sensiolabs_minify:
asset_mapper: false
Ce bundle est intelligent :
- Il détecte que vous utilisez AssetMapper.
- Il s'accroche automatiquement à la commande
asset-map:compile
.
Désormais, lorsque vous lancez bin/console asset-map:compile
pour votre déploiement, le processus est amélioré :
- AssetMapper trouve votre fichier
assets/app.js
(non minifié). - Nouvelle étape Il passe le contenu à
sensiolabs/minify-bundle
, qui le minifie en utilisant des bibliothèques PHP commematthiasmullie/minify.
- Il enregistre la version minifiée dans
public/assets/app-a1b2c3d4.js
. - Grâce à la config
precompress
, il prend ce fichier minifié et créepublic/assets/app-a1b2c3d4.js.gz
.
Vous aurez désormais à la fois minifié ET pré-compressé en Gzip, le tout sans jamais avoir lancé npm
ou configuré Webpack, et ça c'est magnifique.
Le mot de la fin
AssetMapper est un retour aux sources rafraîchissant et incroyablement efficace. Il est l'outil parfait pour la grande majorité des applications web (non-SPA), en particulier celles utilisant la stack Hotwire/Stimulus (la stack par défaut de Symfony 7).