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.7 Beta. Voici quelques mises à jour que TypeScript 5.7 apporte : vérification des variables jamais initialisées, réécriture de chemin pour les chemins relatifs, vérifications plus rapides de la propriété du projet dans les éditeurs pour les projets composites et d'autres changements notables.

TypeScript est un langage de programmation de haut niveau, libre et gratuit, développé par Microsoft, qui ajoute à JavaScript un typage statique avec des annotations de type optionnelles. Il est conçu pour le développement de grandes applications et se transpose à JavaScript. Comme TypeScript est un surensemble de JavaScript, tous les programmes JavaScript sont syntaxiquement valides en TypeScript, mais ils peuvent ne pas vérifier le type pour des raisons de sécurité.

TypeScript 5.7 Beta est désormais disponible. À ce stade, TypeScript 5.7 serait "stable en termes de fonctionnalités". TypeScript 5.7 se concentrera sur les corrections de bogues, le polissage et certaines fonctionnalités d'édition à faible risque. Une version candidate sera disponible dans un peu plus d'un mois, suivie d'une version stable peu après.

Nom : 1.jpg
Affichages : 115
Taille : 10,6 Ko

Voici quelques mises à jour de cette version :

Vérification des variables jamais 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.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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, dans certains cas, cette analyse ne fonctionne pas. Par exemple, si la variable est accédée dans une fonction séparée, le système de types ne sait pas quand la fonction sera appelée, et adopte une vue "optimiste" selon laquelle la variable sera initialisée.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 envers les variables qui ont pu être initialisées, le système de types est capable de signaler des erreurs lorsque les variables n'ont jamais été initialisées du tout.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 vous permettent d'exécuter du code TypeScript "sur place", ce qui signifie qu'ils ne nécessitent pas d'étape de compilation 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é "sur-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, nous devons écrire ce qui suit dans le nouveau support expérimental de Node :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 nous faisions cela, parce qu'il s'attend à ce que nous importions 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 nous avons 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 supporter ce scénario, cette version apporte une nouvelle option de compilation appelée --rewriteRelativeImportExtensions. Lorsqu'un chemin d'importation est relatif (commence par ./ ou ../), se termine par une extension TypeScript (.ts, .tsx, .mts, .cts), et est un fichier sans déclaration, le compilateur réécrira le chemin vers l'extension JavaScript correspondante (.js, .jsx, .mjs, .cjs).

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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.

TypeScript évitait généralement de réécrire les chemins. Il y a plusieurs raisons à cela, mais la plus évidente est l'importation dynamique. Si un développeur écrit ce qui suit, il n'est pas négligeable de gérer le chemin que import reçoit. En fait, il est impossible de remplacer le comportement de import dans les dépendances.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 seuls les chemins "relatifs" sont réécrits, et ils sont écrits "naïvement". Cela signifie que tout chemin qui s'appuie sur baseUrl et path de TypeScript ne sera pas réécrit :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
// tsconfig.json
 
{
    "compilerOptions": {
        "module": "nodenext",
        // ...
        "paths": {
            "@/*": ["./src/*"]
        }
    }
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
// package.json
{
    "name": "my-package",
    "imports": {
        "#root/*": "./dist/*"
    }
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
// Won't be transformed, won't work.
import * as utilities from "#root/utilities.ts";


Par conséquent, si vous avez utilisé une disposition de type espace de travail avec plusieurs paquets se référençant les uns les autres, vous devrez peut-être utiliser des exportations conditionnelles avec des conditions personnalisées à portée pour que cela fonctionne :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 que vous voulez importer les fichiers .ts, vous pouvez 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 development dans son paquet, alors la résolution peut essayer de se résoudre à un fichier .ts, ce qui ne fonctionnera pas nécessairement.


Support 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. Il déplace également Atomics.waitAsync de --lib es2022 à --lib es2024.

Notez que dans le cadre des changements apportés à SharedArrayBuffer et ArrayBuffer, les deux divergent un peu. Pour combler le fossé et préserver le type de tampon sous-jacent, tous les TypedArrays (comme Uint8Array et d'autres) sont maintenant aussi génériques.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike> {
    // ...
}


Chaque TypedArray contient maintenant un paramètre de type nommé TArrayBuffer, bien que ce paramètre de type ait un argument de type par défaut afin de se référer à Int32Array sans écrire explicitement Int32Array<ArrayBufferLike>.

Si vous rencontrez des problèmes dans le cadre de cette mise à jour, vous devrez peut-être 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 "possède" le fichier. Pour ce faire, il remonte l'arborescence 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, imaginez une structure de projet comme la suivante :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
// src/tsconfig.json
{
    "compilerOptions": {
        "outDir": "../dist"
    },
    "exclude": ["**/*.test.ts"]
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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" }
    ]
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 ici est que lors de l'édition de foo-test.ts, l'éditeur trouverait project/src/tsconfig.json comme fichier de configuration "propriétaire" - mais ce n'est pas celui voulu ! Si la marche s'arrête à ce point, ce n'est pas forcément 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.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 à procéder ainsi, TypeScript 5.7 continue à remonter l'arborescence des répertoires pour trouver d'autres fichiers tsconfig.json appropriés aux scénarios de l'éditeur. Cela peut apporter plus de flexibilité dans la façon dont les projets sont organisés et dont les fichiers de configuration sont structurés.


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 :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 dans packages est un projet TypeScript séparé, et le répertoire app est le projet principal qui dépend de tous les autres projets.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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" }
    ]
}


Remarquons maintenant que nous avons le fichier some-script.js dans le répertoire app. Lorsque nous ouvrons 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 va demander "l'un des projets référencés par app/tsconfig.json pourrait-il inclure 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 analysait quand même tous les fichiers d'un projet parce que certains des fichiers de la racine peuvent toujours faire référence à some-script.js ? de manière transitoire.

Au fil du temps, ce comportement entraînait des réactions extrêmes et imprévisibles dans les bases de code 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 pouvant ê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 source 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 à partir 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 d'importation contenant le type : "json" doit être présent pour toute importation de fichier JSON.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 n'est accessible que par défaut.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 des tests, il a été constaté une accélération de 2,5 fois lors de l'exécution de tsc --version.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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


Source : "Announcing TypeScript 5.7 Beta"

Et vous ?

Que pensez-vous de cette mise à jour ?

Voir aussi :

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

Cinq vérités inconfortables à propos de TypeScript selon Stefan Baumgartner, auteur de livres sur le langage de programmation

TypeScript, les types marqués : Produire un moyen de marquer un type, en fournissant un moyen automatisé et facile à utiliser pour rendre un type nominal, par Prosopo