Microsoft annonce TypeScript 6.0. Voici quelques-uns des points forts de cette version : moins de sensibilité au contexte pour les fonctions sans this, importations de sous-chemins commençant par #/, combinaison de --moduleResolution bundler avec --module commonjs, le drapeau --stableTypeOrdering, l'option es2025 pour target et lib, entre autres. TypeScript 6.0 est une version unique en son genre, car son équipe a l'intention d'en faire la dernière version basée sur le code source JavaScript actuel. Comme annoncé en 2025, ils travaillent sur un nouveau code source pour le compilateur TypeScript et le service de langage écrit en Go qui tire parti de la vitesse du code natif et du multithreading à mémoire partagée.Si vous ne connaissez pas encore TypeScript, il s'agit d'un langage qui s'appuie sur JavaScript en y ajoutant une syntaxe de typage, ce qui permet de détecter les erreurs grâce à la vérification des types et offre des outils d'édition avancés. L'écriture de types dans le code permet d'expliquer l'intention et de faire vérifier le code par d'autres outils pour détecter les erreurs comme les fautes de frappe, les problèmes avec null et undefined, et plus encore. Les types alimentent également les outils d'édition de TypeScript, comme l'auto-complétion, la navigation dans le code et les refactorisations que vous pouvez voir dans des éditeurs tels que Visual Studio et VS Code. En fait, TypeScript et son écosystème alimentent l'expérience JavaScript dans ces deux éditeurs également.
TypeScript 6.0 est une version unique en ce sens car il s'agit la dernière version basée sur la base de code JavaScript actuelle. Comme annoncé en 2025, son équipe de développement travaille sur une nouvelle base de code pour le compilateur TypeScript et le service de langage, écrite en Go, qui tire parti de la vitesse du code natif et du multithreading à mémoire partagée. Cette nouvelle base de code servira de fondement à TypeScript 7.0 et aux versions suivantes.
TypeScript 6.0 fait office de passerelle entre TypeScript 5.9 et 7.0. À ce titre, la plupart des changements apportés à TypeScript 6.0 visent à faciliter l'alignement et à préparer l'adoption de TypeScript 7.0. Cela peut paraître surprenant, mais TypeScript 7.0 est en réalité sur le point d'être achevé. Vous pouvez l'essayer dans Visual Studio Code ou l'installer depuis npm. En fait, si vous êtes en mesure d'adopter TypeScript 6.0, il est encouragé à essayer les aperçus natifs de TypeScript 7.0. Cela dit, TypeScript 6.0 comporte certaines nouvelles fonctionnalités et améliorations qui ne concernent pas uniquement l'alignement.
Quelles sont les nouveautés depuis la version bêta et la version RC ?
Début mars, Microsoft annonce la sortie de la version Release Candidate (RC) de TypeScript 6.0. Depuis la version bêta de TypeScript 6.0, ils ont apporté quelques modifications notables, principalement pour aligner sur le comportement de TypeScript 7.0.
L'une de ces modifications concerne la vérification des types pour les expressions de fonction dans les appels génériques, en particulier celles apparaissant dans des expressions JSX génériques. Cela permettra généralement de détecter davantage de bogues dans le code existant, même si vous constaterez peut-être que certains appels génériques nécessitent un argument de type explicite.
Ils ont également étendu la dépréciation de la syntaxe d'assertion d'importation (c'est-à-dire import ... assert {...}) aux appels import() tels que import(..., { assert: {...}}). Enfin, ils ont mis à jour les types DOM afin de refléter les dernières normes web, y compris quelques ajustements apportés aux API temporelles.
Voici les principales améliorations de cette version :
Moins de sensibilité au contexte pour les fonctions sans this
Lorsque les paramètres n'ont pas de types explicites écrits, TypeScript peut généralement les déduire en fonction d'un type attendu, ou même à partir d'autres arguments dans le même appel de fonction.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | declare function callIt<T>(obj: { produce: (x: number) => T, consume: (y: T) => void, }): void; // Works, no issues. callIt({ produce: (x: number) => x * 2, consume: y => y.toFixed(), }); // Works, no issues even though the order of the properties is flipped. callIt({ consume: y => y.toFixed(), produce: (x: number) => x * 2, }); |
Ici, TypeScript peut déduire le type de y dans la fonction consume en se basant sur le T déduit de la fonction produce, quel que soit l'ordre des propriétés. Mais qu'en est-il si ces fonctions étaient écrites en utilisant la syntaxe de méthode au lieu de la syntaxe de fonction fléchée ?
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | declare function callIt<T>(obj: { produce: (x: number) => T, consume: (y: T) => void, }): void; // Works fine, `x` is inferred to be a number. callIt({ produce(x: number) { return x * 2; }, consume(y) { return y.toFixed(); }, }); callIt({ consume(y) { return y.toFixed(); }, // ~ // error: 'y' is of type 'unknown'. produce(x: number) { return x * 2; }, }); |
Curieusement, le deuxième appel à callIt entraîne une erreur, car TypeScript n'est pas en mesure de déduire le type de y dans la méthode consume. Ce qui se passe ici, c'est que lorsque TypeScript essaie de trouver des candidats pour T, il ignore d'abord les fonctions dont les paramètres n'ont pas de types explicites. Il procède ainsi parce que certaines fonctions peuvent avoir besoin que le type déduit de T soit correctement vérifié. Dans notre cas, nous avons besoin de connaître le type de T pour analyser notre fonction consume.
Ces fonctions sont appelées fonctions sensibles au contexte, c'est-à-dire des fonctions dont les paramètres n'ont pas de types explicites. Au final, le système de types devra déterminer les types de ces paramètres, mais cela va quelque peu à l'encontre du fonctionnement de la déduction dans les fonctions génériques, car les deux « tirent » les types dans des directions différentes.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | function callFunc<T>(callback: (x: T) => void, value: T) { return callback(value); } callFunc(x => x.toFixed(), 42); // ^ // We need to figure out the type of `x` here, // but we also need to figure out the type of `T` to check the callback. |
Pour résoudre ce problème, TypeScript ignore les fonctions sensibles au contexte lors de l'inférence des arguments de type et vérifie et infère d'abord à partir d'autres arguments. Si le fait d'ignorer les fonctions sensibles au contexte ne fonctionne pas, l'inférence se poursuit simplement sur tous les arguments non vérifiés, en allant de gauche à droite dans la liste des arguments. Dans l'exemple ci-dessus, TypeScript ignorera le rappel lors de l'inférence pour T, mais examinera ensuite le deuxième argument, 42, et en déduira que T est un number. Ensuite, lorsqu'il reviendra vérifier le rappel, il aura un type contextuel de (x: nombre) => void, ce qui lui permettra de déduire que [c]x[/B] est également un number.
Que se passe-t-il donc dans nos exemples précédents ?
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Arrow syntax - no errors. callIt({ consume: y => y.toFixed(), produce: (x: number) => x * 2, }); // Method syntax - errors! callIt({ consume(y) { return y.toFixed(); }, // ~ // error: 'y' is of type 'unknown'. produce(x: number) { return x * 2; }, }); |
Dans les deux exemples, produce est assignée à une fonction avec un paramètre x explicitement typé. Ne devraient-ils pas être vérifiés de manière identique ?
La question est subtile : la plupart des fonctions (comme celles qui utilisent la syntaxe de méthode) ont un paramètre this implicite, mais ce n'est pas le cas des fonctions fléchées. Toute utilisation de this pourrait nécessiter de « tirer » sur le type de T. Par exemple, connaître le type de l'objet littéral contenant pourrait à son tour nécessiter le type de consume, qui utilise T.
Mais nous n'utilisons pas this ! Bien sûr, la fonction peut avoir une valeur this lors de l'exécution, mais elle n'est jamais utilisée !
TypeScript 6.0 en tient compte lorsqu'il décide si une fonction est sensible au contexte ou non. Si this n'est jamais réellement utilisé dans une fonction, alors il n'est pas considéré comme sensible au contexte. Cela signifie que ces fonctions seront considérées comme ayant une priorité plus élevée en matière d'inférence de type, et tous nos exemples ci-dessus fonctionnent désormais !
Importations de sous-chemins commençant par #/
Lorsque Node.js a ajouté la prise en charge des modules, il a ajouté une fonctionnalité appelée « importations de sous-chemins ». Il s'agit essentiellement d'un champ appelé imports qui permet aux paquets de créer des alias internes pour les modules au sein de leur paquet.
| Code Typescript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | { "name": "my-package", "type": "module", "imports": { "#root": "./dist/index.js", "#root/*": "./dist/*" } } |
Cela permet aux modules de my-package d'importer à partir de #root au lieu d'avoir à utiliser un chemin relatif tel que ../../index.js, et permet essentiellement à tout autre module d'écrire quelque chose comme
| Code Typescript : | Sélectionner tout |
import * as utils from "#root/utils.js";
au lieu d'utiliser un chemin relatif tel que celui ci-dessous.
| Code Typescript : | Sélectionner tout |
import * as utils from "../../utils.js";
Un inconvénient mineur de cette fonctionnalité est que les développeurs devaient toujours écrire quelque chose après le # lorsqu'ils spécifiaient une importation de sous-chemin. Ici, nous avons utilisé root, mais cela est un peu inutile puisqu'il n'y a pas d'autre répertoire que ./dist/Les développeurs qui ont utilisé des bundlers sont également habitués à utiliser le mappage de chemins pour éviter les longs chemins relatifs. Une convention courante avec les bundlers consiste à utiliser un simple @/ comme préfixe. Malheureusement, les importations de sous-chemins ne pouvaient pas commencer par #/, ce qui causait beaucoup de confusion pour les développeurs qui essayaient de les adopter dans leurs projets.
Mais plus récemment, Node.js a ajouté la prise en charge des importations de sous-chemins commençant par #/. Cela permet aux paquets d'utiliser un simple préfixe #/ pour leurs importations de sous-chemins sans avoir à ajouter de segment supplémentaire....
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.