Guide

TypeScript 7, le compilateur natif et la migration

TypeScript 7 passe son compilateur à Go et accélère les builds de 8 à 12 fois. Ce qui change, comment migrer, et quels outils n'ont pas encore suivi.

L'équipe CodeWorlds15 min de lecture ·

Pratique JavaScript et TypeScript dans des leçons interactives avec un éditeur de code.

Crée un compte gratuit→
Dans cet article10

TypeScript ajoute à JavaScript des types statiques, vérifiés avant l'exécution du code. En juillet 2026, son compilateur a été réécrit de JavaScript vers Go, ce qui donne des builds de huit à douze fois plus rapides tout en gardant un comportement de vérification des types identique.

C'est le plus grand changement du langage depuis des années, et en même temps le plus invisible, car il n'ajoute pas la moindre nouvelle construction syntaxique. Tout ce qui change, c'est le temps que tu passes à attendre.

Ce qui a vraiment changé dans la version 7

Le compilateur était jusqu'ici écrit en TypeScript et s'exécutait sur un runtime JavaScript, donc il se compilait lui-même. La nouvelle version, c'est ce même code réécrit en Go, avec du code natif, de la mémoire partagée et du multithreading.

L'effet est mesurable et touche ce qui gêne au quotidien. Une vérification complète des types sur un gros projet passe de plusieurs minutes à quelques secondes. L'autocomplétion de l'éditeur arrête de se bloquer sur un gros fichier, car le service de langage tourne sur le même moteur.

Plus important que les chiffres, les règles de vérification des types ont été préservées : la sept vérifie exactement comme la six, donc ce n'est pas un nouveau langage. La configuration, elle, a changé. strict est maintenant activé par défaut, module prend par défaut la valeur esnext, types est par défaut vide plutôt que d'embarquer tous les paquets de déclarations trouvés, et rootDir pointe par défaut vers le dossier du projet. D'anciens avertissements sont devenus des erreurs bloquantes au passage : target: es5, baseUrl, moduleResolution: classic ainsi que node et node10, et module: amd, umd et systemjs ne sont plus pris en charge. Un projet venant de la version cinq a intérêt à passer d'abord par la six, car c'est elle qui a introduit ces abandons.

Ça ne rend pas la migration inconditionnelle pour autant. La version 7.0 ne fournit aucune interface de programmation, et une nouvelle n'est attendue qu'en 7.1. Les outils qui font appel au compilateur depuis du code plutôt que depuis la ligne de commande tournent en attendant sur le paquet de compatibilité @typescript/typescript6, qui fournit une commande tsc6 et expose l'interface de la 6.0 à côté du binaire de la sept. La conséquence pratique, c'est que les flux de travail avec Vue, MDX, Astro et Svelte, ainsi que la vérification de types dans les templates Angular, reposent encore sur la version 6.0. Vérifie les outils précis que tu utilises avant de migrer, car c'est le seul vrai point de blocage.

Quand TypeScript se justifie vraiment

Ça vaut le coup de trancher honnêtement, car la réponse n'est pas « toujours ».

Ça se justifie sur du code que quelqu'un lira dans six mois, et sur une équipe de plus d'une personne. Les types jouent alors le rôle d'une documentation qui ne peut pas devenir obsolète, puisque le compilateur la vérifie.

Ça se justifie pendant une refactorisation. Renommer un champ sur un objet utilisé à quarante endroits est une opération mécanique dans du code typé, et une chasse aux dégâts dans du code non typé.

Ça ne se justifie pas dans un script à usage unique ni dans un prototype destiné à tester une hypothèse puis à être jeté. La configuration, les déclarations de types des dépendances et les disputes avec le compilateur coûtent alors plus qu'elles ne rapportent.

Il existe un troisième cas, entre les deux : un petit projet susceptible de grandir. Une voie raisonnable consiste alors en du JavaScript ordinaire avec des types décrits dans des commentaires de documentation, vérifiés par le compilateur sans étape de build. Tu obtiens une bonne part du bénéfice sans changer le processus.

Les réglages à faire dès le départ

La configuration par défaut est permissive, et la permissivité, dans ce langage, veut dire moins d'erreurs détectées.

JSON
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "verbatimModuleSyntax": true
  }
}

La première option est évidente et c'est le défaut depuis la sept, donc l'entrée dans le fichier ne fait que fixer cet état. La deuxième est la plus utile de celles qu'on néglige : elle fait que la lecture d'un élément de tableau par index renvoie un type qui peut ne contenir aucune valeur, ce qui correspond à la réalité.

TypeScript
const days = ['lun', 'mar', 'mer']

const day = days[5]
console.log(day.toUpperCase())

Sans cette option, le compilateur accepte la troisième ligne et le programme casse seulement à l'exécution. Avec elle, l'erreur est signalée là où le problème se trouve réellement, ce qui t'oblige à préciser ce qu'il se passe quand l'élément est absent.

La troisième distingue un champ absent d'un champ réglé sur une valeur vide, ce qui compte pour des objets envoyés à des interfaces externes. La quatrième impose de marquer explicitement les imports de types.

TypeScript
import type { User } from './types'
import { saveUser } from './api'

Cette distinction n'est pas cosmétique. Un import de type disparaît au moment du build sans laisser de trace, alors qu'un import sans ce mot-clé reste et entraîne tout le module avec ses effets de bord, même si tu n'en utilises que la définition de type.

Un conseil séparé vaut pour un projet qui ne fait qu'adopter les types : active ces options une par une plutôt que toutes en même temps. Chacune produit sa propre classe d'erreurs, et les mélanger en une seule passe donne une liste de plusieurs centaines d'entrées que personne ne relira.

Les types qui font la différence

Au-delà des bases, trois mécanismes expliquent la majeure partie de la valeur réelle.

Les types affinés par une vérification permettent de décrire un état où certains champs n'existent que dans certaines situations.

TypeScript
type Response =
  | { status: 'success'; data: User }
  | { status: 'error'; message: string }

function handle(r: Response) {
  if (r.status === 'success') {
    console.log(r.data.name)
  } else {
    console.log(r.message)
  }
}

Ça supprime toute une classe de bugs où quelqu'un va chercher des données sur une réponse d'erreur. Le compilateur refuse, tout simplement.

Le second mécanisme, c'est l'inférence plutôt que la déclaration. Un type dérivé d'une valeur réelle ne peut pas s'en écarter, alors qu'un type écrit à la main le peut. Donc, plutôt que de décrire la forme d'une réponse serveur avec une interface séparée, dérive-la du schéma de validation, dans Zod par exemple, et garde une seule source de vérité.

Le troisième, ce sont les types utilitaires, qui transforment des types existants au lieu d'en créer de nouveaux.

TypeScript
type User = {
  id: string
  email: string
  passwordHash: string
  createdAt: Date
}

type PublicUser = Omit<User, 'passwordHash'>
type UserUpdate = Partial<Pick<User, 'email'>>
type FetchResult = Awaited<ReturnType<typeof fetchUser>>

Choisir quelques champs, rendre tout optionnel, ou extraire le type de retour d'une fonction sont des opérations qui économisent de la maintenance, car un changement à la source se propage automatiquement. Le premier de ces types porte ici une valeur supplémentaire : ajouter un nouveau champ sensible au modèle ne le fera pas fuiter à travers l'interface, pour autant que la forme publique soit construite par soustraction plutôt qu'en listant les champs depuis zéro.

Connais tout de même la limite. Les types avancés peuvent exprimer énormément de choses, et c'est tentant, mais un type dont le message d'erreur s'étale sur trente lignes coûte plus à une équipe qu'il ne lui apporte. Un type plus simple accompagné d'un commentaire est souvent une meilleure réponse qu'une construction de virtuose.

Migrer un projet existant

Migrer un projet écrit en JavaScript est la situation la plus courante, et en même temps la façon la plus facile de décourager une équipe.

Commence par activer la vérification sur le code que tu as déjà, sans renommer les fichiers. Le compilateur sait analyser des fichiers JavaScript et signaler des erreurs qu'on ne peut pas ignorer : fautes de frappe dans des noms de propriétés, appels avec le mauvais nombre d'arguments, références à des choses qui n'existent pas. C'est généralement la première poignée de défauts réels, et un bon argument dans la discussion sur l'intérêt de toute la démarche.

La deuxième étape, c'est l'ordre de conversion. Commence par les fichiers sans dépendances internes, les fonctions utilitaires et les constantes, puis remonte. L'ordre inverse veut dire que chaque fichier converti traîne derrière lui les types manquants de tout le reste du projet.

La troisième, c'est veiller à ce que le nouveau code arrive déjà typé. Sans cette règle, la migration ne se termine jamais, car le rythme d'ajout de fichiers non typés peut dépasser le rythme de conversion des anciens.

La quatrième concerne les fichiers qui ne peuvent pas être convertis tout de suite. Plutôt que de tout bloquer, désactive la vérification sur ce fichier précis avec un commentaire et ajoute-le à une liste. Une exemption ciblée accompagnée d'une note est plus honnête qu'un relâchement global de la configuration, car ça laisse voir ce qu'il reste à faire.

Enfin, mets-toi d'accord sur une mesure de progression. Le nombre de fichiers convertis sur le nombre total, publié une fois par semaine, fait plus pour terminer une migration que n'importe quelle déclaration d'intention, car ça montre si le travail avance réellement.

La performance de vérification sur un gros projet

Même après la réécriture du compilateur, un gros projet peut se vérifier plus lentement que sa taille ne le laisserait penser, et la cause se trouve généralement dans le code plutôt que dans l'outil.

Le premier suspect, ce sont les types conditionnels et récursifs de haute complexité. Le compilateur les développe à chaque utilisation, et des constructions imbriquées peuvent multiplier le travail de façon exponentielle. Un seul type utilitaire trop malin utilisé à cent endroits peut dominer le temps de vérification de tout un projet.

Le second, c'est l'absence de découpage en projets composites. Un gros dépôt vérifié comme une seule unité recalcule tout à chaque changement, alors qu'un découpage en unités séparées avec un état sauvegardé permet d'ignorer ce qui n'a pas changé.

Le troisième, ce sont des imports qui embarquent plus que nécessaire. Aller chercher un seul type dans un paquet qui en exporte un millier force le compilateur à lire tout le fichier de déclarations.

Le diagnostic est simple : des compteurs intégrés montrent combien de temps est parti dans l'analyse syntaxique et combien dans la vérification des types, et quel fichier a coûté le plus. Mesure avant de deviner, car l'intuition se trompe ici plus souvent qu'elle ne vise juste.

TypeScript face aux alternatives

SolutionVérificationÉtape de buildQuand le choisir
TypeScriptAvant l'exécution, complèteOuiProjet maintenu plus longtemps qu'un trimestre
JavaScript avec types en commentairesAvant l'exécution, plus restreinteNonPetit projet, pas d'envie de configuration
JavaScript purAucuneNonScript à usage unique, prototype
Validation à l'exécutionPendant l'exécutionSelon le casDonnées externes que les types ne couvrent pas

La dernière ligne n'est pas une alternative mais un complément, et confondre les deux est le malentendu le plus courant autour de ce langage. Les types disparaissent à la compilation, donc une réponse serveur décrite comme un objet d'une forme donnée peut en réalité être n'importe quoi. Le compilateur le croit, et le programme ne casse qu'au moment d'atteindre un champ manquant.

La bonne organisation est celle-ci : validation à la frontière du système, types à l'intérieur. Tout ce qui arrive du réseau, d'un formulaire, d'un fichier ou de variables d'environnement passe par une vérification à l'exécution, et c'est à partir de là que tu travailles avec des types dérivés de cette vérification.

Le travail quotidien et l'écosystème

Dans React, les types concernent surtout les props des composants et l'état, et c'est là que le retour sur investissement se voit le plus vite, car transmettre la mauvaise prop est l'erreur la plus courante lors des changements.

Côté serveur, la valeur grandit avec le code partagé. tRPC dérive les types du client directement du code serveur, donc changer la signature d'une procédure déclenche une erreur dans un composant plutôt que chez un utilisateur. L'inférence dans les bibliothèques de requêtes fonctionne de façon similaire, la forme des données venant de la fonction de récupération.

À côté de la vérification des types se trouve la couche de formatage et d'analyse statique, traditionnellement gérée par une paire d'outils séparés. Biome fusionne les deux tâches dans un seul binaire écrit en Rust, ce qui réduit un passage sur un gros dépôt de plusieurs minutes à quelques secondes. Vérifie tout de même la liste des règles avant de migrer, car certains plugins de l'écosystème ESLint n'ont pas d'équivalent ici.

Si le système de types ressemble encore à un ensemble de règles à mémoriser, travailler les leçons interactives de Visual Types vaut mieux que de lire une description de plus. Les génériques, les types conditionnels et infer se comprennent bien plus facilement en observant le résultat changer quand l'entrée change que par la seule syntaxe.

Les types des dépendances méritent aussi de l'attention. Les bibliothèques les publient aujourd'hui généralement avec le code, et celles qui ne le font pas ont le plus souvent des paquets de déclarations séparés. Une dépendance sans types introduit dans le projet une île où le compilateur ne vérifie rien, et cette île a tendance à grandir.

Le dernier point, c'est compiler contre vérifier. La plupart des outils de build retirent les types sans les vérifier, car c'est plus rapide. Ça veut dire qu'un projet peut se construire malgré des erreurs de types si personne ne lance la vérification séparément. Ajoute-la comme une étape distincte du pipeline, sinon tout ce travail n'est qu'une aide dans l'éditeur. Les runtimes qui exécutent directement des fichiers .ts se comportent pareil, Bun parmi eux : les types sont retirés sans étape de compilation, donc un code qui démarre vite ne dit rien sur sa correction.

Erreurs courantes

La première, c'est utiliser le type any comme solution à un problème. Il désactive la vérification là où il apparaît et se répand ensuite par les affectations. Quand tu ne connais vraiment pas le type, utilise le type unknown, qui impose une vérification avant usage.

La seconde, c'est le transtypage plutôt que la validation. Une assertion qui dit au compilateur qu'une réponse serveur a une forme donnée est une affirmation sans rien derrière, et déplace l'erreur de la compilation vers l'exécution.

La troisième, c'est l'absence de vérification des types dans le pipeline de build. Un outil qui retire les types sans les vérifier laisse passer des erreurs, et l'équipe l'apprend depuis la production.

La quatrième, c'est une configuration trop permissive. Sans le mode strict, une bonne part des erreurs pour lesquelles on choisit ce langage ne sont tout simplement pas détectées.

La cinquième, c'est annoter des types que le compilateur déduirait de lui-même. Une annotation manuelle sur une variable initialisée avec une valeur n'ajoute aucune sécurité et ajoute un endroit à mettre à jour à chaque changement.

La sixième, c'est construire des types compliqués là où un type simple suffirait. Le message d'erreur d'un tel type est souvent illisible, et la personne qui le rencontre un an plus tard y perd plus de temps que l'auteur n'en a économisé.

FAQ

En quoi TypeScript 7 diffère-t-il des versions précédentes ?

Par un compilateur réécrit de JavaScript vers Go, ce qui donne des builds de huit à douze fois plus rapides. La syntaxe et les règles de vérification des types restent compatibles avec la version 6.0, donc le code lui-même se compile à l'identique, tandis que les réglages par défaut de tsconfig.json ont changé.

Migrer vers la version 7 est-il risqué ?

La vérification des types reste compatible avec la version 6.0, tandis que la configuration par défaut a changé et que certaines anciennes options, target: es5 ou baseUrl par exemple, ne sont plus prises en charge. Le second risque concerne les outils qui font appel au compilateur depuis du code : la 7.0 n'a pas encore d'interface de programmation, donc Vue, Astro, Svelte, MDX et les templates Angular reposent sur le paquet de compatibilité construit à partir de la six.

Les types protègent-ils contre de mauvaises données venant d'un serveur ?

Non. Les types disparaissent à la compilation, donc une réponse décrite comme un objet d'une forme donnée peut être n'importe quoi. À la frontière du système, tu as besoin d'une validation à l'exécution, et les types doivent en être dérivés.

TypeScript vaut-il le coup sur un petit projet ?

Si le projet doit vivre plus de quelques semaines et que plus d'une personne va y toucher, oui. Sur un script à usage unique ou un prototype jetable, la configuration coûte plus qu'elle ne rapporte, et des types décrits dans des commentaires de documentation constituent la voie intermédiaire.

Par où commencer pour convertir un projet existant ?

Par l'activation de la vérification sur le JavaScript existant, sans renommer les fichiers. Convertis ensuite fichier par fichier, en commençant par ceux sans dépendances, et active les options strictes une par une pour que le nombre d'erreurs à chaque passage reste possible à relire.

La sortie de la septième version est décrite dans l'annonce de l'équipe, et la documentation complète se trouve sur le site du langage.

À lire aussi