Microsoft annonce la Release Candidate de TypeScript 5.7 et apporte plusieurs améliorations, dont la réécriture des chemins relatifs et des contrôles pour les variables non initialisées
Microsoft annonce la disponibilité de la release candidate (RC) de TypeScript 5.7. Cette version introduit des contrôles pour les variables non initialisées, la réécriture des chemins pour les chemins relatifs, le support pour target et lib es2024, et des contrôles améliorés de la propriété du projet dans les éditeurs.
Pour commencer à utiliser la RC, il est possible de l'obtenir via npm à l'aide de la commande suivante :
npm install -D typescript@rc
Voici un aperçu des nouveautés de TypeScript 5.7.
Vérification des variables non initialisées
Depuis longtemps, TypeScript est capable de détecter les problèmes lorsqu'une variable n'a pas encore été initialisée dans toutes les branches précédentes.
1 2 3 4 5 6 7 8 9 10 11
| let result: number
if (someCondition()) {
result = doSomeWork();
}
else {
let temporaryWork = doSomeWork();
temporaryWork *= 2;
// forgot to assign to 'result'
}
console.log(result); // error: Variable 'result' is used before being assigned. |
Malheureusement, il y a des endroits où cette analyse ne fonctionne pas. Par exemple, si la variable est accédée dans une fonction séparée, le système de type ne sait pas quand la fonction sera appelée, et prend une vue « optimiste » que la variable sera initialisée.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function foo() {
let result: number
if (someCondition()) {
result = doSomeWork();
}
else {
let temporaryWork = doSomeWork();
temporaryWork *= 2;
// forgot to assign to 'result'
}
printResult();
function printResult() {
console.log(result); // no error here.
}
} |
Bien que TypeScript 5.7 soit encore indulgent avec les variables qui ont été éventuellement initialisées, le système de types est capable de signaler des erreurs lorsque les variables n'ont jamais été initialisées du tout.
1 2 3 4 5 6 7 8 9
| function foo() {
let result: number
// do work, but forget to assign to 'result'
function printResult() {
console.log(result); // error: Variable 'result' is used before being assigned.
}
} |
Réécriture de chemin pour les chemins relatifs
Il existe plusieurs outils et moteurs d'exécution qui permettent d'exécuter du code TypeScript « in-place », ce qui signifie qu'ils ne nécessitent pas d'étape de construction qui génère des fichiers JavaScript de sortie. Par exemple, ts-node, tsx, Deno et Bun permettent tous d'exécuter des fichiers .ts directement. Plus récemment, Node.js a étudié ce support avec --experimental-transform-types et --experimental-strip-types. C'est extrêmement pratique car cela permet d'itérer plus rapidement sans avoir à se soucier de relancer une tâche de construction.
Il y a cependant une certaine complexité à prendre en compte lors de l'utilisation de ces modes. Pour être compatible avec tous ces outils, un fichier TypeScript importé « in-place » doit être importé avec l'extension TypeScript appropriée au moment de l'exécution. Par exemple, pour importer un fichier appelé foo.ts, il faut écrire ce qui suit dans le nouveau support expérimental de Node :
1 2 3
| // main.ts
import * as foo from "./foo.ts"; // <- we need foo.ts here, not foo.js |
Typiquement, TypeScript émettrait une erreur si on le faisait, parce qu'il s'attend à ce qu'on importe le fichier de sortie. Parce que certains outils autorisent les importations .ts, TypeScript a supporté ce style d'importation avec une option appelée --allowImportingTsExtensions depuis un certain temps. Cela fonctionne bien, mais que se passe-t-il si on a besoin de générer des fichiers .js à partir de ces fichiers .ts ? C'est une exigence pour les auteurs de bibliothèques qui devront être en mesure de distribuer uniquement des fichiers .js, mais jusqu'à présent TypeScript a évité de réécrire les chemins.
Pour prendre en charge ce scénario, une nouvelle option du compilateur a été ajoutée : --rewriteRelativeImportExtensions. Lorsqu'un chemin d'importation est relatif (commence par ./ ou ../), se termine par une extension TypeScript (.ts, .tsx, .mts, .cts), et qu'il s'agit d'un fichier sans déclaration, le compilateur réécrira le chemin vers l'extension JavaScript correspondante (.js, .jsx, .mjs, .cjs).
1 2 3 4 5 6 7 8 9 10 11 12
| // Under --rewriteRelativeImportExtensions...
// these will be rewritten.
import * as foo from "./foo.ts";
import * as bar from "../someFolder/bar.mts";
// these will NOT be rewritten in any way.
import * as a from "./foo";
import * as b from "some-package/file.ts";
import * as c from "@some-scope/some-package/file.ts";
import * as d from "#/file.ts";
import * as e from "./file.js"; |
Cela permet d'écrire du code TypeScript qui peut être exécuté sur place, puis compilé en JavaScript lorsque tout est prêt.
Maintenant, il a été noté que TypeScript évitait en général de réécrire les chemins d'accès. Il y a plusieurs raisons à cela, mais la plus évidente est celle des importations dynamiques. Si un développeur écrit ce qui suit, il n'est pas trivial de gérer le chemin que l'import reçoit. En fait, il est impossible de remplacer le comportement de l'import dans les dépendances.
1 2 3 4 5 6 7 8 9 10
| function getPath() {
if (Math.random() < 0.5) {
return "./foo.ts";
}
else {
return "./foo.js";
}
}
let myImport = await import(getPath()); |
Un autre problème est que (comme cela a été vu plus haut) seuls les chemins relatifs sont réécrits, et ils sont écrits de manière "naïve". Cela signifie que tout chemin qui repose sur la baseUrl et les paths de TypeScript ne sera pas réécrit :
1 2 3 4 5 6 7 8 9 10 11
| // tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
// ...
"paths": {
"@/*": ["./src/*"]
}
}
} |
1 2
| // Won't be transformed, won't work.
import * as utilities from "@/utilities.ts"; |
Il en va de même pour tout chemin qui pourrait être résolu par les champs exports et imports d'un package.json.
1 2 3 4 5 6 7
| // package.json
{
"name": "my-package",
"imports": {
"#root/*": "./dist/*"
}
} |
1 2
| // Won't be transformed, won't work.
import * as utilities from "#root/utilities.ts"; |
Par conséquent, si une présentation de type espace de travail est utilisée avec plusieurs paquets se référençant les uns les autres, il se peut que l'utilisation des exportations conditionnelles avec des conditions personnalisées étendues soit nécessaire pour que cela fonctionne :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // my-package/package.json
{
"name": "my-package",
"exports": {
".": {
"@my-package/development": "./src/index.ts",
"import": "./lib/index.js"
},
"./*": {
"@my-package/development": "./src/*.ts",
"import": "./lib/*.js"
}
}
} |
Chaque fois qu'on veut importer les fichiers .ts, on peut l'exécuter avec node --conditions=@my-package/development.
Notez le « namespace » ou « scope » utilisé pour la condition @my-package/development. Il s'agit d'une solution de fortune pour éviter les conflits avec les dépendances qui pourraient également utiliser la condition development. Si tout le monde livre un développement dans son paquet, alors la résolution peut essayer de se résoudre à un fichier .ts, ce qui ne fonctionnera pas nécessairement.
Prise en charge de --target es2024 et --lib es2024
TypeScript 5.7 supporte désormais --target es2024, qui permet aux utilisateurs de cibler les runtimes ECMAScript 2024. Cette cible permet principalement de spécifier la nouvelle --lib es2024 qui contient de nombreuses fonctionnalités pour SharedArrayBuffer et ArrayBuffer, Object.groupBy, Map.groupBy, Promise.withResolvers, et plus encore. Elle déplace également Atomics.waitAsync de --lib es2022 vers --lib es2024.
Notez que les modifications apportées à SharedArrayBuffer et ArrayBuffer ont entraîné une légère divergence entre ces deux types de tampons. Pour combler le fossé et préserver le type de tampon sous-jacent, tous les TypedArrays (comme Uint8Array et autres) sont désormais également génériques.
1 2 3
| interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike> {
// ...
} |
Chaque TypedArray contient désormais un paramètre de type nommé TArrayBuffer, bien que ce paramètre de type ait un argument de type par défaut afin que l'on puisse continuer à faire référence à Int32Array sans écrire explicitement Int32Array<ArrayBufferLike>.
Si des problèmes surviennent dans le cadre de cette mise à jour, il est possible de mettre à jour @types/node.
Recherche de fichiers de configuration ancestraux pour la propriété du projet
Lorsqu'un fichier TypeScript est chargé dans un éditeur utilisant TSServer (comme Visual Studio ou VS Code), l'éditeur essaiera de trouver le fichier tsconfig.json pertinent qui « détient » le fichier. Pour ce faire, il remonte l'arbre des répertoires à partir du fichier en cours d'édition, à la recherche de tout fichier nommé tsconfig.json.
Auparavant, cette recherche s'arrêtait au premier fichier tsconfig.json trouvé ; cependant, on peut imaginer une structure de projet comme la suivante :
1 2 3 4 5 6 7
| project/
├── src/
│ ├── foo.ts
│ ├── foo-test.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
└── tsconfig.json |
Ici, l'idée est que src/tsconfig.json est le fichier de configuration « principal » du projet, et src/tsconfig.test.json est un fichier de configuration pour l'exécution des tests.
1 2 3 4 5 6 7
| // src/tsconfig.json
{
"compilerOptions": {
"outDir": "../dist"
},
"exclude": ["**/*.test.ts"]
} |
1 2 3 4 5 6 7 8 9 10
| // src/tsconfig.test.json
{
"compilerOptions": {
"outDir": "../dist/test"
},
"include": ["**/*.test.ts"],
"references": [
{ "path": "./tsconfig.json" }
]
} |
1 2 3 4 5 6 7 8 9 10
| // tsconfig.json
{
// This is a "workspace-style" or "solution-style" tsconfig.
// Instead of specifying any files, it just references all the actual projects.
"files": [],
"references": [
{ "path": "./src/tsconfig.json" },
{ "path": "./src/tsconfig.test.json" },
]
} |
Le problème est qu'en éditant foo-test.ts, l'éditeur trouverait project/src/tsconfig.json comme fichier de configuration « propriétaire » - mais ce n'est pas celui que l'on veut ! Si la marche s'arrête à ce point, ce n'est peut-être pas souhaitable. La seule façon d'éviter cela était de renommer src/tsconfig.json en quelque chose comme src/tsconfig.src.json, et alors tous les fichiers seraient dirigés vers le fichier de premier niveau tsconfig.json qui fait référence à tous les projets possibles.
1 2 3 4 5 6 7
| project/
├── src/
│ ├── foo.ts
│ ├── foo-test.ts
│ ├── tsconfig.src.json
│ └── tsconfig.test.json
└── tsconfig.json |
Au lieu de forcer les développeurs à le faire, TypeScript 5.7 continue maintenant à remonter l'arbre des répertoires pour trouver d'autres fichiers tsconfig.json appropriés pour les scénarios de l'éditeur. Cela permet une plus grande flexibilité dans l'organisation des projets et la structuration des fichiers de configuration.
Vérifications plus rapides de la propriété du projet dans les éditeurs pour les projets composites
Imaginez une grande base de code avec la structure suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| packages
├── graphics/
│ ├── tsconfig.json
│ └── src/
│ └── ...
├── sound/
│ ├── tsconfig.json
│ └── src/
│ └── ...
├── networking/
│ ├── tsconfig.json
│ └── src/
│ └── ...
├── input/
│ ├── tsconfig.json
│ └── src/
│ └── ...
└── app/
├── tsconfig.json
├── some-script.js
└── src/
└── ... |
Chaque répertoire de packages est un projet TypeScript distinct, et le répertoire app est le projet principal qui dépend de tous les autres projets.
1 2 3 4 5 6 7 8 9 10 11 12 13
| // app/tsconfig.json
{
"compilerOptions": {
// ...
},
"include": ["src"],
"references": [
{ "path": "../graphics/tsconfig.json" },
{ "path": "../sound/tsconfig.json" },
{ "path": "../networking/tsconfig.json" },
{ "path": "../input/tsconfig.json" }
]
} |
Remarquez maintenant que le fichier some-script.js se trouve dans le répertoire app. Lorsque l'on ouvre le fichier some-script.js dans l'éditeur, le service de langage TypeScript (qui gère également l'expérience de l'éditeur pour les fichiers JavaScript !) doit déterminer à quel projet appartient le fichier afin d'appliquer les bons paramètres.
Dans ce cas, le fichier tsconfig.json le plus proche n'inclut pas some-script.js, mais TypeScript demandera « si l'un des projets référencés par app/tsconfig.json inclut some-script.js ». Pour ce faire, TypeScript aurait préalablement chargé chaque projet, un par un, et se serait arrêté dès qu'il aurait trouvé un projet contenant some-script.js. Même si some-script.js n'est pas inclus dans l'ensemble des fichiers de la racine, TypeScript analyserait quand même tous les fichiers d'un projet, car certains des fichiers de la racine peuvent toujours faire référence à some-script.js de manière transitoire.
Au fil du temps, il a été constaté que ce comportement entraînait des réactions extrêmes et imprévisibles dans les bases de code les plus importantes. Les développeurs ouvraient des fichiers de script égarés et se retrouvaient à attendre que l'ensemble de leur base de code soit ouvert.
Heureusement, chaque projet qui peut être référencé par un autre projet (hors espace de travail) doit activer un indicateur appelé composite, qui applique une règle selon laquelle tous les fichiers sources d'entrée doivent être connus à l'avance. Ainsi, lors de l'analyse d'un projet composite, TypeScript 5.7 ne vérifiera que si un fichier appartient à l'ensemble de fichiers racine de ce projet. Cela devrait permettre d'éviter ce comportement courant dans le pire des cas.
Importations JSON validées dans --module nodenext
Lors de l'importation d'un fichier .json sous --module nodenext, TypeScript appliquera désormais certaines règles pour éviter les erreurs d'exécution.
Par exemple, un attribut import contenant type : "json" doit être présent pour toute importation de fichier JSON.
1 2 3 4 5 6 7
| import myConfig from "./myConfig.json";
// ~~~~~~~~~~~~~~~~~
// error: Importing a JSON file into an ECMAScript module requires a 'type: "json"' import attribute when 'module' is set to 'NodeNext'.
import myConfig from "./myConfig.json" with { type: "json" };
// ^^^^^^^^^^^^^^^^
// This is fine because we provided `type: "json"` |
En plus de cette validation, TypeScript ne génère pas d'exportations « nommées », et le contenu d'une importation JSON ne sera accessible que par défaut.
1 2 3 4 5 6 7 8 9 10 11 12 13
| // This is okay:
import myConfigA from "./myConfig.json" with { type: "json" };
let version = myConfigA.version;
///////////
import * as myConfigB from "./myConfig.json" with { type: "json" };
// This is not:
let version = myConfig.version;
// This is okay:
let version = myConfig.default.version; |
Prise en charge de la mise en cache de la compilation V8 dans Node.js
Node.js 22 prend en charge une nouvelle API appelée module.enableCompileCache(). Cette API permet au moteur d'exécution de réutiliser une partie du travail d'analyse et de compilation effectué après la première exécution d'un outil.
TypeScript 5.7 exploite désormais l'API afin de pouvoir commencer à effectuer un travail utile plus tôt. Lors de certains tests, il a été constaté une accélération de 2,5 fois lors de l'exécution de tsc --version.
1 2 3 4 5 6 7 8 9 10 11
| Benchmark 1: node ./built/local/_tsc.js --version (*without* caching)
Time (mean ± σ): 122.2 ms ± 1.5 ms [User: 101.7 ms, System: 13.0 ms]
Range (min
max): 119.3 ms
132.3 ms 200 runs
Benchmark 2: node ./built/local/tsc.js --version (*with* caching)
Time (mean ± σ): 48.4 ms ± 1.0 ms [User: 34.0 ms, System: 11.1 ms]
Range (min
max): 45.7 ms
52.8 ms 200 runs
Summary
node ./built/local/tsc.js --version ran
2.52 ± 0.06 times faster than node ./built/local/_tsc.js --version |
Modifications notables du comportement
Vous trouverez dans cette section un ensemble de changements notables qu'il convient de reconnaître et de comprendre dans le cadre d'une mise à jour. Elle met parfois en évidence des dépréciations, des suppressions et de nouvelles restrictions. Elle peut également contenir des corrections de bogues qui sont des améliorations fonctionnelles, mais qui peuvent également affecter une version existante en introduisant de nouvelles erreurs.
Quelles sont les prochaines étapes ?
À ce stade, l'équipe TypeScript prévoit très peu de changements pour TypeScript 5.7 en dehors des corrections de bogues critiques pour le compilateur et des corrections de bogues mineurs pour le service de langage. Dans les prochaines semaines, la première version stable de TypeScript 5.7 sera publiée. Gardez un œil sur le plan d'itération pour connaître les dates de publication.
Envoyé par
Microsoft
Source : Microsoft
Et vous ?
Quel est votre avis sur le sujet ?
Que pensez-vous des fonctionnalités proposées par cette version de TypeScript ?
Voir aussi :
Microsoft annonce la disponibilité de TypeScript 5.7 Beta, apportant des améliorations à la vérification des variables jamais initialisées et une vérification plus rapide de la propriété d'un projet
Microsoft annonce la disponibilité de TypeScript 5.6, apportant des améliorations à la vérification vraie et nulle non autorisée ainsi que des méthodes d'aide pour les itérateurs
Partager