Comment utiliser les modules ECMAScript avec Node.js – CloudSavvy IT
Un moyen standardisé de conditionner le code sous forme de modules réutilisables manquait à ECMAScript pendant la majeure partie de son histoire. En l’absence de solution intégrée, l’approche CommonJS (CJS) est devenue le standard de facto pour le développement de Node.js. Cela utilise require
et module.exports
consommer et fournir des morceaux de code :
// Import a module const fs = require("fs"); // Provide an export module.exports = () => "Hello World";
ES2015, également connu sous le nom d’ES6, a finalement introduit son propre système de modules intégrés. Les modules ECMAScript ou ES (ESM) reposent sur import
et export
syntaxe:
// Import a default export import fs from "fs"; // Provide a default export export default () => "Hello World"; // Import a named export import {helloWorld} from "./hello-world.js"; // Provide a named export export const helloWorld = () => "Hello World";
Node.js offre une prise en charge par défaut pour ESM depuis la v16. Dans les versions antérieures, vous deviez utiliser le --experimental-modules
drapeau pour activer la capacité. Alors que les modules ES sont désormais marqués comme stables et prêts pour une utilisation générale, la présence de deux mécanismes de chargement de module différents signifie qu’il est difficile de consommer les deux types de code dans un seul projet.
Dans cet article, nous verrons comment utiliser les modules ES avec Node et ce que vous pouvez faire pour maximiser l’interopérabilité avec les packages CommonJS.
Sommaire
Les bases
Les choses sont relativement simples si vous démarrez un nouveau projet et que vous souhaitez compter sur ESM. Comme Node.js offre désormais une prise en charge complète, vous pouvez diviser votre code en fichiers séparés et utiliser import
et export
instructions pour accéder à vos modules.
Malheureusement, vous devez faire des choix conscients dès le début. Par défaut, Node ne prend pas en charge import
et export
à l’intérieur des fichiers qui se terminent par le .js
extension. Vous pouvez soit suffixer vos fichiers comme .mjs
où ESM est toujours disponible, ou modifiez votre package.json
fichier à inclure "type": "module"
.
{ "name": "example-package", "type": "module", "dependencies": { "..." } }
Le choix de cette dernière voie est généralement plus pratique pour les projets qui utiliseront exclusivement ESM. Le nœud utilise le type
pour déterminer le système de module par défaut pour votre projet. Ce système de modules est toujours utilisé pour gérer .js
des dossiers. Lorsqu’il n’est pas défini manuellement, CJS est le système de module par défaut pour maximiser la compatibilité avec l’écosystème existant de code Node. Fichiers avec soit .cjs
ou .mjs
les extensions seront toujours traitées comme source au format CJS et ESM respectivement.
Importation d’un module CommonJS à partir d’ESM
Vous pouvez importer des modules CJS dans des fichiers ESM à l’aide d’un import
déclaration:
// cjs-module.cjs module.exports.helloWorld = () => console.log("Hello World"); // esm-module.mjs import component from "./cjs-module.cjs"; component.helloWorld();
component
sera résolu à la valeur du module CJS module.exports
. L’exemple ci-dessus montre comment vous pouvez accéder aux exportations nommées en tant que propriétés d’objet sur le nom de votre importation. Vous pouvez également accéder à des exportations spécifiques à l’aide de la syntaxe d’importation nommée ESM :
import {helloWorld} from "./cjs-module.cjs"; helloWorld();
Cela fonctionne au moyen d’un système d’analyse statique qui analyse les fichiers CJS pour déterminer les exportations qu’ils fournissent. Cette approche est nécessaire car CJS ne comprend pas le concept d’« exportation désignée ». Tous les modules CJS ont une exportation – les exportations « nommées » sont en réalité un objet avec plusieurs paires propriété-valeur.
En raison de la nature du processus d’analyse statique, il est possible que certains modèles de syntaxe rares ne soient pas détectés correctement. Si cela se produit, vous devrez plutôt accéder aux propriétés dont vous avez besoin via l’objet d’exportation par défaut.
Importation d’un module ESM à partir de CJS
Les choses se compliquent lorsque vous souhaitez utiliser un nouveau module ESM dans le code CJS existant. Vous ne pouvez pas écrire le import
déclaration dans les fichiers CJS. Cependant la dynamique import()
la syntaxe fonctionne et peut être associée à await
pour accéder aux modules relativement facilement :
// esm-module.mjs const helloWorld = () => console.log("Hello World"); export {helloWorld}; // esm-module-2.mjs export default = () => console.log("Hello World"); // cjs-module.cjs const loadHelloWorld = async () => { const {helloWorld} = await import("./esm-module.mjs"); return helloWorld; }; const helloWorld = await loadHelloWorld(); helloWorld(); const loadHelloWorld2 = async() => { const helloWorld2 = await import("./esm-module-2.mjs"); return helloWorld2; }; const helloWorld2 = await loadHelloWorld2(); helloWorld2();
Cette structure peut être utilisée pour accéder de manière asynchrone aux exportations par défaut et nommées de vos modules ESM.
Récupération du chemin du module actuel avec les modules ES
Les modules ES n’ont pas accès à toutes les variables globales Node.js familières disponibles dans les contextes CJS. outre require()
et module.exports
vous ne pourrez pas accéder au __dirname
ou __filename
constantes non plus. Ceux-ci sont couramment utilisés par les modules CJS qui ont besoin de connaître le chemin d’accès à leur propre fichier.
Les fichiers ESM peuvent lire import.meta.url
pour obtenir ces informations :
console.log(import.meta.url); // file:///home/demo/module.mjs
L’URL retournée donne le chemin absolu vers le fichier courant.
Pourquoi toutes les incompatibilités ?
Les différences entre CJS et ESM sont beaucoup plus profondes que de simples changements syntaxiques. CJS est un système synchrone ; lorsque vous require()
un module, Node le charge directement depuis le disque et exécute son contenu. L’ESM est asynchrone et divise les importations de scripts en plusieurs phases distinctes. Les importations sont analysées, chargées de manière asynchrone à partir de leur emplacement de stockage, puis exécutées une fois que toutes leurs propres importations ont été récupérées de la même manière.
ESM est conçu comme une solution moderne de chargement de modules avec de nombreuses applications. C’est pourquoi les modules ESM conviennent à une utilisation dans les navigateurs Web : ils sont asynchrones par conception, donc les réseaux lents ne sont pas un problème.
La nature asynchrone d’ESM est également responsable des limitations de son utilisation dans le code CJS. Les fichiers CJS ne prennent pas en charge le niveau supérieur await
donc tu ne peux pas utiliser import
à lui tout seul :
// this... import component from "component.mjs"; // ...can be seen as equivalent to this... const component = await import("component.mjs"); // ...but top-level "await" isn't available in CJS
Il faut donc utiliser la dynamique import()
structure à l’intérieur d’un async
une fonction.
Devriez-vous passer aux modules ES ?
La réponse simple est oui. Les modules ES sont le moyen standardisé d’importer et d’exporter du code JavaScript. CJS a donné à Node un système de modules lorsque le langage n’avait pas le sien. Maintenant qu’un est disponible, il est préférable pour la santé à long terme de la communauté d’adopter l’approche décrite par la norme ECMAScript.
ESM est également le système le plus puissant. Parce qu’il est asynchrone, vous obtenez des importations dynamiques, des importations à distance à partir d’URL et des performances améliorées dans certaines situations. Vous pouvez également réutiliser vos modules avec d’autres runtimes JavaScript, tels que du code livré directement aux navigateurs Web via <script type="module">
Balises HTML.
Néanmoins, la migration reste difficile pour de nombreux projets Node.js existants. CJS ne va pas disparaître de si tôt. En attendant, vous pouvez faciliter la transition en proposant à la fois des exportations CJS et ESM pour vos propres bibliothèques. Pour ce faire, le mieux est d’écrire un wrapper ESM fin autour de toutes les exportations CJS existantes :
import demoComponent from "../cjs-component-demo.js"; import exampleComponent from "../cjs-component-example.js"; export {demoComponent, exampleComponent};
Placez ce fichier dans un nouveau esm
sous-répertoire de votre projet. Ajouter un package.json
à ses côtés contenant le {"type": "module"}
champ.
Ensuite, mettez à jour le package.json
à la racine de votre projet pour inclure le contenu suivant :
{ "exports": { "require": "./cjs-index.js", "import": "./esm/esm-index.js" } }
Cela indique à Node de fournir votre point d’entrée CJS lorsque les utilisateurs de votre package require()
une exportation. Lorsque import
est utilisé, votre script wrapper ESM vous sera proposé à la place. Attention, cette fonctionnalité d’importation de cartes entraîne également un autre effet secondaire : les utilisateurs seront limités aux seules exportations fournies par votre fichier de point d’entrée. Chargement de fichiers spécifiques via des chemins intégrés au package (import demo from "example-package/path/to/file.js
) est désactivé lorsqu’une carte d’importation est présente.
Sommaire
Le paysage du module Node.js peut être pénible, en particulier lorsque vous avez besoin d’une compatibilité avec CJS et ESM. Bien que les choses se soient améliorées avec la stabilisation de la prise en charge ESM, vous devez toujours décider à l’avance quel système de module doit être le « par défaut » pour votre projet. Le chargement de code à partir de «l’autre» système est possible mais souvent inconfortable, en particulier lorsque CJS dépend d’ESM.
Les choses s’amélioreront progressivement au fur et à mesure que de plus en plus de projets majeurs passeront à l’adoption de l’ESM comme approche préférée. Pourtant, avec autant de code CJS existant, la migration en gros est irréaliste et il est probable que les deux mécanismes seront utilisés côte à côte – avec tous les compromis que cela implique – pendant encore plusieurs années à venir.
Si vous démarrez une nouvelle bibliothèque, assurez-vous de distribuer du code compatible ESM et essayez de l’utiliser comme système par défaut dans la mesure du possible. Cela devrait aider l’écosystème Node à converger vers ESM, en le réalignant sur les normes ES.