Pourquoi mon projet TypeScript est lent ?

Blog article image cover
Votre projet TypeScript commence a grossir et devenir lent mais vous ne savez pas ce qui provoque ces ralentissements ? Nous allons voir avec un exemple comment dénicher les morceaux qui prennent le plus de temps lors de la compilation.

Quels outils utiliser ?

Nous allons prendre pour exemple ce repo GitHub. C'est un projet React avec un certain nombre de clés de traduction i18n (8 workspaces de 150 clés, donc 1200 clés).
TypeScript nous propose quelques outils pour investiguer les problèmes de performances liés à la compilation.
Pour commencer, lorsqu'on lance le compilateur TypeScript, on peut lui passer le paramètre extendedDiagnostics.
npx tsc --extendedDiagnostics
donne quelque chose comme :
Files: 90 Lines of TypeScript: 83 Lines of JavaScript: 0 Lines of JSON: 2448 Parse time: 0.42s ResolveModule time: 0.02s ResolveTypeReference time: 0.00s Program time: 0.48s Bind time: 0.20s Check time: 2.64s Emit time: 0.00s Total time: 3.32s Done in 3.53s.
On s'apperçoit que sur ce repo :
  • il y a très peu de TypeScript
  • il y a beaucoup de JSON (en fait ce sont les clés de traductions, par exemple menu.json)
  • ce qui prend le plus de temps c'est l'étape de type checking
Bon, c'est bien de savoir que le type checking prend du temps, mais ce serait mieux de savoir quelles lignes dans le code sont responsables !
Et bien ça tombe bien, on va pouvoir le découvrir en 2 étapes.
D'abord on va générer des fichiers de débug lors de la compilation :
npx tsc --generateTrace <path>
(il faut remplacer <path> par un chemin valide où tsc va créer un dossier, puis écrire les fichiers de débug de la compilation)
Pour lire ce fichier de débug on va utiliser l'onglet "Performance" des outils de développement (dans un navigateur basé sur Chromium).
On y trouvera un bouton "Load profile..." pour charger un fichier de débug (trace.json) :
Une fois le fichier chargé il faudra naviguer tant bien que mal dans l'interface et cliquer sur les morceaux du graphique qui prennent le plus de temps. Par exemple :
Sur l'image on voit qu'une opération de type "checkExpression" dans le fichier App.tsx prend 3.17 secondes
On peut savoir exactement ce à quoi cela correspond dans le code grâce à l'indication : pos 372 end 378
Il faudrait donc ouvrir App.tsx et regarder à partir du caractère 372 jusqu'au caractère 378.
Bon, c'est un peu pénible j'avoue, ce serait bien si un outil pouvait trouver les parties les plus longues de la compilation automatiquement et calculer la ligne / la colonne à notre place...
Bonne nouvelle cet outil existe !
Pour l'utiliser rien de plus simple :
npx @typescript/analyze-trace <path>
(remplacer <path> par le chemin vers le dossier dans lequel on a généré les fichiers de débug)
Sur notre exemple, cela donnera :
Hot Spots └─ Check file /home/mayeul/Projects/tests/test-tsc/src/App.tsx (3700ms) └─ Check deferred node from (line 17, char 27) to (line 18, char 34) (3186ms) └─ Check expression from (line 18, char 11) to (line 18, char 21) (3172ms) └─ Check expression from (line 18, char 22) to (line 18, char 30) (3170ms) └─ Check expression from (line 18, char 23) to (line 18, char 29) (3170ms)
Si on regarde dans App.tsx la ligne 18, colonne 23 (exactement notre charactère 372 de tout à l'heure), on s'aperçoit que le probleme est dans keys.map()
(le fichier App.tsx)
export const App = () => { const { t } = useTranslation(); const keys: AllI18nKeys[] = [ "anotherTest:anotherTest-100", "hehe:hehe-30", "login:login-103", "common:common-126" ]; return ( <div> <h1>{t("login:login-0")}</h1> {keys.map((key) => ( <p key={key}>{t(key)}</p> ))} </div> ); };
Maintenant on peut se demander pourquoi c'est lent.
Et bien pour avoir une petite idée, on va regarder le wiki de Microsoft sur les performances de TypeScript.
Dans le paragraphe lié aux unions, on s'aperçoit que Microsoft déconseille d'utiliser des unions avec de nombreux éléments, or le type AllI18nKeys est une union de 1200 clés de traduction...
Et voilà ! Maintenant vous êtes capable de trouver d'où viennent les lenteurs de compilation sur votre projet. Par contre pour résoudre ces problèmes, c'est une autre affaire 😉
En réalité, j'ai rencontré le problème décrit dans cet exemple. Mon article suivant explique comment résoudre le problème tout en gardant un typage fort sur les clés de traduction.
Vous souhaitez être accompagné pour lancer votre projet digital ?
Déposez votre projet dès maintenant
Article presentation image
Comment utiliser GitLab CI/CD pour améliorer votre flow de développement ?
Lors du développement d'une application, il y a toujours une petite appréhension lors la mise en production. Cette petite ...
Matthieu Locussol
Matthieu Locussol
Full-Stack Developer @ Galadrim
Article presentation image
Comment changer de version de Node.js avec NVM ?
Vous voulez changer rapidement de version de `node` ? nvm est l’outil qu’il vous faut. Pourquoi nvm ? `node` est un exécutable. ...
Florian Yusuf Ali
Florian Yusuf Ali
Full-Stack Developer @ Galadrim
Article presentation image
Next.js App Router : le cache et ses dangers
“Il y a seulement 2 problèmes compliqués en informatique : nommer les choses, et l’invalidation de cache”. Phil Karlton. Avec ...
Valentin Gerest
Valentin Gerest
Full-Stack Developer @ Galadrim