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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

TypeScript Discussion :

Microsoft annonce la disponibilité de TypeScript 5.0


Sujet :

TypeScript

  1. #1
    Chroniqueur Actualités
    Avatar de Anthony
    Homme Profil pro
    Rédacteur technique
    Inscrit en
    Novembre 2022
    Messages
    900
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Rédacteur technique

    Informations forums :
    Inscription : Novembre 2022
    Messages : 900
    Points : 14 884
    Points
    14 884
    Par défaut Microsoft annonce la disponibilité de TypeScript 5.0
    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

    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - 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
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - 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
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - 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
    // 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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    /* app.css */
    .cookie-banner {
      display: none;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    // app.d.css.ts
    declare const css: {
      cookieBanner: string;
    };
    export default css;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 :

    Nom : table comparaison.PNG
Affichages : 15701
Taille : 30,9 Ko

    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 - 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
    // 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 - 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
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - 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
    // @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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - 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
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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.

    Nom : switchCaseSnippets-5-0_1.gif
Affichages : 3189
Taille : 19,0 Ko

    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.

    Nom : tableau perf typescript 5.PNG
Affichages : 3187
Taille : 27,3 Ko

    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.

    Nom : speed-and-size-5-0-beta.png
Affichages : 3181
Taille : 29,5 Ko

    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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
    Contribuez au club : corrections, suggestions, critiques, ... Contactez le service news et Rédigez des actualités

  2. #2
    Membre à l'essai
    Homme Profil pro
    Architecte de système d'information
    Inscrit en
    Juillet 2009
    Messages
    8
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France

    Informations professionnelles :
    Activité : Architecte de système d'information

    Informations forums :
    Inscription : Juillet 2009
    Messages : 8
    Points : 16
    Points
    16
    Par défaut Bel article
    Bel article qui au moins ne reprend char après char le post original.

    Merci.

  3. #3
    Chroniqueur Actualités
    Avatar de Anthony
    Homme Profil pro
    Rédacteur technique
    Inscrit en
    Novembre 2022
    Messages
    900
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Rédacteur technique

    Informations forums :
    Inscription : Novembre 2022
    Messages : 900
    Points : 14 884
    Points
    14 884
    Par défaut Microsoft annonce la disponibilité de TypeScript 5.0
    Microsoft annonce la disponibilité de TypeScript 5.0, et présente les principaux changements notables depuis la publication de la version bêta

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

    Si vous ne connaissez pas encore TypeScript, il s'agit d'un langage qui s'appuie sur JavaScript en ajoutant une syntaxe pour les types qui peuvent être utilisés pour la vérification de type. Le contrôle de type permet de détecter de nombreuses erreurs courantes, des fautes de frappe aux erreurs de logique. L'ajout de types à JavaScript nous permet également de construire des outils performants, puisque les types peuvent alimenter des fonctionnalités telles que les complétions de code, les définitions de type, et les refactorings dans votre éditeur favori. En fait, si vous avez utilisé des éditeurs comme Visual Studio ou VS Code, TypeScript vous offre déjà l'expérience JavaScript !

    Mais si vous connaissez déjà TypeScript, n'ayez crainte ! La version 5.0 n'est pas un bouleversement, et tout ce que vous savez est encore applicable. Bien que TypeScript 5.0 comprenne des changements de correction et quelques dépréciations pour des options peu utilisées, nous pensons que la plupart des développeurs auront une expérience de mise à jour similaire à celle des versions précédentes.

    Pour commencer à utiliser TypeScript 5.0, vous pouvez l'obtenir via NuGet, ou utiliser npm avec la commande suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    npm install -D typescript

    Quelles sont les nouveautés depuis la Beta et la RC ?

    TypeScript 5.0 a connu plusieurs changements notables depuis notre version bêta.

    Une nouvelle différence depuis TypeScript 5.0 Beta est que TypeScript permet aux décorateurs d'être placés avant ou après export et export default. Ce changement reflète les discussions et le consensus au sein du TC39, l'organisme de normalisation pour ECMAScript/JavaScript.

    Par ailleurs, la nouvelle option de résolution de module du bundler ne peut désormais être utilisée que lorsque l'option --module est définie sur esnext. Ceci a été fait pour s'assurer que les instructions import écrites dans les fichiers d'entrée ne seront pas transformées en appels require avant que le bundler ne les résolve, que le bundler ou le chargeur respecte ou non l'option module de TypeScript. Nous avons également fourni un certain contexte dans ces notes de mise à jour en recommandant à la plupart des auteurs de bibliothèques de s'en tenir à node16 ou nodenext.

    Bien que TypeScript 5.0 Beta ait été livré avec cette fonctionnalité, nous n'avons pas documenté notre travail sur la prise en charge du tri des importations non-sensibles à la casse dans les scénarios de l'éditeur. C'est en partie parce que l'interface utilisateur pour la personnalisation est encore en discussion, mais par défaut, TypeScript devrait maintenant fonctionner mieux avec le reste de votre outillage.

    Depuis notre RC, notre changement le plus notable est que TypeScript 5.0 spécifie maintenant une version minimale de Node.js de 12.20 dans notre package.json. Nous avons également publié un article sur la migration de TypeScript 5.0 vers les modules, et y avons ajouté un lien.

    Depuis l'annonce de TypeScript 5.0 Beta et RC, les chiffres spécifiques pour les benchmarks de vitesse et les deltas de taille de paquet ont également été ajustés, bien que le bruit ait été un facteur dans toutes les exécutions. Les noms de certains benchmarks ont également été ajustés pour plus de clarté, et les améliorations de la taille des paquets ont été déplacées dans un tableau séparé.

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

    En plus de permettre aux décorateurs d'être placés avant le mot-clé export, la proposition relative aux décorateurs offre désormais la possibilité de placer les décorateurs après l'exportation ou l'exportation par défaut. La seule exception est que le mélange des deux styles n'est pas autorisé.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //  allowed
    @register export default class Foo {
        // ...
    }
     
    //  also allowed
    export default @register class Bar {
        // ...
    }
     
    //  error - before *and* after is not allowed
    @before export @after class Bar {
        // ...
    }

    Bundler --moduleResolution

    TypeScript 4.7 a introduit les options node16 et nodenext pour ses paramètres --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 d'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 - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    // 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 le navigateur - cela rend les recherches de fichiers plus rapides et fonctionne mieux pour les serveurs de fichiers naïfs. Mais pour de nombreux développeurs utilisant des outils tels que les bundlers, les paramètres node16/nodenext étaient encombrants 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 tous ceux qui utilisaient un bundler.

    Mais d'un autre côté, 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 parfaitement comme dans CommonJS, mais lorsque l'on examine les conditions d'exportation d'un paquet, on préfère une condition import comme dans un fichier ECMAScript.

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

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 implémentent une stratégie de recherche hybride, la nouvelle option bundler devrait vous convenir.

    D'un autre côté, si vous écrivez une bibliothèque destinée à être publiée sur npm, l'utilisation de l'option bundler peut cacher des problèmes de compatibilité qui peuvent survenir pour vos utilisateurs qui n'utilisent pas de bundler. Dans ce cas, l'utilisation des options de résolution node16 ou nodenext est probablement la meilleure solution.

    Tri des importations non-sensibles à la casse dans les éditeurs

    Dans les éditeurs comme Visual Studio et VS Code, TypeScript permet d'organiser et de trier les importations et les exportations. Souvent, cependant, il peut y avoir différentes interprétations du moment où une liste est "triée".

    Par exemple, la liste d'importation suivante est-elle triée ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    import {
     
        Toggle,
     
        freeze,
        toBoolean,
    } from "./utils";

    La réponse pourrait être étonnamment "ça dépend". Si nous ne tenons pas compte de la sensibilité à la casse, il est clair que cette liste n'est pas triée. La lettre f précède à la fois t et T.

    Mais dans la plupart des langages de programmation, le tri se fait par défaut en comparant les valeurs d'octets des chaînes de caractères. La façon dont JavaScript compare les chaînes signifie que "Toggle" vient toujours avant "freeze" parce que, selon le codage des caractères ASCII, les majuscules viennent avant les minuscules. De ce point de vue, la liste des importations est donc triée.

    TypeScript considérait auparavant que la liste d'importation était triée parce qu'il effectuait un tri de base sensible à la casse. Cela pouvait être une source de frustration pour les développeurs qui préféraient un ordre insensible à la casse, ou qui utilisaient des outils comme ESLint qui exigent un ordre insensible à la casse par défaut.

    TypeScript détecte désormais la sensibilité à la casse par défaut. Cela signifie que TypeScript et des outils comme ESLint ne se "battent" généralement pas entre eux sur la meilleure façon de trier les importations.

    Notre équipe a également expérimenté d'autres stratégies de tri. Ces options pourront éventuellement être configurées par les éditeurs. Pour l'instant, elles sont encore instables et expérimentales, et vous pouvez les utiliser dans VS Code dès aujourd'hui en utilisant l'entrée typescript.unstable dans vos options JSON. Vous trouverez ci-dessous toutes les options que vous pouvez essayer (avec leurs valeurs 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
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    {
        "typescript.unstable": {
            // Should sorting be case-sensitive? Can be:
            // - true
            // - false
            // - "auto" (auto-detect)
            "organizeImportsIgnoreCase": "auto",
     
            // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be:
            // - "ordinal"
            // - "unicode"
            "organizeImportsCollation": "ordinal",
     
            // Under `"organizeImportsCollation": "unicode"`,
            // what is the current locale? Can be:
            // - [any other locale code]
            // - "auto" (use the editor's locale)
            "organizeImportsLocale": "en",
     
            // Under `"organizeImportsCollation": "unicode"`,
            // should upper-case letters or lower-case letters come first? Can be:
            // - false (locale-specific)
            // - "upper"
            // - "lower"
            "organizeImportsCaseFirst": false,
     
            // Under `"organizeImportsCollation": "unicode"`,
            // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be:
            // - true
            // - false
            "organizeImportsNumericCollation": true,
     
            // Under `"organizeImportsCollation": "unicode"`,
            // do letters with accent marks/diacritics get sorted distinctly
            // from their "base" letter (i.e. is é different from e)? Can be
            // - true
            // - false
            "organizeImportsAccentCollation": true
        },
        "javascript.unstable": {
            // same options valid here...
        },
    }

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

    TypeScript 5.0 contient de nombreux changements puissants dans notre structure de code, nos structures de données et nos implémentations algorithmiques. Cela signifie que l'ensemble de votre expérience devrait être plus rapide - non seulement en exécutant TypeScript, mais aussi en l'installant.

    Voici quelques gains intéressants en termes de vitesse et de taille que nous avons pu réaliser par rapport à TypeScript 4.9.

    Nom : typescript 5 img 1.PNG
Affichages : 42152
Taille : 29,6 Ko

    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 ce billet de blog.

    Tout d'abord, nous avons récemment migré TypeScript des espaces de noms vers les modules, ce qui nous permet d'exploiter des outils de construction modernes capables d'effectuer des optimisations telles que 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 nous ont permis de gagner environ 26,4 Mo sur les 63,8 Mo de TypeScript 4.9. Cela nous a également apporté une accélération notable grâce aux appels directs de fonctions.

    TypeScript a également ajouté plus d'uniformité aux types d'objets internes dans le compilateur, et a également allégé les données stockées sur certains de ces types d'objets. Cela a permis de réduire les opérations polymorphes, tout en équilibrant l'augmentation de l'utilisation de la mémoire résultant de l'uniformisation des formes d'objets.

    Nom : speed-5.0-stable-2.png
Affichages : 4265
Taille : 58,2 Ko

    Nous avons également procédé à 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 d'un rapport d'erreur, d'une émission de déclaration, d'une complétion de code, et plus encore, peut s'avérer assez coûteux. TypeScript met désormais en cache certaines machines couramment utilisées afin de les réutiliser dans le cadre de ces opérations.

    Un autre changement notable que nous avons effectué et qui a amélioré notre analyseur est l'utilisation de var pour contourner occasionnellement le coût de l'utilisation de let et const dans les fermetures. Cela a permis d'améliorer certaines de nos performances d'analyse.

    Dans l'ensemble, nous pensons que la plupart des bases de code devraient bénéficier d'améliorations de vitesse grâce à TypeScript 5.0, et nous avons toujours été en mesure de reproduire des gains de 10 à 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 !

    Nom : size-5.0-stable-1.png
Affichages : 4130
Taille : 12,9 Ko

    Exigences d'exécution

    TypeScript vise désormais ECMAScript 2018. Le package TypeScript a également défini un moteur minimum attendu de 12.20. Pour les utilisateurs de Node, cela signifie que TypeScript 5.0 nécessite une version minimale de Node.js 12.20 et plus.

    What's Next ?

    Sans vouloir nous avancer, TypeScript 5.1 est déjà en préparation, et tous nos plans sont déjà sur GitHub. Si vous êtes impatient, nous vous encourageons à essayer nos nightly builds de TypeScript ou l'extension JavaScript et TypeScript Nightly pour VS Code !

    Bien sûr, nous ne serons pas déçus si vous choisissez d'apprécier la nouvelle version stable de TypeScript. Nous espérons que TypeScript 5.0 rendra le codage plus rapide et plus amusant pour tout le monde.

    Joyeux hacking !

    - Daniel Rosenwasser et l'équipe TypeScript

    Source : Microsoft

    Et vous ?

    Quel est votre avis sur le sujet ?

    Quelles sont les fonctionnalités que vous aimeriez retrouver dans la version 5.1 de Typescript ?

    Voir aussi

    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

    Microsoft annonce la migration de TypeScript 5.0 vers les modules ECMAScript, pour le doter d'une base de code plus moderne, d'une interface améliorée et de performances plus accrues

    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
    Contribuez au club : corrections, suggestions, critiques, ... Contactez le service news et Rédigez des actualités

Discussions similaires

  1. Microsoft annonce la disponibilité de la version stable de TypeScript 2.7
    Par Stéphane le calme dans le forum TypeScript
    Réponses: 0
    Dernier message: 22/01/2018, 16h44
  2. Microsoft annonce la sortie de la version 1.0 de WinAppDriver
    Par Michael Guilloux dans le forum Outils
    Réponses: 2
    Dernier message: 27/10/2017, 15h00
  3. Réponses: 3
    Dernier message: 13/09/2009, 11h06
  4. Réponses: 0
    Dernier message: 12/09/2009, 13h21
  5. Réponses: 33
    Dernier message: 03/02/2009, 15h17

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo