AssetMapper et TypeScript : récupérer les types manquants avec un bundle Symfony
AssetMapper simplifie la gestion des assets dans Symfony, mais il ne télécharge pas les types TypeScript. Votre IDE ne connaît rien des libs JS que vous utilisez, tout est any implicite, et les erreurs de type passent à la trappe.
Le problème : AssetMapper ne télécharge pas les types
Avec npm, quand on installe une dépendance, on récupère l'intégralité du package : les sources, les fichiers de déclaration TypeScript (.d.ts), les métadonnées. TypeScript sait où chercher les types, et l'IDE suit.
AssetMapper fonctionne différemment. Il télécharge uniquement le fichier JS exposé par le package, ce qui suffit pour l'exécution, mais pas pour le type checking. Les fichiers .d.ts ne font pas partie du bundle téléchargé.
Conséquence directe : TypeScript et les IDE comme VSCode ou PhpStorm ne trouvent aucune définition de type pour vos imports. Tout est any implicite. Les erreurs de type ne sont pas détectées et l'autocomplétion ne fonctionne pas.
Comment TypeScript résout les types
Avant de chercher une solution, il faut comprendre le mécanisme de résolution.
Un package JS peut embarquer ses propres définitions de types via un champ types ou typings dans son package.json. Ce champ pointe vers un fichier .d.ts racine, qui peut lui-même importer d'autres fichiers de déclaration. C'est ce graphe de fichiers que TypeScript parcourt pour résoudre les types à la compilation.
Dans un projet standard avec npm, tout ça est dans node_modules. TypeScript sait regarder là par défaut. Sans node_modules, il faut lui dire explicitement où chercher. C'est le rôle de la config paths dans tsconfig.json.
{ "compilerOptions": { "paths": { "@hotwired/stimulus": ["./assets/vendor/@hotwired/stimulus/dist/types/index.d.ts"] } } }
pathsfait correspondre un identifiant d'import (comme@hotwired/stimulus) à un ou plusieurs chemins locaux. TypeScript les parcourt dans l'ordre jusqu'à trouver une correspondance.
La solution manuelle
La procédure est simple, mais répétitive à faire à la main pour chaque package.
Première étape : identifier si le package expose des types. On consulte son package.json sur jsDelivr ou npm, et on cherche le champ types ou typings.
curl https://cdn.jsdelivr.net/npm/@hotwired/stimulus/package.json | jq '.types' # → "dist/types/index.d.ts"
Deuxième étape : récupérer le fichier .d.ts racine, puis chaque fichier importé récursivement. Un fichier de déclaration peut en importer d'autres via des chemins relatifs : il faut tous les télécharger pour que la résolution fonctionne.
# Récupérer le fichier racine curl -o assets/vendor/@hotwired/stimulus/dist/types/index.d.ts \ https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/types/index.d.ts # Récupérer les fichiers référencés curl -o assets/vendor/@hotwired/stimulus/dist/types/core.d.ts \ https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/types/core.d.ts
Troisième étape : mettre à jour le tsconfig.json avec l'entrée paths correspondante.
Cette procédure fonctionne. On l'a validée sur plusieurs packages. Mais elle ne passe pas à l'échelle : à chaque importmap:require, on recommence. Et si un package met à jour ses types, on ne le sait pas.
TypescriptTypesBundle : automatiser tout ça
Pour éviter de reproduire cette procédure à la main, on a développé IQ2i/typescript-types-bundle. L'idée est simple : lire importmap.php, détecter les packages qui exposent des types, télécharger les fichiers .d.ts, et mettre à jour tsconfig.json.
L'installation se fait via Composer, en dépendance de développement : le bundle ne sert qu'à l'outillage IDE, rien n'est nécessaire en production.
composer require --dev iq2i/typescript-types-bundle
Une fois installé, une seule commande suffit pour synchroniser les types de tous les packages déclarés dans l'importmap :
php bin/console typescript:types:download
Le bundle interroge jsDelivr pour récupérer le package.json de chaque package, vérifie la présence d'un champ types ou typings, télécharge tous les fichiers .d.ts du package dans assets/vendor/@types/, puis met à jour tsconfig.json. Les paths pointent vers le dossier de chaque package, pas vers un fichier précis : c'est le types du package.json téléchargé qui indique à TypeScript le point d'entrée. Voici ce que ça donne sur un projet avec Stimulus et Tom Select :
{ "compilerOptions": { "paths": { "@hotwired/stimulus": ["./assets/vendor/@types/@hotwired/stimulus"], "tom-select": ["./assets/vendor/@types/tom-select"] } } }
Et si un package ne déclare pas de champ types ? Le bundle ne s'arrête pas là : il cherche le package @types/* correspondant sur DefinitelyTyped, comme le ferait npm. Un package comme lodash, qui n'embarque pas ses propres déclarations, récupère quand même ses types via @types/lodash. Si rien n'existe non plus côté DefinitelyTyped, le package est ignoré sans erreur.
Le bundle ne touche que la section
paths. Si letsconfig.jsonexiste déjà, seules les entrées manquantes sont ajoutées, le reste est préservé. S'il n'existe pas, il est créé.
Le résultat se voit immédiatement dans l'éditeur : autocomplétion sur les imports, signatures de méthodes, erreurs de type signalées à la frappe.
Give it a try!
Le bundle est open source et disponible sur GitHub : IQ2i/typescript-types-bundle. Le README couvre l'installation et l'usage de la commande. Si vous tombez sur un package dont les types ne se résolvent pas correctement, les issues sont ouvertes : c'est exactement le genre de cas limite qui fait progresser l'outil.