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 sortie de la version bêta de TypeScript 5.0
Et apporte un nouveau standard pour les décorateurs en plus de nombreuses autres améliorations

Le , par Anthony

25PARTAGES

9  0 
Aujourd'hui, nous sommes heureux d'annoncer la version bêta de TypeScript 5.0 ! Cette version apporte de nombreuses nouvelles fonctionnalités, tout en visant à rendre TypeScript, plus léger, plus simple et plus rapide. Nous avons implémenté le nouveau standard des décorateurs, une fonctionnalité pour mieux supporter les projets ESM dans Node et les bundlers, de nouvelles façons pour les auteurs de bibliothèques de contrôler l'inférence générique, nous avons étendu notre fonctionnalité JSDoc, simplifié la configuration et apporté de nombreuses autres améliorations.

Bien que la version 5.0 comprenne des modifications de correction et des dépréciations pour les flags moins utilisés, nous pensons que la plupart des utilisateurs auront une expérience de mise à niveau similaire à celle des versions précédentes.

Pour commencer à utiliser la version bêta, vous pouvez l'obtenir via NuGet, ou utiliser npm avec la commande suivante :

Code : Sélectionner tout
npm install typescript@beta

Voici une liste rapide de toutes les nouveautés de TypeScript 5.0 !

  • Décorateurs
  • Paramètres de type const
  • Prise en charge de plusieurs fichiers de configuration dans extends
  • Tous les enums sont des enums d'union
  • bundler --moduleResolution
  • Flags de personnalisation de la résolution
  • --verbatimModuleSyntax
  • Prise en charge de export type *
  • Support de @satisfies dans JSDoc
  • Support de @overload dans JSDoc
  • Passage des flags spécifiques à l'émission sous --build
  • Complétions exhaustives de switch/case
  • Optimisations de la vitesse, de la mémoire et de la taille des paquets

Décorateurs

Les décorateurs sont une fonctionnalité ECMAScript à venir qui nous permet de personnaliser les classes et leurs membres de manière réutilisable.

Considérons le code suivant :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    } 
} 
 
const p = new Person("Ray"); 
p.greet();

La méthode greet est assez simple ici, mais imaginons qu'il s'agisse de quelque chose de plus compliqué - peut-être qu'elle fait de la logique asynchrone, qu'elle est récursive, qu'elle a des effets de bord, etc. Indépendamment du type de boue que vous imaginez, disons que vous ajoutez quelques appels à console.log pour aider à déboguer la méthode greet.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    greet() { 
        console.log("LOG: Entering method."); 
 
        console.log(`Hello, my name is ${this.name}.`); 
 
        console.log("LOG: Exiting method.") 
    } 
}

Ce modèle est assez commun. Ce serait bien s'il y avait un moyen de le faire pour chaque méthode !

C'est là que les décorateurs interviennent. Nous pouvons écrire une fonction appelée loggedMethod qui ressemble à ce qui suit :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
function loggedMethod(originalMethod: any, _context: any) { 
 
    function replacementMethod(this: any, ...args: any[]) { 
        console.log("LOG: Entering method.") 
        const result = originalMethod.call(this, ...args); 
        console.log("LOG: Exiting method.") 
        return result; 
    } 
 
    return replacementMethod; 
}

"C'est quoi le problème avec tous ces anys ? Qu'est-ce que c'est, anyScript ! ?"

Soyez patient - nous gardons les choses simples pour l'instant afin de pouvoir nous concentrer sur ce que fait cette fonction. Remarquez que loggedMethod prend la méthode originale (originalMethod) et renvoie une fonction qui

  1. enregistre un message "Entering..." (entrée)
  2. transmet this ainsi que tous ses arguments à la méthode originale
  3. enregistre un message "Exiting...", et
  4. renvoie ce que la méthode originale a retourné.

Nous pouvons maintenant utiliser loggedMethod pour décorer la méthode greet :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    @loggedMethod 
    greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    } 
} 
 
const p = new Person("Ray"); 
p.greet(); 
 
// Output: 
// 
//   LOG: Entering method. 
//   Hello, my name is Ray. 
//   LOG: Exiting method.

Nous venons d'utiliser loggedMethod comme décorateur au-dessus de greet - et remarquez que nous l'avons écrit sous la forme @loggedMethod. Lorsque nous avons fait cela, il a été appelé avec la cible de la méthode et un objet de contexte. Parce que loggedMethod a retourné une nouvelle fonction, cette fonction a remplacé la définition originale de greet.

Nous ne l'avons pas encore mentionné, mais loggedMethod a été défini avec un deuxième paramètre. Il s'agit d'un "objet de contexte", qui contient des informations utiles sur la façon dont la méthode décorée a été déclarée - par exemple, s'il s'agit d'un membre #privé, ou statique, ou encore le nom de la méthode. Réécrivons loggedMethod pour en tirer parti et afficher le nom de la méthode qui a été décorée.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) { 
    const methodName = String(context.name); 
 
    function replacementMethod(this: any, ...args: any[]) { 
        console.log(`LOG: Entering method '${methodName}'.`) 
        const result = originalMethod.call(this, ...args); 
        console.log(`LOG: Exiting method '${methodName}'.`) 
        return result; 
    } 
 
    return replacementMethod; 
}

Nous utilisons maintenant le paramètre de contexte - et c'est la première chose dans loggedMethod qui a un type plus strict que any et any[]. TypeScript fournit un type appelé ClassMethodDecoratorContext qui modélise l'objet de contexte que les décorateurs de méthodes prennent.

Outre les métadonnées, l'objet de contexte pour les méthodes possède également une fonction très utile appelée addInitializer. C'est un moyen de s'accrocher au début du constructeur (ou de l'initialisation de la classe elle-même si nous travaillons avec des statics).

Par exemple, en JavaScript, il est courant d'écrire quelque chose comme le modèle suivant :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
 
        this.greet = this.greet.bind(this); 
    } 
 
    greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    } 
}

Alternativement, greet pourrait être déclaré comme une propriété initialisée avec une fonction arrow.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    greet = () => { 
        console.log(`Hello, my name is ${this.name}.`); 
    }; 
}

Ce code est écrit pour s'assurer que this n'est pas lié à nouveau si greet est appelé en tant que fonction autonome ou passé en tant que callback.

Code : Sélectionner tout
1
2
3
4
const greet = new Person("Ray").greet; 
 
// We don't want this to fail! 
greet();

Nous pouvons écrire un décorateur qui utilise addInitializer pour appeler bind dans le constructeur à notre place.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
function bound(originalMethod: any, context: ClassMethodDecoratorContext) { 
    const methodName = context.name; 
    if (context.private) { 
        throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`); 
    } 
    context.addInitializer(function () { 
        this[methodName] = this[methodName].bind(this); 
    }); 
}

bound ne renvoie rien. Par conséquent, lorsqu'il décore une méthode, il ne touche pas à l'original. Au lieu de cela, il ajoutera une logique avant que tout autre champ ne soit initialisé.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    @bound 
    @loggedMethod 
    greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    } 
} 
 
const p = new Person("Ray"); 
const greet = p.greet; 
 
// Works! 
greet();

Remarquez que nous avons empilé deux décorateurs - @bound et @loggedMethod. Ces décorations s'exécutent dans un "ordre inverse". Autrement dit, @loggedMethod décore la méthode originale greet, et @bound décore le résultat de @loggedMethod. Dans cet exemple, cela n'a pas d'importance, mais cela pourrait en avoir si vos décorateurs ont des effets de bord ou s'ils attendent un certain ordre.

Il convient également de noter que, si vous le préférez d'un point de vue stylistique, vous pouvez placer ces décorateurs sur la même ligne.

Code : Sélectionner tout
1
2
3
@bound @loggedMethod greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    }

Ce qui n'est peut-être pas évident, c'est que nous pouvons même créer des fonctions qui retournent des fonctions décoratrices. Cela permet de personnaliser légèrement le décorateur final. Si nous le voulions, nous aurions pu faire en sorte que loggedMethod renvoie un décorateur et personnaliser la façon dont il enregistre ses messages.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function loggedMethod(headMessage = "LOG:") { 
    return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) { 
        const methodName = String(context.name); 
 
        function replacementMethod(this: any, ...args: any[]) { 
            console.log(`${headMessage} Entering method '${methodName}'.`) 
            const result = originalMethod.call(this, ...args); 
            console.log(`${headMessage} Exiting method '${methodName}'.`) 
            return result; 
        } 
 
        return replacementMethod; 
    } 
}

Si nous faisions cela, nous devrions appeler loggedMethod avant de l'utiliser comme décorateur. Nous pourrions alors passer n'importe quel string comme préfixe pour les messages qui sont enregistrés dans la console.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person { 
    name: string; 
    constructor(name: string) { 
        this.name = name; 
    } 
 
    @loggedMethod("") 
    greet() { 
        console.log(`Hello, my name is ${this.name}.`); 
    } 
} 
 
const p = new Person("Ray"); 
p.greet(); 
 
// Output: 
// 
//    Entering method 'greet'. 
//   Hello, my name is Ray. 
//    Exiting method 'greet'.

Les décorateurs ne sont pas seulement utilisables avec les méthodes ! Ils peuvent être utilisés sur les propriétés/champs, les getters, les setters et les auto-accesseurs. Les classes elles-mêmes peuvent être décorées pour des choses comme le sous-classement et l'enregistrement.

Différences avec les anciens décorateurs expérimentaux

Si vous utilisez TypeScript depuis un certain temps, vous savez peut-être qu'il prend en charge les décorateurs "expérimentaux" depuis des années. Bien que ces décorateurs expérimentaux aient été incroyablement utiles, ils ont modélisé une version beaucoup plus ancienne de la proposition de décorateurs, et ont toujours nécessité un flag de compilation opt-in appelé --experimentalDecorators. Toute tentative d'utilisation des décorateurs dans TypeScript sans ce flag entraînait un message d'erreur.

--experimentalDecorators continuera à exister dans un avenir proche ; cependant, sans ce flag, les décorateurs seront désormais considérés comme une syntaxe valide pour tout nouveau code. En dehors de --experimentalDecorators, ils seront vérifiés au niveau du type et émis différemment. Les règles de vérification de type et d'émission sont suffisamment différentes pour que, même si les décorateurs peuvent être écrits pour supporter à la fois l'ancien et le nouveau comportement des décorateurs, il est peu probable que les fonctions de décorateurs existantes le fassent.

Cette nouvelle proposition de décorateurs n'est pas compatible avec --emitDecoratorMetadata, et elle n'autorise pas les paramètres de décoration. Les futures propositions de l'ECMAScript pourront peut-être aider à combler cette lacune.

Une dernière remarque : pour l'instant, la proposition relative aux décorateurs exige qu'un décorateur de classe vienne après le mot-clé export s'il est présent.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
export @register class Foo { 
    // ... 
} 
 
export 
@Component({ 
    // ... 
}) 
class Bar { 
    // ... 
}

TypeScript appliquera cette restriction dans les fichiers JavaScript, mais ne le fera pas pour les fichiers TypeScript. Une partie de ceci est motivée par les utilisateurs existants - nous espérons fournir un chemin de migration légèrement plus facile entre nos décorateurs "expérimentaux" originaux et les décorateurs standardisés. En outre, de nombreux utilisateurs nous ont fait part de leur préférence pour le style original, et nous espérons pouvoir discuter de cette question en toute bonne foi lors des futures discussions sur les normes.

Écrire des décorateurs bien typés

Les exemples de décorateurs loggedMethod et bound ci-dessus sont intentionnellement simples et omettent beaucoup de détails sur les types.

Le typage des décorateurs peut être assez complexe. Par exemple, une version bien typée du décorateur loggedMethod ci-dessus pourrait ressembler à quelque chose comme ceci :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loggedMethod<This, Args extends any[], Return>( 
    target: (this: This, ...args: Args) => Return, 
    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return> 
) { 
    const methodName = String(context.name); 
 
    function replacementMethod(this: This, ...args: Args): Return { 
        console.log(`LOG: Entering method '${methodName}'.`) 
        const result = target.call(this, ...args); 
        console.log(`LOG: Exiting method '${methodName}'.`) 
        return result; 
    } 
 
    return replacementMethod; 
}

Nous avons dû modéliser séparément le type de this, les paramètres et le type de retour de la méthode originale, en utilisant les paramètres de type This, Args et Return.

La complexité exacte de la définition de vos fonctions décoratrices dépend de ce que vous voulez garantir. Gardez à l'esprit que vos décorateurs seront plus utilisés qu'ils ne sont écrits, donc une version bien typée sera généralement préférable - mais il y a clairement un compromis avec la lisibilité, donc essayez de garder les choses simples.

Paramètres de type const

Lorsqu'il déduit le type d'un objet, TypeScript choisit généralement un type qui est censé être général. Par exemple, dans ce cas, le type inféré de names est string[] :

Code : Sélectionner tout
1
2
3
4
5
6
7
type HasNames = { readonly names: string[] }; 
function getNamesExactly<T extends HasNames>(arg: T): T["names"] { 
    return arg.names; 
} 
 
// Inferred type: string[] 
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

Habituellement, l'intention est de permettre la mutation en aval.

Cependant, en fonction de ce que fait exactement getNamesExactly et de la manière dont elle est censée être utilisée, il peut souvent arriver qu'un type plus spécifique soit souhaité.

Jusqu'à présent, les auteurs d'API devaient généralement recommander d'ajouter as const à certains endroits pour obtenir l'inférence souhaitée :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
// The type we wanted: 
//    readonly ["Alice", "Bob", "Eve"] 
// The type we got: 
//    string[] 
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]}); 
 
// Correctly gets what we wanted: 
//    readonly ["Alice", "Bob", "Eve"] 
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

Cela peut être fastidieux et facile à oublier. Dans TypeScript 5.0, vous pouvez désormais ajouter un modificateur const à une déclaration de paramètre de type pour que l'inférence de type const soit la valeur par défaut :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
type HasNames = { names: readonly string[] }; 
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] { 
//                       ^^^^^ 
    return arg.names; 
} 
 
// Inferred type: readonly ["Alice", "Bob", "Eve"] 
// Note: Didn't need to write 'as const' here 
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

Notez que le modificateur const ne rejette pas les valeurs mutables et n'exige pas de contraintes immuables. L'utilisation d'une contrainte de type mutable peut donner des résultats surprenants. Par exemple :

Code : Sélectionner tout
1
2
3
4
declare function fnBad<const T extends string[]>(args: T): void; 
 
// 'T' is still 'string[]' since 'readonly ["a", "b", "c"]' is not assignable to 'string[]' 
fnBad(["a", "b" ,"c"]);

Ici, le candidat inféré pour T est readonly ["a", "b", "c"], et un tableau readonly ne peut pas être utilisé là où un tableau mutable est nécessaire. Dans ce cas, l'inférence est ramenée à la contrainte, la chaîne est traitée comme un string[], et l'appel se déroule toujours avec succès.

Une meilleure définition de cette fonction devrait utiliser readonly string[] :

Code : Sélectionner tout
1
2
3
4
declare function fnGood<const T extends readonly string[]>(args: T): void; 
 
// T is readonly ["a", "b", "c"] 
fnGood(["a", "b" ,"c"]);

De même, n'oubliez pas que le modificateur const n'affecte que l'inférence des expressions d'objets, de tableaux et de primitives qui ont été écrites dans l'appel, donc les arguments qui ne seraient pas (ou ne pourraient pas) être modifiés avec as const ne verront pas de changement de comportement :

Code : Sélectionner tout
1
2
3
4
5
declare function fnGood<const T extends readonly string[]>(args: T): void; 
const arr = ["a", "b" ,"c"]; 
 
// 'T' is still 'string[]'-- the 'const' modifier has no effect here 
fnGood(arr);

Prise en charge de plusieurs fichiers de configuration dans extends

Lorsque vous gérez plusieurs projets, il peut être utile de disposer d'un fichier de configuration "base" à partir duquel les autres fichiers tsconfig.json peuvent s'étendre. C'est pourquoi TypeScript prend en charge un champ extends pour copier les champs de compilerOptions.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
// packages/front-end/src/tsconfig.json 
{ 
    "compilerOptions": { 
        "extends": "../../../tsconfig.base.json", 
 
        "outDir": "../lib", 
        // ... 
    } 
}

Cependant, il existe des scénarios dans lesquels vous pourriez vouloir étendre à partir de plusieurs fichiers de configuration. Par exemple, imaginez que vous utilisez un fichier de configuration de base TypeScript fourni par npm. Si vous souhaitez que tous vos projets utilisent également les options du paquet @tsconfig/strictest sur npm, il existe une solution simple : faites en sorte que tsconfig.base.json étende @tsconfig/strictest :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
// tsconfig.base.json 
{ 
    "compilerOptions": { 
        "extends": "@tsconfig/strictest/tsconfig.json", 
 
        // ... 
    } 
}

Cela fonctionne jusqu'à un certain point. Si vous avez des projets qui ne veulent pas utiliser @tsconfig/strictest, ils doivent soit désactiver manuellement les options, soit créer une version séparée de tsconfig.base.json qui ne s'étend pas à partir de @tsconfig/strictest.

Pour plus de souplesse, Typescript 5.0 permet désormais au champ extends de prendre plusieurs entrées. Par exemple, dans ce fichier de configuration :

Code : Sélectionner tout
1
2
3
4
5
{ 
    "compilerOptions": { 
        "extends": ["a", "b", "c"] 
    } 
}

Écrire ceci revient à étendre directement c, où c étend b, et b étend a. Si l'un des champs est " en conflit ", la dernière entrée l'emporte.

Ainsi, dans l'exemple suivant, strictNullChecks et noImplicitAny sont tous deux activés dans le fichier tsconfig.json final.

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
// tsconfig1.json 
{ 
    "compilerOptions": { 
        "strictNullChecks": true 
    } 
} 
 
// tsconfig2.json 
{ 
    "compilerOptions": { 
        "noImplicitAny": true 
    } 
} 
 
// tsconfig.json 
{ 
    "compilerOptions": { 
        "extends": ["./tsconfig1.json", "./tsconfig2.json"] 
    }, 
    "files": ["./index.ts"] 
}

À titre d'exemple, nous pouvons réécrire notre exemple original de la manière suivante.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
// packages/front-end/src/tsconfig.json 
{ 
    "compilerOptions": { 
        "extends": ["@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json"], 
 
        "outDir": "../lib", 
        // ... 
    } 
}

Tous les enums sont des enums d'union

Lorsque TypeScript a initialement introduit les enums, ils n'étaient rien de plus qu'un ensemble de constantes numériques avec le même type.

Code : Sélectionner tout
1
2
3
4
enum E { 
    Foo = 10, 
    Bar = 20, 
}

La seule chose spéciale à propos de E.Foo et E.Bar était qu'ils étaient affectables à tout ce qui attendait le type E. En dehors de cela, ils étaient à peu près juste des numbers.

Code : Sélectionner tout
1
2
3
4
function takeValue(e: E) {} 
 
takeValue(E.Foo); // works 
takeValue(123);   // error!

Ce n'est que lorsque TypeScript 2.0 a introduit les types littéraux d'enum que les enums sont devenus un peu plus spéciaux. Les types littéraux d'enum donnent à chaque membre de l'enum son propre type, et transforment l'enum elle-même en une union du type de chaque membre. Ils nous permettent également de nous référer uniquement à un sous-ensemble de types d'une enum, et de réduire ces types.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet 
enum Color { 
    Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet 
} 
 
// Each enum member has its own type that we can refer to! 
type PrimaryColor = Color.Red | Color.Green | Color.Blue; 
 
function isPrimaryColor(c: Color): C is PrimaryColor { 
    // Narrowing literal types can catch bugs. 
    // TypeScript will error here because 
    // we'll end up comparing 'Color.Red' to 'Color.Green'. 
    // We meant to use ||, but accidentally wrote &&. 
    return c === Color.Red && c === Color.Green && c === Color.Blue; 
}

Le fait de donner à chaque membre d'une enum son propre type posait un problème : ces types étaient en partie associés à la valeur réelle du membre. Dans certains cas, il n'est pas possible de calculer cette valeur - par exemple, un membre d'une énumération peut être initialisé par un appel de fonction.

Code : Sélectionner tout
1
2
3
enum E { 
    Blah = Math.random() 
}

Chaque fois que TypeScript rencontrait ces problèmes, il faisait discrètement demi-tour et utilisait l'ancienne stratégie d'enum. Cela signifiait renoncer à tous les avantages des unions et des types littéraux.

TypeScript 5.0 parvient à transformer tous les enums en unions d'enums en créant un type unique pour chaque membre calculé. Cela signifie que tous les enums peuvent désormais être réduits et que leurs membres sont également référencés en tant que types.

Bundler --moduleResolution

TypeScript 4.7 a introduit les options node16 et nodenext pour ses options --module et --moduleResolution. L'intention de ces options était de mieux modéliser les règles de recherche précises pour les modules ECMAScript dans Node.js ; cependant, ce mode a de nombreuses restrictions que les autres outils n'appliquent pas vraiment.

Par exemple, dans un module ECMAScript de Node.js, toute importation relative doit inclure une extension de fichier.

Code : Sélectionner tout
1
2
3
4
// entry.mjs 
import * as utils from "./utils";     //  wrong - we need to include the file extension. 
 
import * as utils from "./utils.mjs"; //  works

Il y a certaines raisons à cela dans Node.js et dans le navigateur - cela accélère la recherche de fichiers et fonctionne mieux pour les serveurs de fichiers naïfs. Mais pour de nombreux développeurs utilisant des outils comme les bundlers, les options node16/nodenext étaient encombrantes car les bundlers n'ont pas la plupart de ces restrictions. D'une certaine manière, le mode de résolution de node était meilleur pour quiconque utilise un bundler.

Mais d'une certaine manière, le mode de résolution original de node était déjà dépassé. La plupart des bundlers modernes utilisent une fusion du module ECMAScript et des règles de recherche CommonJS dans Node.js. Par exemple, les importations sans extension fonctionnent très bien comme dans CommonJS, mais lorsqu'on consulte les conditions d'exportation d'un paquet, on préfère une condition d'import comme dans un fichier ECMAScript.

Pour modéliser le fonctionnement des bundlers, TypeScript introduit désormais une nouvelle stratégie : le bundler --moduleResolution.

Code : Sélectionner tout
1
2
3
4
5
6
{ 
    "compilerOptions": { 
        "target": "esnext", 
        "moduleResolution": "bundler" 
    } 
}

Si vous utilisez un bundler moderne comme Vite, esbuild, swc, Webpack, Parcel, et d'autres qui mettent en œuvre une stratégie de recherche hybride, la nouvelle option bundler devrait vous convenir.

Flags de personnalisation de la résolution

Les outils JavaScript peuvent désormais modéliser des règles de résolution "hybrides", comme dans le mode bundler que nous avons décrit ci-dessus. Parce que les outils peuvent différer légèrement dans leur support, TypeScript 5.0 fournit des moyens d'activer ou de désactiver quelques fonctionnalités qui peuvent ou non fonctionner avec votre configuration.

allowImportingTsExtensions

--allowImportingTsExtensions permet aux fichiers TypeScript de s'importer mutuellement avec une extension spécifique à TypeScript comme .ts, .mts ou .tsx.

Ce flag n'est autorisé que lorsque --noEmit ou --emitDeclarationOnly est activé, car ces chemins d'importation ne seraient pas résolvables au moment de l'exécution dans les fichiers de sortie JavaScript. On s'attend ici à ce que votre résolveur (par exemple votre bundler, un runtime ou un autre outil) fasse fonctionner ces importations entre fichiers .ts.

resolvePackageJsonExports

--resolvePackageJsonExports force TypeScript à consulter le champ exports des fichiers package.json s'il lit un package dans node_modules.

Cette option a la valeur true par défaut sous les options node16, nodenext, et bundler pour --moduleResolution.

resolvePackageJsonImports

--resolvePackageJsonImports force TypeScript à consulter le champ imports des fichiers package.json lorsqu'il effectue une recherche qui commence par # à partir d'un fichier dont le répertoire ancêtre contient un package.json.

Cette option a la valeur true par défaut sous les options node16, nodenext, et bundler pour --moduleResolution.

allowArbitraryExtensions

Dans TypeScript 5.0, lorsqu'un chemin d'importation se termine par une extension qui n'est pas une extension de fichier JavaScript ou TypeScript connue, le compilateur recherche un fichier de déclaration pour ce chemin sous la forme {nom de base du fichier}.d.{extension}.ts. Par exemple, si vous utilisez un chargeur CSS dans un projet bundler, vous pouvez souhaiter écrire (ou générer) des fichiers de déclaration pour ces feuilles de style :

Code : Sélectionner tout
1
2
3
4
/* app.css */ 
.cookie-banner { 
  display: none; 
}
Code : Sélectionner tout
1
2
3
4
5
// app.d.css.ts 
declare const css: { 
  cookieBanner: string; 
}; 
export default css;
Code : Sélectionner tout
1
2
3
4
// App.tsx 
import styles from "./app.css"; 
 
styles.cookieBanner; // string

Par défaut, cette importation génère une erreur pour vous informer que TypeScript ne comprend pas ce type de fichier et que votre moteur d'exécution peut ne pas prendre en charge son importation. Mais si vous avez configuré votre runtime ou bundler pour le gérer, vous pouvez supprimer l'erreur avec la nouvelle option de compilation --allowArbitraryExtensions.

Notez qu'historiquement, un effet similaire a souvent pu être obtenu en ajoutant un fichier de déclaration nommé app.css.d.ts au lieu de app.d.css.ts - cependant, cela a juste fonctionné à travers les règles de résolution require de Node pour CommonJS. Strictement parlant, le premier est interprété comme un fichier de déclaration pour un fichier JavaScript nommé app.css.js. Comme les importations de fichiers relatifs doivent inclure des extensions dans le support ESM de Node, TypeScript commettrait une erreur sur notre exemple dans un fichier ESM sous --moduleResolution node16 ou nodenext.

customConditions

--customConditions prend une liste de conditions supplémentaires qui devraient réussir lorsque TypeScript résout à partir d'un champ [exports] ou imports d'un package.json. Ces conditions sont ajoutées aux conditions existantes qu'un résolveur utilisera par défaut.

Par exemple, lorsque ce champ est défini dans un tsconfig.json comme suit :

Code : Sélectionner tout
1
2
3
4
5
6
7
{ 
    "compilerOptions": { 
        "target": "es2022", 
        "moduleResolution": "bundler", 
        "customConditions": ["my-condition"] 
    } 
}

Chaque fois qu'un champ exports ou imports est référencé dans package.json, TypeScript prendra en compte les conditions appelées my-condition.

Ainsi, lors de l'importation à partir d'un paquet avec le package.json suivant

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
{ 
    // ... 
    "exports": { 
        ".": { 
            "my-condition": "./foo.mjs", 
            "node": "./bar.mjs", 
            "import": "./baz.mjs", 
            "require": "./biz.mjs" 
        } 
    } 
}

TypeScript va essayer de rechercher les fichiers correspondant à foo.mjs.

Ce champ n'est valide que sous les options node16, nodenext, et bundler pour --moduleResolution.

--verbatimModuleSyntaxe

Par défaut, TypeScript fait quelque chose appelée élision d'importation. Fondamentalement, si vous écrivez quelque chose comme

Code : Sélectionner tout
1
2
3
4
5
import { Car } from "./car"; 
 
export function drive(car: Car) { 
    // ... 
}

TypeScript détecte que vous utilisez une importation uniquement pour les types et abandonne l'importation en entier. Votre sortie JavaScript pourrait ressembler à quelque chose comme ceci :

Code : Sélectionner tout
1
2
3
export function drive(car) { 
    // ... 
}

La plupart du temps, c'est une bonne chose, car si Car n'est pas une valeur exportée de ./car, nous obtiendrons une erreur d'exécution.

Mais cela ajoute une couche de complexité pour certains cas limites. Par exemple, remarquez qu'il n'y a pas d'instruction comme import "./car" ; - l'importation a été entièrement abandonnée. Cela fait réellement une différence pour les modules qui ont des effets de bord ou non.

La stratégie d'émission de TypeScript pour JavaScript présente également quelques autres couches de complexité - l'élision d'importation n'est pas toujours uniquement déterminée par la manière dont une importation est utilisée - elle consulte souvent la manière dont une valeur est également déclarée. Il n'est donc pas toujours évident de savoir si un code comme le suivant

Code : Sélectionner tout
export { Car } from "./car";

doit être préservé ou abandonné. Si Car est déclaré avec quelque chose comme class, alors il peut être préservé dans le fichier JavaScript résultant. Mais si Car est uniquement déclaré comme un type alias ou une interface, le fichier JavaScript ne doit pas exporter Car du tout.

Alors que TypeScript pourrait être en mesure de prendre ces décisions d'émission basées sur des informations provenant de plusieurs fichiers, tous les compilateurs ne le peuvent pas.

Le modificateur type sur les importations et les exportations aide un peu à résoudre ces situations. Nous pouvons rendre explicite le fait qu'une importation ou une exportation est uniquement utilisée pour l'analyse de type, et peut être abandonnée entièrement dans les fichiers JavaScript en utilisant le modificateur type.

Code : Sélectionner tout
1
2
3
4
5
6
// This statement can be dropped entirely in JS output 
import type * as car from "./car"; 
 
// The named import/export 'Car' can be dropped in JS output 
import { type Car } from "./car"; 
export { type Car } from "./car";

Les modificateurs type ne sont pas tout à fait utiles en eux-mêmes - par défaut, l'élision de module laissera toujours tomber les importations, et rien ne vous oblige à faire la distinction entre les importations et les exportations type et ordinaires. TypeScript dispose donc de l'indicateur --importsNotUsedAsValues pour s'assurer que vous utilisez le modificateur type, --preserveValueImports pour empêcher certains comportements d'élision de module, et --isolatedModules pour s'assurer que votre code TypeScript fonctionne sur différents compilateurs. Malheureusement, il est difficile de comprendre les détails de ces trois flags, et il existe encore quelques cas limites avec des comportements inattendus.

TypeScript 5.0 introduit une nouvelle option appelée --verbatimModuleSyntax pour simplifier la situation. Les règles sont beaucoup plus simples - toutes les importations ou exportations sans un modificateur type sont laissées en place. Tout ce qui utilise le modificateur type est entièrement abandonné.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
// Erased away entirely. 
import type { A } from "a"; 
 
// Rewritten to 'import { b } from "bcd";' 
import { b, type c, type d } from "bcd"; 
 
// Rewritten to 'import {} from "xyz";' 
import { type xyz } from "xyz";

Avec cette nouvelle option, ce que vous voyez est ce que vous obtenez.

Cela a cependant quelques implications lorsqu'il s'agit de l'interopérabilité des modules. Avec ce drapeau, les imports et exports ECMAScript ne seront pas réécrites par des appels require lorsque vos paramètres ou votre extension de fichier impliquent un système de module différent. Au lieu de cela, vous obtiendrez une erreur. Si vous devez émettre du code qui utilise require et module.exports, vous devrez utiliser la syntaxe de module de TypeScript qui est antérieure à ES2015 :


Bien qu'il s'agisse d'une limitation, cela permet de rendre certains problèmes plus évidents. Par exemple, il est très courant d'oublier de définir le champ type dans package.json sous --module node16. Par conséquent, les développeurs commencent à écrire des modules CommonJS au lieu de modules ES sans s'en rendre compte, ce qui donne des règles de recherche et des résultats JavaScript surprenants. Ce nouveau flag garantit que vous êtes intentionnel quant au type de fichier que vous utilisez car la syntaxe est intentionnellement différente.

Parce que --verbatimModuleSyntax fournit une histoire plus cohérente que --importsNotUsedAsValues et --preserveValueImports, ces deux flags existants sont dépréciés en sa faveur.

Prise en charge de export type *

Lorsque TypeScript 3.8 a introduit les importations de type uniquement, la nouvelle syntaxe n'était pas autorisée sur les réexportations export * from "module" ou export * as ns from "module". TypeScript 5.0 ajoute la prise en charge de ces deux formes :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// models/vehicles.ts 
export class Spaceship { 
  // ... 
} 
 
// models/index.ts 
export type * as vehicles from "./spaceship"; 
 
// main.ts 
import { vehicles } from "./models"; 
 
function takeASpaceship(s: vehicles.Spaceship) { 
  //  ok - `vehicles` only used in a type position 
} 
 
function makeASpaceship() { 
  return new vehicles.Spaceship(); 
  //         ^^^^^^^^ 
  // 'vehicles' cannot be used as a value because it was exported using 'export type'. 
}

Support de @satisfies dans JSDoc

TypeScript 4.9 a introduit l'opérateur satisfies. Il s'assure que le type d'une expression est compatible, sans affecter le type lui-même. Par exemple, prenons le code suivant :

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
interface CompilerOptions { 
    strict?: boolean; 
    outDir?: string; 
    // ... 
 
    extends?: string | string[]; 
} 
 
declare function resolveConfig(configPath: string): CompilerOptions; 
 
let myCompilerOptions = { 
    strict: true, 
    outDir: "../lib", 
    // ... 
 
    extends: [ 
        "@tsconfig/strictest/tsconfig.json", 
        "../../../tsconfig.base.json" 
    ], 
 
} satisfies CompilerOptions;

Ici, TypeScript sait que myCompilerOptions.extends a été déclaré avec un tableau - parce que tandis que satisfies a validé le type de notre objet, il ne l'a pas carrément changé en CompilerOptions et perdu l'information. Donc si nous voulons mapper sur extends, c'est très bien.

Code : Sélectionner tout
let inheritedConfigs = myCompilerOptions.extends.map(resolveConfig);

Ceci était utile pour les utilisateurs de TypeScript, mais beaucoup de gens utilisent TypeScript pour vérifier le type de leur code JavaScript en utilisant les annotations JSDoc. C'est pourquoi TypeScript 5.0 prend en charge une nouvelle balise JSDoc appelée @satisfies qui fait exactement la même chose.

/** @satisfies */ permet de détecter les incompatibilités de type :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// @ts-check 
 
/** 
 * @typedef CompilerOptions 
 * @prop {boolean} [strict] 
 * @prop {string} [outDir] 
 * @prop {string | string[]} [extends] 
 */ 
 
/** 
 * @satisfies {CompilerOptions} 
 */ 
let myCompilerOptions = { 
    outdir: "../lib", 
//  ~~~~~~ oops! we meant outDir 
};

Mais elle préservera le type original de nos expressions, ce qui nous permettra d'utiliser nos valeurs de manière plus précise plus tard dans notre code.

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
// @ts-check 
 
/** 
 * @typedef CompilerOptions 
 * @prop {boolean} [strict] 
 * @prop {string} [outDir] 
 * @prop {string | string[]} [extends] 
 */ 
 
/** 
 * @satisfies {CompilerOptions} 
 */ 
let myCompilerOptions = { 
    strict: true, 
    outDir: "../lib", 
    extends: [ 
        "@tsconfig/strictest/tsconfig.json", 
        "../../../tsconfig.base.json" 
    ], 
}; 
 
let inheritedConfigs = myCompilerOptions.extends.map(resolveConfig);

/** @satisfies */ peut également être utilisé en ligne sur toute expression entre parenthèses. Nous aurions pu écrire myCompilerOptions comme ceci :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
let myCompilerOptions = /** @satisfies {CompilerOptions} */ ({ 
    strict: true, 
    outDir: "../lib", 
    extends: [ 
        "@tsconfig/strictest/tsconfig.json", 
        "../../../tsconfig.base.json" 
    ], 
});

Pourquoi ? Eh bien, cela a généralement plus de sens lorsque vous vous trouvez plus en profondeur dans un autre code, comme un appel de fonction.

Code : Sélectionner tout
1
2
3
compileCode(/** @satisfies {CompilerOptions} */ ({ 
    // ... 
}));

Support de @overload dans JSDoc

En TypeScript, vous pouvez spécifier des surcharges pour une fonction. Les surcharges nous donnent un moyen de dire qu'une fonction peut être appelée avec différents arguments, et éventuellement retourner différents résultats. Elles peuvent restreindre la façon dont les callers peuvent effectivement utiliser nos fonctions, et affiner les résultats qu'ils obtiendront en retour.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Our overloads: 
function printValue(str: string): void; 
function printValue(num: number, maxFractionDigits?: number): void; 
 
// Our implementation: 
function printValue(value: string | number, maximumFractionDigits?: number) { 
    if (typeof value === "number") { 
        const formatter = Intl.NumberFormat("en-US", { 
            maximumFractionDigits, 
        }); 
        value = formatter.format(value); 
    } 
 
    console.log(value); 
}

Ici, nous avons dit que printValue prend soit un string soit un number comme premier argument. Si elle prend un number, elle peut prendre un deuxième argument pour déterminer le nombre de chiffres fractionnaires que nous pouvons imprimer.

TypeScript 5.0 permet désormais à JSDoc de déclarer des surcharges avec une nouvelle balise @overload. Chaque commentaire JSDoc avec une balise @overload est traité comme une surcharge distincte pour la déclaration de fonction suivante.

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
23
24
25
26
27
28
29
// @ts-check 
 
/** 
 * @overload 
 * @param {string} value 
 * @return {void} 
 */ 
 
/** 
 * @overload 
 * @param {number} value 
 * @param {number} [maximumFractionDigits] 
 * @return {void} 
 */ 
 
/** 
 * @param {string | number} value 
 * @param {number} [maximumFractionDigits] 
 */ 
function printValue(value, maximumFractionDigits) { 
    if (typeof value === "number") { 
        const formatter = Intl.NumberFormat("en-US", { 
            maximumFractionDigits, 
        }); 
        value = formatter.format(value); 
    } 
 
    console.log(value); 
}

Maintenant, indépendamment du fait que nous écrivons dans un fichier TypeScript ou JavaScript, TypeScript peut nous indiquer si nous avons appelé nos fonctions de manière incorrecte.

Code : Sélectionner tout
1
2
3
4
5
6
// all allowed 
printValue("hello!"); 
printValue(123.45); 
printValue(123.45, 2); 
 
printValue("hello!", 123); // error!

Passage des flags spécifiques à Emit sous --build

TypeScript permet maintenant de passer les flags suivants sous le mode --build

  • --declaration
  • --emitDeclarationOnly
  • --declarationMap
  • --soureMap
  • --inlineSourceMap

Il est ainsi beaucoup plus facile de personnaliser certaines parties d'un build lorsque les builds de développement et de production sont différents.

Par exemple, la version de développement d'une bibliothèque peut ne pas avoir besoin de produire des fichiers de déclaration, alors que la version de production en aura besoin. Un projet peut configurer l'émission de déclarations pour qu'elle soit désactivée par défaut et simplement construite avec l'option

Code : Sélectionner tout
tsc --build -p ./my-project-dir

Une fois que vous avez fini d'itérer dans la boucle interne, un build de "production" peut simplement passer le flag --declaration.

Code : Sélectionner tout
tsc --build -p ./my-project-dir --declaration

Complétions exhaustives de switch/case

Lors de l'écriture d'une déclaration switch, TypeScript détecte maintenant si la valeur vérifiée a un type littéral. Si c'est le cas, il offrira une complétion qui échafaude chaque case non couvert.


Optimisations de la vitesse, de la mémoire et de la taille des paquets

TypeScript 5.0 contient beaucoup de changements puissants à travers notre structure de code, nos structures de données, et les implémentations algorithmiques. Ce que tout cela signifie, c'est que l'ensemble de votre expérience devrait être plus rapide - pas seulement l'exécution de TypeScript, mais même son installation.

Voici quelques gains intéressants en termes de vitesse et de taille que nous avons été en mesure de capturer par rapport à TypeScript 4.9.


En d'autres termes, nous avons constaté que TypeScript 5.0 Beta prend seulement 81% du temps qu'il faut à TypeScript 4.9 pour un build VS Code.


Comment ? Il y a quelques améliorations notables sur lesquelles nous aimerions donner plus de détails à l'avenir. Mais nous ne vous ferons pas attendre pour ce billet de blog.

Tout d'abord, nous avons récemment fait migrer TypeScript des espaces de noms vers les modules, ce qui nous permet de tirer parti d'un outil de build moderne capable d'effectuer des optimisations comme le scope hoisting. L'utilisation de cet outil, la révision de notre stratégie d'empaquetage et la suppression de certains codes obsolètes ont permis de réduire d'environ 26,5 Mo la taille des paquets de TypeScript 4.9, qui était de 63,8 Mo. Cela nous a également apporté une accélération notable grâce aux appels de fonctions directs.

TypeScript a également ajouté plus d'uniformité aux types d'objets internes dans le compilateur, tout en réduisant également certains types d'objets. Cela a permis de réduire les sites d'utilisation polymorphique et mégamorphique, tout en compensant une partie de l'empreinte mémoire qui en découlait.

Nous avons également effectué une mise en cache lors de la sérialisation des informations en chaînes de caractères. L'affichage des types, qui peut se produire dans le cadre de rapports d'erreurs, de déclarations émises, de complétions de code, et plus encore, peut finir par être assez coûteux. TypeScript met maintenant en cache certaines machines couramment utilisées pour les réutiliser dans ces opérations.

Dans l'ensemble, nous nous attendons à ce que la plupart des bases de code devraient voir des améliorations de vitesse de TypeScript 5.0, et nous avons toujours été en mesure de reproduire des gains entre 10 % et 20 %. Bien sûr, cela dépendra du matériel et des caractéristiques de la base de code, mais nous vous encourageons à l'essayer sur votre base de code dès aujourd'hui !

Changements importants et dépréciations

Exigences d'exécution

TypeScript vise maintenant ECMAScript 2018. Pour les utilisateurs de Node, cela signifie que la version minimale requise est au moins Node.js 10 et plus.

Modifications de lib.d.ts

Les modifications apportées à la façon dont les types pour le DOM sont générés peuvent avoir un impact sur le code existant. Notamment, certaines propriétés ont été converties du type number en type littéral numérique, et les propriétés et méthodes de gestion des événements couper, copier et coller ont été déplacées entre les interfaces.

Changements importants concernant l'API

Dans TypeScript 5.0, nous sommes passés aux modules, nous avons supprimé certaines interfaces inutiles et nous avons apporté quelques améliorations en matière de correction.

Coercitions implicites interdits dans les opérateurs relationnels

Certaines opérations dans TypeScript vous avertissent déjà si vous écrivez du code susceptible de provoquer une coercition implicite entre une chaîne et un nombre :

Code : Sélectionner tout
1
2
3
function func(ns: number | string) { 
  return ns * 4; // Error, possible implicit coercion 
}

Dans la version 5.0, cela sera également appliqué aux opérateurs relationnels >, <, <= et >= :

Code : Sélectionner tout
1
2
3
function func(ns: number | string) { 
  return ns > 4; // Now also an error 
}

Pour permettre cette coercition, vous pouvez explicitement transformer l'opérande en number en utilisant + :

Code : Sélectionner tout
1
2
3
function func(ns: number | string) { 
  return +ns > 4; // OK 
}

Révision des Enum

Depuis la première version de TypeScript, il existe des bizarreries de longue date concernant les enums. Dans la version 5.0, nous avons résolu certains de ces problèmes et réduit le nombre de concepts nécessaires pour comprendre les différents types d'enums que vous pouvez déclarer.

Il y a deux nouvelles erreurs principales que vous pourriez voir dans ce cadre. La première est que l'affectation d'un littéral hors domaine à un type d'enum donnera lieu à l'erreur à laquelle on peut s'attendre :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
enum SomeEvenDigit { 
    Zero = 0, 
    Two = 2, 
    Four = 4 
} 
 
// Now correctly an error 
let m: SomeEvenDigit = 1;

L'autre problème est que la déclaration de certains types de formes d'enum indirecte mixte chaîne/nombre créerait, à tort, une enum entièrement numérique :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
enum Letters { 
    A = "a" 
} 
enum Numbers { 
    one = 1, 
    two = Letters.A 
} 
 
// Now correctly an error 
const t: number = Numbers.two;

Vérification de type plus précise pour les décorateurs de paramètres dans les constructeurs sous --experimentalDecorators

TypeScript 5.0 rend la vérification de type plus précise pour les décorateurs sous --experimentalDecorators. Un endroit où cela devient apparent est l'utilisation d'un décorateur sur un paramètre de constructeur.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
export declare const inject: 
  (entity: any) => 
    (target: object, key: string | symbol, index?: number) => void; 
 
export class Foo {} 
 
export class C { 
    constructor(@inject(Foo) private x: any) { 
    } 
}

Cet appel échouera parce que key attend une string | symbol, mais les paramètres du constructeur reçoivent une clé undefined. La solution correcte consiste à changer le type de key dans inject. Une solution de contournement raisonnable si vous utilisez une bibliothèque qui ne peut pas être mise à jour est de wrapper inject dans une fonction décorateur plus sûre en termes de type, et d'utiliser une assertion de type sur key.

Dépréciations et changements par défaut

Dans TypeScript 5.0, nous avons supprimé les paramètres et valeurs de paramètres suivants :

  • --target : ES3
  • --out
  • --noImplicitUseStrict
  • --keyofStringsOnly
  • --suppressExcessPropertyErrors
  • --suppressImplicitAnyIndexErrors
  • --noStrictGenericChecks
  • --charset
  • --importsNotUsedAsValues
  • --preserveValueImports
  • prepend dans les références du projet

Ces configurations resteront autorisées jusqu'à TypeScript 5.5, date à laquelle elles seront entièrement supprimées. Toutefois, vous recevrez un avertissement si vous utilisez ces paramètres. Dans TypeScript 5.0, ainsi que dans les futures versions 5.1, 5.2, 5.3 et 5.4, vous pouvez spécifier "ignoreDeprecations" : "5.0" pour faire taire ces avertissements. Nous publierons prochainement un patch 4.9 permettant de spécifier ignoreDeprecations pour faciliter les mises à jour. En dehors des dépréciations, nous avons modifié certains paramètres pour améliorer le comportement multiplateforme de TypeScript.

--newLine, qui contrôle les fins de ligne émises dans les fichiers JavaScript, était auparavant déduit en fonction du système d'exploitation actuel s'il n'était pas spécifié. Nous pensons que les builds doivent être aussi déterministes que possible, et le Bloc-notes de Windows prend en charge les fins de ligne avec saut de ligne, donc le nouveau paramètre par défaut est LF. L'ancien comportement d'inférence spécifique au système d'exploitation n'est plus disponible.

--forceConsistentCasingInFileNames, qui s'assurait que toutes les références au même nom de fichier dans un projet s'accordaient sur le casing, a maintenant la valeur par défaut true. Cela peut aider à attraper des problèmes de différences avec du code écrit sur des systèmes de fichiers insensibles à la casse.

Source : Microsoft

Et vous ?

Avez-vous testé la dernière version de TypeScript ? Qu'en pensez-vous ?

Voir aussi :

Microsoft annonce la disponibilité de TypeScript 4.9 qui se dote du nouvel opérateur « satisfies », améliore l'opérateur « in » et prend déjà en charge les accesseurs automatiques d'ECMAScript
TypeScript 4.9 Beta est disponible et apporte la restriction des propriétés non listées avec l'opérateur in, ainsi que la vérification de l'égalité sur NaN
TypeScript 4.7 Beta s'accompagne de la prise en charge du module ECMAScript dans Node.js et propose un contrôle de la détection de module

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

Avatar de richardc
Membre à l'essai https://www.developpez.com
Le 01/02/2023 à 8:56
Bel article qui au moins ne reprend char après char le post original.

Merci.
1  0