
et propose un contrôle de la détection de module
Prise en charge du module ECMAScript dans Node.js
Depuis quelques années, Node.js travaille pour supporter les modules ECMAScript (ESM). Cela a été une fonctionnalité très difficile à implémenter, car l'écosystème Node.js est construit sur un système de modules différent appelé CommonJS (CJS). L'interopérabilité entre les deux apporte de grands défis. Cependant, la prise en charge d'ESM dans Node.js a été largement implémentée dans Node.js 12 et versions ultérieures. Autour de TypeScript 4.5, Microsoft a déployé une prise en charge en nightly uniquement pour ESM dans Node.js afin d'obtenir des commentaires des utilisateurs et de permettre aux auteurs de bibliothèques de se préparer à une prise en charge plus large.
TypeScript 4.7 ajoute cette fonctionnalité avec deux nouveaux paramètres de module : node12 et nodenext.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 | { "compilerOptions": { "module": "nodenext", } } |
Ces nouveaux modes apportent quelques fonctionnalités de haut niveau que nous allons explorer ici.
type dans package.json et les nouvelles extensions
Node.js prend en charge un nouveau paramètre dans package.json appelé type. "type" peut être défini sur "module" ou "commonjs".
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | { "name": "my-package", "type": "module", "//": "...", "dependencies": { } } |
Ce paramètre contrôle si les fichiers .js sont interprétés comme des modules ES ou des modules CommonJS, et par défaut sur CommonJS lorsqu'il n'est pas défini. Lorsqu'un fichier est considéré comme un module ES, quelques règles différentes entrent en jeu par rapport à CommonJS*:
- Les instructions d'importation/exportation (et l'attente de niveau supérieur dans nodenext) peuvent être utilisées.
- Les chemins d'importation relatifs nécessitent des extensions complètes (nous devons écrire import "./foo.js" au lieu de import "./foo").
- Les importations peuvent être résolues différemment des dépendances dans node_modules.
- Certaines valeurs de type global telles que require() et process ne peuvent pas être utilisées directement.
- Les modules CommonJS sont importés selon certaines règles spéciales.
Pour superposer le fonctionnement de TypeScript dans ce système, les fichiers .ts et .tsx fonctionnent désormais de la même manière. Lorsque TypeScript trouve un fichier .ts, .tsx, .js ou .jsx, il recherche un package.json pour voir si ce fichier est un module ES et l'utilise pour déterminer*:
- comment trouver d'autres modules que ce fichier importe
- et comment transformer ce fichier s'il produit des sorties
Lorsqu'un fichier .ts est compilé en tant que module ES, les instructions d'import/export ECMAScript sont laissées seules dans la sortie .js ; lorsqu'il est compilé en tant que module CommonJS, il produira la même sortie que vous obtenez aujourd'hui sous --module commonjs.
Cela signifie également que les chemins se résolvent différemment entre les fichiers .ts qui sont des modules ES et ceux qui sont des modules CJS. Par exemple, supposons que vous ayez le code suivant aujourd'hui*:
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | // ./foo.ts export function helper() { // ... } // ./bar.ts import { helper } from "./foo"; // only works in CJS helper(); |
Ce code fonctionne dans les modules CommonJS, mais échouera dans les modules ES car les chemins d'importation relatifs doivent utiliser des extensions. En conséquence, il devra être réécrit pour utiliser l'extension de la sortie de foot.ts - donc bar.ts devra plutôt importer depuis ./foo.js.
Code TypeScript : | Sélectionner tout |
1 2 3 4 | // ./bar.ts import { helper } from "./foo.js"; // works in ESM & CJS helper(); |
Cela peut sembler un peu lourd au début, mais les outils TypeScript tels que les importations automatiques et la saisie semi-automatique le feront généralement pour vous.
Une autre chose à mentionner est le fait que cela s'applique également aux fichiers .d.ts. Lorsque TypeScript trouve un fichier .d.ts dans le package, il est interprété en fonction du package contenant.
Nouvelles extensions de fichiers
Le champ type dans package.json est agréable car il nous permet de continuer à utiliser les extensions de fichier .ts et .js, ce qui peut être pratique*; cependant, vous devrez parfois écrire un fichier qui diffère du type spécifié. Vous pourriez aussi préférer être toujours explicite.
Node.js prend en charge deux extensions pour vous aider*: .mjs et .cjs. Les fichiers .mjs sont toujours des modules ES et les fichiers .cjs sont toujours des modules CommonJS, et il n'y a aucun moyen de les remplacer.
À son tour, TypeScript prend en charge deux nouvelles extensions de fichier source*: .mts et .cts. Lorsque TypeScript les émet vers des fichiers JavaScript, il les émet vers .mjs et .cjs respectivement.
De plus, TypeScript prend également en charge deux nouvelles extensions de fichier de déclaration : .d.mts et .d.cts. Lorsque TypeScript génère des fichiers de déclaration pour .mts et .cts, leurs extensions correspondantes seront .d.mts et .d.cts.
L'utilisation de ces extensions est entièrement facultative, mais sera souvent utile même si vous choisissez de ne pas les utiliser dans le cadre de votre flux de travail principal.
Interopérabilité CommonJS
Node.js permet aux modules ES d'importer des modules CommonJS comme s'il s'agissait de modules ES avec une exportation par défaut.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | // ./foo.cts export function helper() { console.log("hello world!"); } // ./bar.mts import foo from "./foo.cjs"; // prints "hello world!" foo.helper(); |
Dans certains cas, Node.js synthétise également des exportations nommées à partir de modules CommonJS, ce qui peut être plus pratique. Dans ces cas, les modules ES peuvent utiliser une importation "de style espace de noms" (c'est-à-dire import * as foo from "...")) ou des importations nommées (c'est-à-dire import { helper } from "...")
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | // ./foo.cts export function helper() { console.log("hello world!"); } // ./bar.mts import { helper } from "./foo.cjs"; // prints "hello world!" helper(); |
Il n'y a pas toujours un moyen pour TypeScript de savoir si ces importations nommées seront synthétisées, mais TypeScript se trompera en étant permissif et utilisera certaines heuristiques lors de l'importation à partir d'un fichier qui est définitivement un module CommonJS.
Une note spécifique à TypeScript concernant l'interopérabilité est la syntaxe suivante*:
Code TypeScript : | Sélectionner tout |
import foo = require("foo");
Dans un module CommonJS, cela se résume à un appel require() , et dans un module ES, cela importe createRequire pour obtenir la même chose. Cela rendra le code moins portable sur les runtimes comme le navigateur (qui ne prend pas en charge require()), mais sera souvent utile pour l'interopérabilité. À son tour, vous pouvez écrire l'exemple ci-dessus en utilisant cette syntaxe comme suit*:
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | // ./foo.cts export function helper() { console.log("hello world!"); } // ./bar.mts import foo = require("./foo.cjs"); foo.helper() |
Enfin, il convient de noter que le seul moyen d'importer des fichiers ESM à partir d'un module CJS consiste à utiliser des appels dynamiques import(). Cela peut présenter des défis, mais c'est le comportement dans Node.js aujourd'hui.
package.json Exportations, importations et auto-référencement
Node.js prend en charge un nouveau champ pour définir les points d'entrée dans package.json appelé "exports". Ce champ est une alternative plus puissante à la définition de "main" dans package.json, et peut contrôler quelles parties de votre package sont exposées aux consommateurs.
Voici un package.json qui prend en charge des points d'entrée séparés pour CommonJS et ESM*:
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // package.json { "name": "my-package", "type": "module", "exports": { ".": { // Entry-point for `import "my-package"` in ESM "import": "./esm/index.js", // Entry-point for `require("my-package") in CJS "require": "./commonjs/index.cjs", }, }, // CJS fall-back for older versions of Node.js "main": "./commonjs/index.cjs", } |
Avec la prise en charge de Node d'origine de TypeScript, il rechercherait un champ "main", puis rechercherait les fichiers de déclaration correspondant à cette entrée. Par exemple, si "main" pointe vers ./lib/index.js, TypeScript recherchera un fichier appelé ./lib/index.d.ts. Un auteur de package pourrait remplacer cela en spécifiant un champ séparé appelé "types" (par exemple "types": "./types/index.d.ts").
Le nouveau support fonctionne de la même manière avec les conditions d'importation. Par défaut, TypeScript superpose les mêmes règles avec les...
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.