AssetMapper : le frontend pour ceux qui détestent (vraiment) le frontend

Temps de lecture : 9 min

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 (comme app.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.jsassets/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 layout base.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 fichier importmap.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 demande app.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 dossier assets/).
  • Elle copie tous les fichiers nécessaires (votre code, mais aussi les fichiers de assets/vendor/) dans public/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 comme matthiasmullie/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ée public/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).