IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Microsoft annonce la disponibilité de TypeScript 5.8, apportant des vérifications plus granulaires pour les branches dans les expressions de retour
Ainsi que plusieurs autres améliorations

Le , par Anthony

7PARTAGES

6  0 
Microsoft annonce la disponibilité générale (GA) de TypeScript 5.8. Cette version apporte plusieurs nouveautés et améliorations, cependant, une nouvelle fonctionnalité introduite lors de la phase bêta, une forme limitée de vérification des types conditionnels dans les instructions de retour, a été supprimée de la version GA.

Cette annonce intervient après la publication de la release candidate de TypeScript 5.8, qui apporte des contrôles plus granulaires pour les branches à l'intérieur des expressions de retour. Avec ces contrôles granulaires, le système de type traite les expressions conditionnelles directement à l'intérieur des instructions de retour. Chaque branche de l'expression conditionnelle est ainsi vérifiée par rapport au type de retour déclaré de la fonction qui la contient, s'il en existe un.

TypeScript est un langage qui s'appuie sur JavaScript en ajoutant une syntaxe pour les types. 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.


Pour commencer à utiliser TypeScript 5.8, vous pouvez l'installer via npm avec la commande suivante :

Code : Sélectionner tout
npm install -D typescript

Jetons un coup d'œil aux nouveautés de TypeScript 5.8.

Quelles sont les nouveautés depuis la bêta et la RC ?

Depuis la sortie de la version bêta, Microsoft a dû faire marche arrière sur la façon dont les fonctions avec des types de retour conditionnels sont vérifiées. Sur la base de certaines limitations et modifications que Microsoft souhaitait apporter, l'équipe a décidé d'itérer sur la fonctionnalité dans le but de la livrer dans TypeScript 5.9. Cependant, dans le cadre de ce travail, Microsoft a ajouté des vérifications plus granulaires pour les branches dans les expressions de retour.

Aucun nouveau changement majeur n'a été ajouté depuis la version candidate.

Vérifications granulaires des branches dans les expressions de retour

Considérons un code comme le suivant :

Code : Sélectionner tout
1
2
3
4
5
6
7
declare const untypedCache: Map<any, any>;

function getUrlObject(urlString: string): URL {
    return untypedCache.has(urlString) ?
        untypedCache.get(urlString) :
        urlString;
}

L'intention de ce code est de récupérer un objet URL à partir d'un cache s'il existe, ou de créer un nouvel objet URL s'il n'existe pas. Cependant, il y a un bug : on a oublié de construire un nouvel objet URL avec l'entrée. Malheureusement, TypeScript ne détecte généralement pas ce genre de bogue.

Lorsque TypeScript vérifie des expressions conditionnelles comme cond ? trueBranch : falseBranch, son type est traité comme une union des types des deux branches. En d'autres termes, il obtient le type de trueBranch et de falseBranch, et les combine en un type d'union. Dans ce cas, le type de untypedCache.get(urlString) est any, et le type de urlString est string. C'est là que les choses se gâtent, car le type any est tellement infectieux dans la manière dont il interagit avec les autres types. L'union any | string est simplifiée en any, de sorte qu'au moment où TypeScript commence à vérifier si l'expression de l'instruction return est compatible avec le type de retour attendu d'URL, le système de types a perdu toute information qui aurait permis de détecter le bogue dans ce code.

Dans TypeScript 5.8, le système de type traite les expressions conditionnelles directement à l'intérieur des instructions return. Chaque branche de l'expression conditionnelle est vérifiée par rapport au type de retour déclaré de la fonction qui la contient (s'il y en a un), de sorte que le système de type peut détecter le bogue dans l'exemple ci-dessus.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
declare const untypedCache: Map<any, any>;

function getUrlObject(urlString: string): URL {
    return untypedCache.has(urlString) ?
        untypedCache.get(urlString) :
        urlString;
    //  ~~~~~~~~~
    // error! Type 'string' is not assignable to type 'URL'.
}

Prise en charge de require() pour les modules ECMAScript dans --module nodenext

Pendant des années, Node.js a supporté les modules ECMAScript (ESM) en même temps que les modules CommonJS. Malheureusement, l'interopérabilité entre les deux a posé quelques problèmes.

  • Les fichiers ESM pouvaient import des fichiers CommonJS
  • Les fichiers CommonJS ne pouvaient pas require() les fichiers ESM

En d'autres termes, il était possible de consommer des fichiers CommonJS à partir de fichiers ESM, mais pas l'inverse. Cela a posé de nombreux problèmes aux auteurs de bibliothèques qui souhaitaient fournir un support ESM. Ces auteurs devaient soit rompre la compatibilité avec les utilisateurs de CommonJS, soit « publier deux fois » leurs bibliothèques (en fournissant des points d'entrée distincts pour ESM et CommonJS), soit rester indéfiniment sur CommonJS. Bien que la double publication puisse sembler être un bon compromis, il s'agit d'un processus complexe et sujet aux erreurs, qui double également la quantité de code dans un paquetage.

Node.js 22 assouplit certaines de ces restrictions et autorise les appels require(« esm ») des modules CommonJS vers les modules ECMAScript. Node.js n'autorise toujours pas l'utilisation de require() pour les fichiers ESM qui contiennent une await de premier niveau, mais la plupart des autres fichiers ESM sont désormais consommables à partir des fichiers CommonJS. Cela représente une opportunité majeure pour les auteurs de bibliothèques de fournir un support ESM sans avoir à publier leurs bibliothèques en double.

TypeScript 5.8 supporte ce comportement avec l'option --module nodenext. Lorsque --module nodenext est activé, TypeScript évitera d'émettre des erreurs sur ces appels require() aux fichiers ESM.

Parce que cette fonctionnalité peut être rétroportée vers des versions plus anciennes de Node.js, il n'y a actuellement pas d'option stable --module nodeXXXX qui active ce comportement ; cependant, Microsoft prédit que les futures versions de TypeScript pourront stabiliser la fonctionnalité sous node20. En attendant, les utilisateurs de Node.js 22 et plus récents sont encouragés à utiliser --module nodenext, tandis que les auteurs de bibliothèques et les utilisateurs de versions plus anciennes de Node.js devraient continuer à utiliser --module node16 (ou faire la mise à jour mineure vers --module node18).

--module node18

TypeScript 5.8 introduit une option stable --module node18. Pour les utilisateurs qui sont fixés sur l'utilisation de Node.js 18, ce drapeau fournit un point de référence stable qui n'incorpore pas certains comportements qui sont dans --module nodenext. En particulier :

  • require() des modules ECMAScript est interdit sous node18, mais autorisé sous nodenext
  • les assertions import (dépréciées en faveur des attributs d'importation) sont autorisées sous node18, mais ne sont pas autorisées sous nodenext

L'option --erasableSyntaxOnly

Récemment, Node.js 23.6 a supprimé le support expérimental pour l'exécution directe de fichiers TypeScript ; cependant, seules certaines constructions sont supportées dans ce mode. Node.js a supprimé un mode appelé --experimental-strip-types qui exige que toute syntaxe spécifique à TypeScript ne puisse pas avoir de sémantique d'exécution. En d'autres termes, il doit être possible d'effacer facilement toute syntaxe spécifique à TypeScript d'un fichier, en laissant un fichier JavaScript valide.

Cela signifie que des constructions comme celles qui suivent ne sont pas prises en charge :

  • déclarations enum
  • namespaces et modules avec code d'exécution
  • propriétés des paramètres dans les classes
  • alias import =

Voici quelques exemples de ce qui ne fonctionne pas :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//  error: A namespace with runtime code.
namespace container {
    foo.method();

    export type Bar = string;
}

//  error: An `import =` alias
import Bar = container.Bar;

class Point {
    //  error: Parameter properties
    constructor(public x: number, public y: number) { }
}

//  error: An enum declaration.
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

Des outils similaires comme ts-blank-space ou Amaro (la bibliothèque sous-jacente pour la séparation des types dans Node.js) ont les mêmes limitations. Ces outils fourniront des messages d'erreur utiles s'ils rencontrent du code qui ne répond pas à ces exigences, mais l'utilisateur ne découvrira toujours pas que son code ne fonctionne pas tant qu'il n'aura pas essayé de l'exécuter.

C'est pourquoi TypeScript 5.8 introduit l'option --erasableSyntaxOnly. Lorsque ce drapeau est activé, TypeScript génère des erreurs sur la plupart des constructions spécifiques à TypeScript qui ont un comportement à l'exécution.

Code : Sélectionner tout
1
2
3
4
5
6
class C {
    constructor(public x: number) { }
    //          ~~~~~~~~~~~~~~~~
    // error! This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
    }
}

Typiquement, vous voudrez combiner cette option avec --verbatimModuleSyntax, qui garantit qu'un module contient la syntaxe d'importation appropriée, et que l'élision d'importation n'a pas lieu.

L'option --libReplacement

Dans TypeScript 4.5, Microsoft a introduit la possibilité de remplacer les fichiers lib par défaut par des fichiers personnalisés. Ceci était basé sur la possibilité de résoudre un fichier de bibliothèque à partir de paquets nommés @typescript/lib-*. Par exemple, vous pouvez verrouiller vos bibliothèques dom sur une version spécifique du package @types/web avec le package.json suivant :

Code : Sélectionner tout
1
2
3
4
5
{
    "devDependencies": {
       "@typescript/lib-dom": "npm:@types/web@0.0.199"
     }
}

Une fois installé, un paquetage appelé @typescript/lib-dom devrait exister, et TypeScript le recherchera toujours lorsque dom est impliqué par vos paramètres.

Il s'agit d'une fonctionnalité puissante, mais qui implique également un peu de travail supplémentaire. Même si vous n'utilisez pas cette fonctionnalité, TypeScript effectue toujours cette recherche et doit surveiller les changements dans node_modules au cas où un paquetage de remplacement de lib commencerait à exister.

TypeScript 5.8 introduit l'option --libReplacement, qui permet de désactiver ce comportement. Si vous n'utilisez pas --libReplacement, vous pouvez maintenant le désactiver avec --libReplacement false. Dans le futur, --libReplacement false pourrait devenir la valeur par défaut, donc si vous utilisez actuellement ce comportement, vous devriez envisager de l'activer explicitement avec --libReplacement true.


Conservation des noms de propriété calculés dans les fichiers de déclaration

Afin de rendre l'émission des propriétés calculées plus prévisible dans les fichiers de déclaration, TypeScript 5.8 préservera systématiquement les noms d'entités (bareVariables et dotted.names.that.look.like.this) dans les noms de propriétés calculées dans les classes.

Par exemple, considérons le code suivant :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
export let propName = "theAnswer";

export class MyClass {
    [propName] = 42;
//  ~~~~~~~~~~
// error!
// A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type.
}

Les versions précédentes de TypeScript émettaient une erreur lors de la génération d'un fichier de déclaration pour ce module, et un fichier de déclaration au mieux de sa forme générait une signature d'index.

Code : Sélectionner tout
1
2
3
4
export declare let propName: string;
export declare class MyClass {
    [x: string]: number;
}

Dans TypeScript 5.8, l'exemple de code est désormais autorisé et le fichier de déclaration généré correspondra à ce que vous avez écrit :

Code : Sélectionner tout
1
2
3
4
export declare let propName: string;
export declare class MyClass {
    [propName]: number;
}

Notez que cela ne crée pas de propriétés nommées statiquement sur la classe. Vous obtiendrez toujours ce qui est en fait une signature d'index comme [x : string] : number, donc pour ce cas d'utilisation, vous devrez utiliser unique symbol ou des types littéraux.

Notez que l'écriture de ce code était et est toujours une erreur sous l'option --isolatedDeclarations ; mais il est prévu que grâce à ce changement, les noms de propriétés calculés seront généralement autorisés dans les déclarations emit.

Il est possible (bien que peu probable) qu'un fichier compilé en TypeScript 5.8 génère un fichier de déclaration qui n'est pas rétro-compatible avec TypeScript 5.7 ou ant...
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.

Une erreur dans cette actualité ? Signalez-nous-la !