Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Node.js ES Modules: Import and Export

Recommended resources

Bonjour ! J'espère que vous êtes prêt à organiser votre code de manière professionnelle.

Introduction

Dans la leçon précédente, nous avons maîtrisé async/await pour écrire du code asynchrone lisible et séquentiel. Vous savez maintenant créer des fonctions propres qui gèrent des opérations complexes sans vous perdre dans des chaînes de .then().

La question naturelle qui suit est : comment organiser ces fonctions et ce code dans une structure de projet maintenable ? Si on met tout dans un seul fichier, il deviendra rapidement ingérable. C'est là que les modules entrent en jeu.

Avec votre expérience de Laravel, vous êtes familier du concept d'inclure des fichiers en PHP avec require ou include. JavaScript possède son propre système de modules standardisé, connu sous le nom de Modules ECMAScript (ESM). Ce système utilise les mots-clés import et export pour partager du code entre les fichiers.

L'objectif de cette leçon est de vous rendre capable de créer et d'utiliser des modules ES avec import et export en Node.js. Maîtriser les modules est une compétence fondamentale pour construire n'importe quelle application backend, car elle est à la base de l'organisation du code, de la réutilisabilité et de la collaboration.


1. Pourquoi utiliser des modules ?

Avant de plonger dans la syntaxe, il est essentiel de comprendre pourquoi les modules sont si importants. À l'origine, le code JavaScript s'exécutait dans un "scope global", ce qui entraînait des conflits de noms de variables et rendait les gros projets difficiles à maintenir. Les modules résolvent ce problème.

La vidéo suivante explique très bien la motivation derrière l'introduction des modules en JavaScript.

JavaScript Modules Crash Course

Regardons cette première partie du 'JavaScript Modules Crash Course' de freeCodeCamp.org pour comprendre le problème que les modules viennent résoudre.

Regardez la vidéo de 01:35 à 05:03. Concentrez-vous sur la transition d'un unique fichier JavaScript gigantesque vers une structure modulaire, plus propre et réutilisable.

En résumé, les modules nous apportent trois avantages majeurs :

  • Organisation : Chaque fichier a une responsabilité unique, ce qui rend le code plus facile à trouver et à comprendre.
  • Encapsulation : Par défaut, tout ce qui est dans un module est privé à ce module. Vous devez explicitement exporter ce que vous voulez rendre public. Cela évite de polluer le scope global et prévient les conflits.
  • Réutilisabilité : Une fonction ou une classe exportée peut être importée et utilisée dans n'importe quelle autre partie de votre application.

2. La syntaxe export et import

Maintenant que nous savons pourquoi les modules sont utiles, voyons comment les utiliser avec les deux mots-clés principaux : export et import. Il existe deux types principaux d'exports : les exports nommés et l'export par défaut.

Exports nommés (Named Exports)

Les exports nommés permettent d'exporter plusieurs valeurs depuis un même module. Lors de l'import, vous devez utiliser le nom exact de la variable ou fonction exportée.

// 📁 utils.js

// On peut exporter chaque élément individuellement
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

// Ou on peut tout exporter à la fin dans un seul objet
function subtract(a, b) {
  return a - b;
}
const version = '1.0';

export { subtract, version };

Pour les importer, on utilise la syntaxe de déstructuration :

// 📁 main.js
import { add, PI, version } from './utils.js';

console.log(`Version: ${version}`); // "Version: 1.0"
const sum = add(5, 3);
console.log(`5 + 3 = ${sum}`); // "5 + 3 = 8"

Export par défaut (Default Export)

Un module peut avoir un seul et unique export par défaut. C'est utile lorsque le module a une fonction ou une classe principale. L'avantage est que vous pouvez lui donner le nom que vous voulez lors de l'import.

// 📁 User.js
export default class User {
  constructor(name) {
    this.name = name;
  }
}

Lors de l'import, on n'utilise pas les accolades :

// 📁 main.js
import MyUserClass from './User.js'; // On peut le nommer comme on veut

const user = new MyUserClass('Alice');
console.log(user.name); // "Alice"

L'article suivant fournit des exemples clairs de ces deux syntaxes.

CommonJS vs ES Modules: A Deep Dive into Node.js Module Systems

Pour bien ancrer ces concepts, lisez la section sur la syntaxe ESM de l'article 'CommonJS vs ES Modules' sur CoderCrafter. Il illustre parfaitement les exports nommés et par défaut.

Lisez la section 'ES Modules (ESM) in Action'. Elle vous montrera concrètement comment écrire le code pour les exports (nommés et par défaut) et les imports correspondants.

Quand utiliser l'un ou l'autre ?

  • Exports nommés : Idéal pour les fichiers "utilitaires" qui exportent plusieurs fonctions indépendantes (ex: stringUtils.js, math.js).
  • Export par défaut : Parfait pour les modules qui représentent une seule "chose" : une classe, un composant principal, une configuration.

Un module peut combiner un export par défaut et plusieurs exports nommés.


3. Utiliser les modules ES en Node.js

Jusqu'à présent, la syntaxe est standard à tout environnement JavaScript. Cependant, pour faire fonctionner les modules en Node.js, il y a quelques spécificités cruciales à connaître. Historiquement, Node.js utilisait un autre système appelé CommonJS (require() et module.exports). Bien que CommonJS soit toujours supporté, les ESM sont le standard moderne.

Alors, comment dire à Node.js d'interpréter nos fichiers .js comme des modules ES ?

La vidéo suivante explique de manière très concise les étapes à suivre.

EcmaScript Modules in NodeJS // Upgrade your Projects to ES Modules

Cette courte vidéo de Basarat est parfaite pour comprendre comment activer et utiliser les modules ES dans un projet Node.js. Elle met en lumière les pièges les plus courants.

Regardez la vidéo en entier (elle dure moins de 5 minutes). Portez une attention particulière à deux points critiques : La modification nécessaire dans package.json ("type": "module"). L'obligation d'inclure les extensions de fichier (ex: .js) dans les chemins d'import relatifs.

Pour résumer, voici les deux étapes clés pour utiliser les ESM dans un projet Node.js :

Étape 1 : Activer le mode ESM dans package.json

La méthode la plus simple et la plus courante est d'ajouter une ligne à votre fichier package.json :

{
  "name": "mon-projet-node",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",  // <--- CETTE LIGNE EST CRUCIALE
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC"
}
Setting 'type': 'module' in package.json for ES Modules
En ajoutant `"type": "module"` à votre `package.json`, vous indiquez à Node.js que tous les fichiers se terminant par `.js` dans votre projet doivent être traités comme des modules ES.

Étape 2 : Utiliser les extensions de fichier dans les imports

C'est un point qui piège souvent les développeurs. Contrairement à CommonJS ou à de nombreux outils de build front-end, lorsque vous utilisez des modules ES nativement en Node.js, vous devez spécifier l'extension complète du fichier dans les imports relatifs.

❌ Incorrect :
import { add } from './utils';

✅ Correct :
import { add } from './utils.js';

Oublier le .js est l'une des causes les plus fréquentes de l'erreur Error [ERR_MODULE_NOT_FOUND].


4. Quelques "Gotchas" et bonnes pratiques

En migrant vers les modules ES, vous rencontrerez quelques différences par rapport à l'ancien système CommonJS. Voici les plus importantes à connaître.

  • L'erreur Cannot use import statement outside a module : Si vous voyez cette erreur, cela signifie que Node.js essaie d'exécuter un fichier contenant import/export comme un module CommonJS. La solution est presque toujours d'ajouter "type": "module" à votre package.json.

  • __dirname et __filename n'existent pas : En CommonJS, ces variables globales donnaient le chemin du dossier et du fichier courant. En ESM, elles n'existent pas. L'équivalent moderne est un peu plus verbeux mais plus standard :

    import { fileURLToPath } from 'url';
    import { dirname } from 'path';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    
    console.log('Chemin du fichier:', __filename);
    console.log('Chemin du dossier:', __dirname);
    

    import.meta.url est l'objet qui contient les métadonnées sur le module courant.

  • Interopérabilité avec CommonJS : Vous travaillerez souvent avec des dépendances (packages npm) qui sont encore en CommonJS. Heureusement, vous pouvez les importer sans problème dans un module ES. Node.js s'occupe de la compatibilité.

    // lodash est un package CommonJS
    import _ from 'lodash'; // Cela fonctionne parfaitement
    
    console.log(_.capitalize('BONJOUR'));
    

Conclusion

Félicitations ! Vous avez maintenant les clés pour structurer n'importe quelle application Node.js de manière propre et moderne. L'organisation du code n'est plus un mystère.

Points clés à retenir :

  • Les modules permettent d'organiser, d'encapsuler et de réutiliser le code via les mots-clés import et export.
  • Il existe deux types d'exports : les exports nommés (export const ...) et l'export par défaut (export default ...), chacun avec ses cas d'usage.
  • Pour activer les modules ES dans un projet Node.js, ajoutez "type": "module" à votre package.json.
  • N'oubliez jamais de spécifier l'extension de fichier (ex: .js) dans les chemins d'import relatifs.
  • Les anciennes variables globales comme __dirname sont remplacées par des alternatives basées sur import.meta.url.

Dans la prochaine leçon, nous passerons à la vitesse supérieure en apprenant à utiliser npm pour initialiser un projet et gérer les dépendances. Cela nous permettra d'intégrer facilement des bibliothèques tierces (qui sont elles-mêmes des modules !) dans nos projets.