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

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

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

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

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

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

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

Je m'inscris !

Microsoft annonce la disponibilité de la version bêta de TypeScript 7.1 avec une API en avant-première, la prise en charge des mappeurs de contenu ainsi que de nouvelles fonctionnalités

Le , par Anthony

50PARTAGES

2  0 
La version bêta de TypeScript 7.1 est désormais disponible. Cette mise à jour ajoute la prise en charge des « content mappers » (mappeurs de contenu), un nouveau point de plugin qui permet à TypeScript de fournir des messages d'erreur et des plages d'erreur plus précis. Elle ajoute aussi la commande import source pour WebAssembly, les attributs d'importation dans les déclarations de modules ambiants (CSS, texte, octets), ainsi qu'une nouvelle API programmatique avec des instantanés (snapshots) incrémentiels. TypeScript 7.1 bêta intègre par ailleurs une multitude de nouvelles optimisations visant à réduire la consommation de mémoire et à accélérer les compilations.

TypeScript est un langage de programmation de haut niveau qui ajoute à JavaScript un typage statique avec des annotations de type facultatives. Il est conçu pour le développement d'applications volumineuses et est compilé en JavaScript. TypeScript peut être utilisé pour développer des applications JavaScript destinées à être exécutées aussi bien côté client que côté serveur (comme avec React.js, Node.js, Deno ou Bun). Il est développé par Microsoft en tant que logiciel libre et open source, distribué sous licence Apache 2.0. Avant la version 7.0 de TypeScript, le compilateur TypeScript était écrit en TypeScript et compilé en JavaScript. À partir de la version 7.0, il a été réécrit en Go.


Prise en charge des attributs d'importation dans les modules Ambient

Les attributs d'importation sont des métadonnées associées aux instructions d'importation. En général, ils servent à indiquer aux environnements d'exécution et aux outils de regroupement comment interpréter le contenu d'une importation, ainsi que la structure du module résultant. Par exemple,

Code : Sélectionner tout
import myData from "./data/someData.json" with { type: "json" };

Tout ce qui figure dans l’objet suivant le mot-clé with est un attribut d’importation. Les environnements tels que les navigateurs ne peuvent pas simplement déterminer comment traiter une importation en se basant sur des extensions de nom de fichier comme .json. Spécifier type: "json" indique à n’importe quel moteur d’exécution que someData.json doit être interprété comme un fichier JSON, analysé, puis fourni via une importation par défaut (dans notre exemple ci-dessus, il s’agit de myData).

L’attribut type est devenu la norme de facto permettant aux environnements d’exécution de personnaliser ce comportement. ECMAScript (JavaScript) propose une extension permettant au code de spécifier type: "text", ce qui renvoie le contenu de l’importation sous forme de chaîne de caractères.

Code : Sélectionner tout
1
2
3
import readme from "../README.md" with { type: "text" };
 
console.log(readme);

Il existe également une proposition visant à ce que type: "bytes" renvoie un Uint8Array d'octets bruts.

Code : Sélectionner tout
1
2
3
4
import { pngToPixels, boxBlur } from "./photoUtils.js";
 
import photo from "./myPhoto.png" with { type: "bytes" };
const blurryPhoto = boxBlur(pngToPixels(photo));

Depuis quelques années déjà, les navigateurs permettent également d'importer des fichiers CSS en tant que CSSStyleSheets avec type: "css".

Code : Sélectionner tout
1
2
3
import someTheme from "./themes/someCoolTheme.css" with { type: "css" };
 
document.adoptedStyleSheets.push(someTheme);

Pour faciliter l'importation de ces fichiers, on peut utiliser des déclarations de modules génériques dans TypeScript en recourant à un motif générique (par exemple, *.css).

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
// You can write this in a global `.d.ts` file with no import statements.
 
declare module "*.css" {
    const content: CSSStyleSheet;
    export default content;
}
 
declare module "*.txt" {
    const content: string;
    export default content;
}
 
declare module "*.md" {
    const content: string;
    export default content;
}
 
declare module "*.bin" {
    const content: Uint8Array;
    export default content;
}

C'est une solution acceptable, mais elle ne convient pas à tous les environnements, car elle ne tient absolument pas compte des attributs d'importation.

Vous vous demandez peut-être en quoi cela pose problème : à première vue, on pourrait penser que le seul inconvénient est que cela permet aux utilisateurs d'oublier accidentellement d'inclure un attribut d'importation.

Code : Sélectionner tout
1
2
3
4
5
//  oops, forgot an import attribute
import readme from "../README.md";
 
//  what we were supposed to write
import readme from "../README.md" with { type: "text" };

Mais cela reste un problème qu’il convient de résoudre. Un autre problème réside dans le fait que cela devient répétitif. Avec les attributs d’importation, le problème réside dans le fait que l’environnement d’exécution se soucie des attributs, et non de l’extension du fichier. Nous ne devrions pas avoir à redéfinir un modèle à la fois pour les fichiers .txt et .md si les deux devaient utiliser type: "text".

Mais un problème majeur réside dans le fait que les environnements d’exécution peuvent prendre en charge des attributs d’importation totalement différents et se comporter différemment selon qu’ils sont présents ou non. Par exemple, les outils de bundling peuvent utiliser une fonctionnalité de compilation appelée « CSS Modules » comme comportement par défaut lors de l’importation de fichiers .css, mais la spécification type: "css" pris en charge par les navigateurs dans l’instruction d’importation remplacera ce comportement et garantira que le fichier .css soit importé en tant qu’objet CSSStyleSheet.

C'est pourquoi TypeScript 7.1 prend désormais en charge les attributs d'importation dans les déclarations de modules ambiants. Vous pouvez désormais déclarer ce qui suit dans vos projets TypeScript ; vous obtiendrez une erreur si vous essayez d'importer un fichier sans les attributs d'importation requis, et le type correct sera déterminé en fonction de ces attributs.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare module "*" with { type: "css" } {
    const content: CSSStyleSheet;
    export default content;
}
 
declare module "*" with { type: "text" } {
    const content: string;
    export default content;
}
 
declare module "*" with { type: "bytes" } {
    const content: Uint8Array;
    export default content;
}

Notez que les importations correspondant à ces déclarations ne sont pas validées par rapport aux fichiers présents sur le disque. Comme c'est actuellement le cas lorsqu'une importation correspond à une déclaration de module ambiant, les échecs de recherche seront signalés lors de l'exécution ou pendant le processus de compilation d'un bundler.

Notez également que ces déclarations ne sont pas fournies dans le cadre des bibliothèques intégrées de TypeScript. Microsoft recommande aux bibliothèques de ne pas les inclure dans leurs paquets publiés ; les utilisateurs finaux devront les déclarer dans leurs propres projets si nécessaire. Cela offrira aux utilisateurs finaux une certaine flexibilité quant à la configuration de la portée globale (par exemple, lib.dom.d.ts par opposition à @types/node) et permettra d’éviter à l’avenir tout conflit avec les déclarations intégrées.

Importations en phase source

Les importations en phase source constituent une nouvelle fonctionnalité proposée dans ECMAScript. Cette fonctionnalité introduit un nouveau modificateur/mot-clé source qui suit le mot-clé import. Seules les importations par défaut peuvent être écrites avec import source.

Code : Sélectionner tout
import source SomeModule from "some-module";

L'idée derrière cette fonctionnalité est que, lorsque l'on utilise import source, on obtient un objet qui représente le module après son analyse syntaxique et son analyse par le moteur d'exécution, mais avant que ses importations aient été chargées (et donc avant qu'elles aient été validées ou liées à d'autres modules, et avant que le code contenu dans le module puisse s'exécuter). Cet objet est en soi quelque peu « inerte » : il s’agit d’une instance d’une classe appelée AbstractModuleSource, qui est destinée à être importée ou instanciée par la suite.

Pour les importations WebAssembly (wasm), cet objet sera une instance de WebAssembly.Module. L'intérêt de ces objets réside dans le fait qu'ils peuvent être transmis directement à la fonction WebAssembly.instantiate.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
import source SuperFastThing from "./super-fast-thing.wasm";
import { WASI } from "node:wasi";
 
const wasi = new WASI({ /*...*/ });
const fastThingInstance = await WebAssembly.instantiate(SuperFastThing, {
    wasi_snapshot_preview1: wasi.wasiImport
});
 
wasi.start(fastThingInstance);

Les importations de code source WebAssembly sont déjà prises en charge dans les versions récentes de Node.js.

Pour l'instant, l'importation du code source d'un fichier JavaScript n'offre pas beaucoup de possibilités. L'esprit de la proposition laisse entendre qu'à terme, il sera peut-être possible de passer directement le code source d'un module JavaScript dans un appel à la fonction import().

Code : Sélectionner tout
1
2
import source SomeModule from "./someModule.js";
const moduleInstance = await import(SomeModule);

Un autre aspect des importations de sources JavaScript réside dans le fait qu’avec le temps, nous pourrions disposer d’autres fonctionnalités nous permettant de personnaliser le comportement lié au chargement et à l’instanciation des modules, à l’instar de ce que nous avons fait ci-dessus avec WebAssembly.instantiate. Mais nous n’en sommes pas encore là, et en réalité, de nombreux environnements et outils ne prennent toujours pas en charge les importations de sources JavaScript à l’heure actuelle.

Les importations de source ne se limitent toutefois pas aux instructions d'importation. Vous pouvez également effectuer une importation dynamique de source via la fonction de méta-propriété import.source.

Code : Sélectionner tout
1
2
3
4
const SuperFastThing = await import.source("./super-fast-thing.wasm");
const fastThingInstance = await WebAssembly.instantiate(SuperFastThing, {
    // ...
});

Les importations de source conservent leur identité. Cela signifie que vous pouvez les utiliser dans des cartes et des ensembles, ou effectuer des comparaisons fiables avec celles-ci.

Code : Sélectionner tout
1
2
3
4
5
6
7
// Note that `WebAssembly.instantiate` always returns a distinct object.
import source source1 from "./plugin.wasm"
import source source2 from "./plugin.wasm"
const source3 = await import.source("./plugin.wasm");
 
console.log(source1 === source2); // true
console.log(source2 === source3); // true

Lorsque vous utilisez import source, chaque module importé est de type AbstractModuleSource, un nouveau type global défini dans lib.esnext.d.ts. Vous devez disposer d'un paramètre --module approprié (à savoir esnext, nodenext ou preserve) pour utiliser les importations de source, ainsi que de l'option esnext appropriée dans --lib ou --target.

API TypeScript 7.1

TypeScript 7.1 intègre une nouvelle API qui permet de créer des outils et des utilitaires puissants en s'appuyant sur les analyses syntaxiques et sémantiques de TypeScript.

Plutôt que de proposer une API monolithique, TypeScript 7.1 s'articule autour de plusieurs points d'entrée, chacun répondant à un objectif spécifique.


Voici un exemple illustrant comment effectuer une compilation simple à l'aide de l'API TypeScript 7.1, en passant explicitement les noms de fichiers et les options de compilation sur la ligne de commande. Contrairement à tsc, cet exemple ne recherche ni ne charge de fichier tsconfig.json et ne prend pas en charge l'option --project.

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
30
31
32
33
34
35
36
37
38
// Compile explicit filenames using command-line compiler options.
 
import { API, formatDiagnosticsWithColorAndContext, type Diagnostic } from "typescript/sync";
 
using api = new API();
const reportErrors = (diagnostics: readonly Diagnostic[]) => {
    console.log(formatDiagnosticsWithColorAndContext(diagnostics, api))
};
 
// Parse command-line filenames and compiler options, report any errors.
const commandLineArgs = process.argv.slice(2);
const parsedCommandLine = api.parseCommandLine(commandLineArgs);
if (parsedCommandLine.errors.length > 0) {
    reportErrors(parsedCommandLine.errors);
    process.exit();
}
 
// Create the program by resolving and parsing all files in the entry point.
using program = api.createProgram(parsedCommandLine.fileNames, parsedCommandLine.options);
 
// Collect and report any diagnostics from the program before emitting.
// You can customize whether any of these block reporting errors in subsequent passes if desired.
const diagnostics = [
    ...program.getGlobalDiagnostics(),
    ...program.getProgramDiagnostics(),
    ...program.getSyntacticDiagnostics(),
    ...program.getSemanticDiagnostics(),
];
if (diagnostics.length > 0) {
    reportErrors(diagnostics);
}
 
// Emit files - this respects the `noEmitOnError` option,
// so nothing will be emitted if an earlier step reported errors.
const emitResult = program.emit();
if (emitResult.diagnostics.length > 0) {
    reportErrors(emitResult.diagnostics);
}

Remarquez le mot-clé using, une fonctionnalité relativement récente en JavaScript, dans les déclarations ci-dessus.

Les instances API et les instances Program sont toutes deux des objets « Disposable ». Lorsque nous demandons leur création, TypeScript alloue de la mémoire et effectue un suivi pour s'assurer qu'elles persistent entre les environnements d'exécution Go et JavaScript ; cependant, lorsque nous n'en avons plus besoin, nous ne pouvons pas simplement laisser le ramasse-miettes s'en charger, sous peine de provoquer une fuite de mémoire côté Go. Nous devons donc effectuer l'une des opérations suivantes :

  • les supprimer manuellement à l’aide d’appels de méthode explicites (par exemple, api.close(), program.dispose() ou api[Symbol.dispose]()), ou
  • les supprimer automatiquement lorsqu’elles sortent de leur portée en les déclarant avec le mot-clé using.

Une fois qu'un programme a été supprimé, il n'est plus possible de l'utiliser.

Instantanés

Ci-dessus, une compilation « par lots » a été modélisée en ligne droite à l’aide de l’API ; cependant, de nombreux workflows avec TypeScript s’articulent autour des changements susceptibles de se produire au fil du temps. Plutôt que de reconstruire l’intégralité de l’environnement à chaque fois que cela est nécessaire, TypeScript est capable de modéliser les transitions d’état à mesure que de nouveaux fichiers sont mis à jour, que ce soit sur le disque ou au fur et à mesure que l’utilisateur tape du code.

Le concept d'« instantané » (snapshot) est au cœur de l'API. Un instantané correspond à une version de l'ensemble des projets et programmes à un moment donné. Nous pouvons à tout moment créer un nouvel instantané contenant toutes les informations dont nous avons besoin à l'aide de la fonction api.createSnapshot, mais nous pouvons également générer un nouvel instantané à partir d'un précédent grâce à la fonction snapshot.update(/*...*/). Cela permet souvent de gagner beaucoup de temps, tant en évitant d'envoyer l'intégralité des informations d'origine qu'en évitant au compilateur de reconstruire l'environnement.

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { ScriptTarget } from "typescript/ast";
import { createFileSystemLayer, createFileSystemWithLib } from "typescript/fs";
import { API } from "typescript/sync";
import { resolve } from "node:path";
 
const fileName = resolve("snapshot-example.ts");
 
using api = new API();
using before = api.createSnapshot({
    fileSystem: createFileSystemWithLib([
        [fileName, 'export const answer: number = "forty-two";'],
    ]),
    createPrograms: [
        {
            rootFiles: [fileName],
            compilerOptions: {
                strict: true,
                noEmit: true,
                target: ScriptTarget.ES2022,
            },
        },
    ],
});
 
using beforeProgram = before.operation.createdPrograms[0];
console.log("before:", beforeProgram.getSemanticDiagnostics().length);
 
using after = before.update({
    fileSystem: createFileSystemLayer([
        [fileName, "export const answer: number = 42;"],
    ]),
    ensurePrograms: [beforeProgram.id],
});
 
using afterProgram = after.getProgram(beforeProgram.id);
if (!afterProgram) {
    throw new Error("The updated program was not found.");
}
 
console.log("after:", afterProgram.getSemanticDiagnostics().length);
console.log("before again:", beforeProgram.getSemanticDiagnostics().length);
 
// Outputs:
//  before: 1
//  after: 0
//  before again: 1

Il convient de noter ici que nous avons pu partir d’un instantané d’origine nommé before et en dériver un nouvel instantané après avoir effectué une modification, nommé after. Les instantanés peuvent contenir des objets Program (et des Projet) dont les identifiants persistent d’un instantané à l’autre. Nous avons utilisé ensurePrograms pour spécifier les Program dont nous souhaitons observer les mises à jour dans le nouvel instantané (le comportement par défaut actuel est plus « paresseux » et permet d’enchaîner les appels à update() sans forcer la mise à jour des Program).

Notez que même après avoir appelé la méthode update, nous pouvons toujours utiliser les informations issues de l’instantané précédent. Par exemple, nous pouvons toujours demander des diagnostics à la fois sur beforeProgram et afterProgram. Cela dit, Microsoft recommande de ne pas mélanger des objets dérivés du programme, du projet ou du vérificateur d’un instantané donné.

Les instantanés sont des Disposable, tout comme les autres types mentionnés ci-dessus ; nous devons donc soit les détruire manuellement à l’aide de dispose(), soit créer une liaison locale pour ceux-ci à l’aide de déclarations using. Une fois qu’un Snapshot a été détruit, nous ne pouvons plus l’utiliser.

API du système de fichiers

Par défaut, les API de TypeScript opèrent sur le système de fichiers de l'hôte. Cela signifie qu'elles accèdent aux fichiers « sur le disque », sauf indication contraire de la part de Microsoft.


Dans notre exemple précédent, nous avions créé un système de fichiers virtuel (partiel) à l’aide de createFileSystemWithLib et n’avions spécifié dans notre programme que le fichier qui nous intéressait. Lorsque nous avons voulu effectuer une mise à jour, nous avons simplement pu utiliser createFileSystemLayer uniquement pour les fichiers modifiés, tandis que les autres fichiers (tels que lib.d.ts et tout autre fichier) continuaient d’être résolus via la couche de système de fichiers de base que nous avions créée dans notre instantané initial.

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
30
31
// ...
 
using snapshot = api.createSnapshot({
    fileSystem: createFileSystemWithLib([
        [
            mainFile,
            [
                'import { greeting } from "./generated.js";',
                "export const message: string = greeting;",
            ].join("\n"),
        ],
        [generatedFile, 'export const greeting = "Hello from memory!";'],
    ]),
    createPrograms: [
        {
            rootFiles: [mainFile],
            compilerOptions: {
                strict: true,
                module: ModuleKind.ESNext,
                moduleResolution: ModuleResolutionKind.Bundler,
                target: ScriptTarget.ES2022,
            },
        },
    ],
});
 
using program = snapshot.operation.createdPrograms[0];
 
// ...
// Get emit without writing files to any file system.
const emitResult = program.emitToString();

Il existe plusieurs moyens de contrôler la manière dont les fichiers sont surveillés à travers ces couches. Par exemple, l’option removedPaths permet de bloquer les requêtes provenant du système sous-jacent.

Si vous avez besoin d’assurer un certain niveau de surveillance des fichiers, cela doit actuellement être mis en place séparément. Les créations, suppressions et modifications de fichiers doivent être signalées via l’option fileNotifications de la méthode snapshot.update(). Toute modification qui ne se produit pas dans le système de fichiers hôte doit être signalée via une autre couche du système de fichiers.

Mappeurs de contenu

TypeScript dispose depuis longtemps de puissants outils de vérification et de...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

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