Je vais être brutalement honnête : nous sommes fatigués. Fatigués de taper npm install et de voir notre disque dur se remplir de 800 Mo de dépendances obscures pour afficher trois boutons. Fatigués des fichiers de configuration Webpack de 400 lignes que personne ne comprend vraiment, copiés-collés de StackOverflow comme des incantations magiques. Fatigués de devoir mettre à jour 12 paquets critiques juste parce qu'une librairie a décidé de changer son système de bundling du jour au lendemain.
Pendant dix ans, le développement frontend a été pris en otage par une complexité accidentelle. Nous avons construit des cathédrales d'outillage sur des fondations de sable. Nous avons accepté l'idée absurde qu'il fallait "compiler" du JavaScript — un langage interprété par définition — pour qu'il soit lisible par un navigateur.
Et si on arrêtait tout ? Et si on revenait à la simplicité radicale du web, sans sacrifier la modernité ? Ce n'est pas une utopie réactionnaire. C'est la promesse tenue d'AssetMapper.
Dans ce guide, nous n'allons pas juste apprendre à l'utiliser. Nous allons disséquer pourquoi c'est la seule architecture viable pour 95% des projets web modernes, en plongeant au cœur des protocoles réseaux et des moteurs de rendu des navigateurs.
Le web est redevenu la plateforme
Pour comprendre la révolution AssetMapper, il faut comprendre ce qui s'est passé dans vos navigateurs ces dernières années pendant que vous regardiez votre terminal Node.js défiler.
Le navigateur a grandi. Il a mûri. Il supporte désormais nativement trois technologies qui rendent les "bundlers" (Webpack, Rollup, Parcel) obsolètes pour la majorité des cas d'usage :
- Les Modules ES (ESM) : L'instruction
importfonctionne nativement. Fini lesrequire()propriétaires ou les définitions AMD/UMD complexes. - HTTP/3 et le Multiplexing : Le téléchargement de fichiers n'est plus un goulot d'étranglement séquentiel.
- Les Importmaps : La pièce maîtresse du puzzle qui manquait jusqu'en 2023.
Le chaînon manquant : Qu'est-ce qu'un Importmap ?
C'est le GPS du navigateur. Avant l'Importmap, quand vous écriviez import { Controller } from '@hotwired/stimulus', le navigateur levait les bras au ciel : "C'est quoi @hotwired/stimulus ? Je ne connais que des URLs ! Donnez-moi un chemin qui commence par / ou ./".
C'est pour cette unique raison qu'on utilisait Webpack : pour remplacer ce nom abstrait par le code réel du fichier dans un gros bundle.
Aujourd'hui, on donne au navigateur une carte (Importmap) dans le HTML :
<script type="importmap">
{
"imports": {
"@hotwired/stimulus": "/assets/vendor/stimulus.js",
"app": "/assets/app.js",
"leaflet": "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.js"
}
}
</script>
Quand le navigateur rencontre l'import, il consulte la carte, trouve l'URL correspondante, et la télécharge. C'est natif. C'est standard. C'est instantané. Et cela rend l'étape de "Build" obsolète pour la résolution des dépendances.
Comment AssetMapper remplace Webpack
Si importmap est le standard, AssetMapper est le moteur qui l'alimente côté Symfony.
Il ne faut pas le voir comme un "bundler" (il ne concatène rien), mais comme un serveur de fichiers intelligent et un gestionnaire de paquets.
Son rôle est triple et précis :
- Mapper : Il scanne vos dossiers (
assets/) et expose chaque fichier via une URL publique. - Versionner : Il ajoute un hash au contenu (
app.jsdevientapp-d41d8cd98f.js) pour garantir un cache infini. Si vous changez un octet, le nom change, le cache est invalidé. - Générer : Il construit dynamiquement le JSON de l'importmap et l'injecte dans vos templates Twig.
Regardons la configuration réelle de notre projet (config/packages/asset_mapper.yaml) :
framework:
asset_mapper:
# Les dossiers à scanner. AssetMapper va regarder ici récursivement.
paths:
- assets/
# En DEV : si un fichier manque, on veut une erreur fatale immédiate.
# Pas de console JS silencieuse. On veut savoir que ça a cassé.
missing_import_mode: strict
when@prod:
framework:
asset_mapper:
# En PROD : si un fichier manque (rare), on ne plante pas toute la page.
# On loggue un warning critique dans Sentry.
missing_import_mode: warn
C'est tout. Pas de webpack.config.js, pas de vite.config.js, pas de plugins Rollup pour gérer le CSS ou les images. Juste un mapping de dossiers.
La gestion des dépendances sans le trou noir node_modules
C'est ici que le choc culturel est le plus fort.
Ouvrez votre projet Symfony. Cherchez le dossier node_modules. Il n'y en a pas.
Comment gérons-nous nos librairies JS (Stimulus, Turbo, Highlight.js) ?
Via le fichier importmap.php à la racine du projet. C'est le package.json de ce nouveau monde, mais en PHP.
return [
// Notre point d'entrée
'app' => [
'path' => './assets/app.js',
'entrypoint' => true,
],
// Une librairie externe JS
'@hotwired/stimulus' => [
'version' => '3.2.2',
],
// Une librairie CSS ! AssetMapper gère aussi les styles
'highlight.js/styles/atom-one-dark.css' => [
'version' => '11.11.1',
'type' => 'css',
],
];
Pour ajouter une librairie, vous ne tapez pas npm install. Vous tapez :
php bin/console importmap:require highlight.js
Que se passe-t-il sous le capot ?
- AssetMapper interroge un registre CDN fiable (JSDelivr par défaut).
- Il télécharge le fichier JS (et ses dépendances !).
- Il le stocke physiquement dans
assets/vendor/. - Il met à jour
importmap.php.
Résultat : Votre projet est totalement autonome. Si JSDelivr tombe en panne demain, votre site fonctionne toujours car les fichiers sont chez vous. Vous possédez vos dépendances, mais sans la lourdeur de l'écosystème Node.
Pourquoi HTTP/3 tue le Bundling
Les sceptiques (et les experts Webpack) diront immédiatement : "Mais télécharger 50 fichiers JS séparés, c'est lent ! Le Waterfall (chargement en cascade) va tuer la performance ! Il faut concaténer !"
C'était vrai en 2015 avec HTTP/1.1. C'est faux en 2026 avec HTTP/3.
Le problème du Head-of-Line Blocking
Avec HTTP/1.1 (et le bundling), on mettait tout dans un gros tuyau TCP unique. Si un paquet de données était perdu, tout s'arrêtait. C'était le "Head-of-Line Blocking". De plus, le navigateur limitait le nombre de connexions parallèles (souvent à 6).
La solution HTTP/3 (QUIC)
Avec FrankenPHP (notre serveur web), HTTP/3 est activé par défaut via le protocole UDP/QUIC. HTTP/3 utilise le multiplexing natif. Il peut envoyer 50, 100, 200 fichiers simultanément sur la même connexion UDP, chacun dans son propre flux indépendant. Si un paquet d'une image est perdu, le fichier JS à côté continue de se charger.
Dans ce contexte, charger 50 petits fichiers de 10Ko est souvent plus rapide que de charger un gros fichier de 500Ko, car le navigateur peut commencer à parser et exécuter les premiers fichiers pendant que les autres arrivent.
Le cache granulaire : l'avantage décisif
Mieux encore : le Cache Granulaire. Avec un gros bundle Webpack (app.js de 5Mo), si vous changez une seule ligne de code dans un contrôleur obscur, le hash du fichier change (app-v2.js), et l'utilisateur doit re-télécharger les 5Mo.
Avec AssetMapper, si vous changez search_controller.js, l'utilisateur ne re-télécharge que search_controller.js (3Ko). Tout le reste (Stimulus, Turbo, le cœur de votre app) reste en cache navigateur instantané (304 Not Modified ou cache disque).
Le CSS moderne : Tailwind sans Node.js
"D'accord pour le JS, mais je ne peux pas vivre sans Tailwind, et Tailwind a besoin de Node pour compiler ses classes utilitaires."
Faux. Tailwind est écrit en Go/Rust (selon les versions et outils). Il existe un binaire autonome.
L'écosystème Symfony a créé le symfonycasts/tailwind-bundle pour l'intégrer.
Il télécharge le binaire Tailwind pour votre architecture (Linux/Mac/Windows), le met dans le dossier var/, et l'utilise pour compiler votre CSS en tâche de fond.
En développement :
php bin/console tailwind:build --watch
C'est aussi rapide que la version Node. C'est transparent. Et ça s'intègre parfaitement avec AssetMapper :
- Le binaire génère
assets/styles/app.tailwind.css. - AssetMapper sert ce fichier, le versionne et l'inclut dans l'importmap.
Vous avez la puissance de Tailwind, sans avoir installé une seule fois npm.
La Performance ultime : preload et compression
En production, nous ne voulons pas que le navigateur "découvre" les fichiers JS au fur et à mesure qu'il les parse (Waterfall). Nous voulons qu'il les télécharge tous dès la première milliseconde.
AssetMapper gère cela via le Preloading (Link headers).
Comme il sait exactement quels fichiers sont nécessaires pour votre point d'entrée app (grâce à l'importmap), il ajoute automatiquement des headers HTTP Link à la réponse initiale du serveur :
Link: </assets/app-123.js>; rel="modulepreload"
Link: </assets/vendor/stimulus-456.js>; rel="modulepreload"
Le navigateur lance les téléchargements avant même d'avoir fini de parser le HTML.
De plus, nous activons la pré-compression dans config/packages/prod/asset_mapper.yaml :
when@prod:
framework:
asset_mapper:
precompress:
enabled: true
formats: [gzip, brotli]
Lors du déploiement (commande composer run-script auto-scripts-build), AssetMapper génère les versions .gz et .br de tous vos assets.
FrankenPHP est configuré pour chercher ces fichiers. S'il voit app.js demandé et que app.js.br existe, il sert le fichier Brotli directement.
C'est le niveau zéro de l'utilisation CPU en production.
Le mot de la fin
AssetMapper n'est pas un retour en arrière vers l'époque du "jQuery spaghetti". C'est un saut vers l'avant, par-dessus la complexité accidentelle des dix dernières années.
En supprimant l'étape de build, nous avons :
- Supprimé la fragilité de la CI/CD : Plus de
npm installqui échoue aléatoirement ou prend 10 minutes. - Accéléré le déploiement : On pousse des fichiers statiques, on ne compile rien sur le serveur.
- Simplifié le debugging : Le code qui tourne dans le navigateur est votre code exact, pas une bouillie minifiée et transpilée. Le "Source Map" devient inutile car le code est lisible par défaut.
- Rendu le projet pérenne : Les standards web (ESM, Importmap) ne changent pas tous les 6 mois comme les configurations Webpack/Vite.
Pour ceux qui aiment la pureté, la simplicité et la performance, AssetMapper est la seule voie. Pour les autres, il reste node_modules et ses 800 Mo de nostalgie.