Contactez-nous

Modules CommonJS : require() et module.exports

Maitrisez le systeme de modules historique de Node.js, CommonJS. Apprenez a importer avec require() et a exporter avec module.exports et exports.

CommonJS : Le système de modules historique de Node.js

Lorsque Node.js a été créé, JavaScript ne disposait pas d'un système de modules standardisé intégré au langage. Pour pallier ce manque et permettre l'organisation du code côté serveur, Node.js a adopté et popularisé la spécification CommonJS (CJS). Pendant de nombreuses années, CommonJS a été le système de modules par défaut et le plus utilisé dans l'écosystème Node.js.

Même avec l'arrivée et le support croissant des ES Modules (ESM), comprendre CommonJS reste essentiel car une immense quantité de code existant (y compris de nombreux modules sur npm et même certaines parties de Node.js lui-même) est écrite en utilisant ce système. Vous serez forcément amené à lire, utiliser, et parfois écrire du code CommonJS.

La philosophie de CommonJS est relativement simple et repose principalement sur deux éléments clés : la fonction `require()` pour importer des modules et l'objet `module.exports` (ou son raccourci `exports`) pour définir ce qu'un module rend accessible aux autres.

Importer des modules avec `require()`

La fonction `require()` est la pierre angulaire de l'importation dans CommonJS. Elle est utilisée pour charger et exécuter le code d'un autre module et pour récupérer les éléments que ce module a exportés.

Syntaxe :

const monModule = require('chemin/vers/le/module');
  • `'chemin/vers/le/module'` : C'est l'identifiant du module à importer. Il peut prendre plusieurs formes (nous verrons la résolution de modules plus tard) :
    • Module natif (Core Module) : Le nom du module intégré à Node.js (ex: `'fs'`, `'http'`, `'path'`).
    • Module de `node_modules` : Le nom d'un paquet installé via npm/yarn (ex: `'express'`, `'lodash'`). Node.js le cherchera dans le dossier `node_modules` le plus proche et ses parents.
    • Chemin relatif ou absolu vers un fichier : Un chemin commençant par `./` (même dossier), `../` (dossier parent), ou `/` (racine du système) pour importer vos propres fichiers/modules (ex: `'./utils'`, `'../config/database.js'`). L'extension `.js` est souvent implicite, mais il est parfois plus sûr de l'inclure.
  • Valeur de retour : La fonction `require()` retourne la valeur qui a été assignée à `module.exports` par le module importé.

Comportement synchrone et mise en cache : Un aspect important de `require()` dans CommonJS est qu'il est synchrone. Lorsque Node.js rencontre un appel `require()`, il :

  1. Résout le chemin pour trouver le fichier du module.
  2. Charge le contenu du fichier.
  3. Exécute le code du module (s'il n'a pas déjà été chargé).
  4. Retourne la valeur de `module.exports`.

Cette exécution est bloquante. De plus, Node.js met en cache les modules après leur première importation. Les appels `require()` ultérieurs pour le même module retourneront directement la version mise en cache de `module.exports` sans ré-exécuter le code du module. Cela garantit que le code d'initialisation d'un module n'est exécuté qu'une seule fois.

// Importer un module natif
const path = require('path');

// Importer un module installé
const _ = require('lodash'); // Supposant que lodash est installé

// Importer un module local
const mesUtilitaires = require('./libs/utilitaires'); 

console.log(typeof path.join); // 'function'
console.log(_.VERSION); // Affiche la version de lodash
mesUtilitaires.fonctionA(); // Appel d'une fonction exportée par notre module

Exporter avec `module.exports`

Pour qu'un module puisse être utile via `require()`, il doit définir ce qu'il exporte. Node.js fournit à chaque module une variable spéciale nommée `module`. Cet objet `module` possède une propriété `exports`. C'est la valeur assignée à `module.exports` qui sera retournée par `require()` lorsqu'un autre module importe celui-ci.

Par défaut, `module.exports` est un objet vide (`{}`). Vous pouvez l'utiliser de plusieurs manières :

1. Exporter un seul élément (Objet, Fonction, Classe, etc.) : C'est le cas le plus courant lorsque votre module a une fonction principale ou représente une classe.

// Fichier: mon-calculateur.js
function addition(a, b) {
  return a + b;
}

// Exporte directement la fonction addition
module.exports = addition;

// --- Autre fichier ---
const additionner = require('./mon-calculateur');
console.log(additionner(5, 3)); // 8

// --- Exemple avec une classe ---
// Fichier: Utilisateur.js
class Utilisateur {
  constructor(nom) { this.nom = nom; }
  saluer() { console.log(`Bonjour ${this.nom}`); }
}
// Exporte la classe
module.exports = Utilisateur;

// --- Autre fichier ---
const User = require('./Utilisateur');
const user1 = new User('Alice');
user1.saluer(); // Bonjour Alice

2. Exporter plusieurs éléments via un objet : Si vous voulez exporter plusieurs fonctions, variables ou classes, vous pouvez les assigner comme propriétés de l'objet `module.exports`.

// Fichier: operations.js
function multiplier(a, b) {
  return a * b;
}

function diviser(a, b) {
  if (b === 0) throw new Error("Division par zéro !");
  return a / b;
}

const PI = 3.14159;

// Ajoute les fonctions et la constante comme propriétés de module.exports
module.exports.multiplier = multiplier;
module.exports.diviser = diviser;
module.exports.CONST_PI = PI; // On peut renommer à l'export

// --- Autre fichier ---
const ops = require('./operations');

console.log(ops.multiplier(4, 5)); // 20
console.log(ops.CONST_PI);        // 3.14159

Alternative courante pour exporter plusieurs éléments : Au lieu d'ajouter des propriétés une par une, vous pouvez directement assigner un nouvel objet littéral à `module.exports`.

// Fichier: operations_alt.js
const multiplier = (a, b) => a * b;
const diviser = (a, b) => (b === 0 ? Infinity : a / b);
const PI = 3.14;

// Assigne un nouvel objet à module.exports
module.exports = {
  multiplier: multiplier, // clé: valeur
  diviser: diviser,
  PI_VAL: PI,
  // Syntaxe concise ES6 si clé et nom de variable sont identiques
  ajouter(a, b) { return a + b; } 
};

// --- Autre fichier ---
const opsAlt = require('./operations_alt');
console.log(opsAlt.ajouter(1, 2)); // 3
console.log(opsAlt.PI_VAL);      // 3.14

Important : Si vous assignez un nouvel objet à `module.exports`, toute modification ultérieure de l'objet `exports` (voir section suivante) sera ignorée.

Le raccourci `exports` et ses pièges

Node.js fournit également une variable `exports` dans chaque module. Au début de l'exécution d'un module, cette variable `exports` est simplement une référence qui pointe vers le même objet que `module.exports` (c'est-à-dire `exports = module.exports`).

Cela permet une syntaxe légèrement plus courte pour ajouter des propriétés à l'objet exporté :

// Fichier: raccourci.js

// Equivaut à module.exports.direBonjour = ...
exports.direBonjour = function(nom) {
  console.log(`Bonjour, ${nom} depuis raccourci.js`);
};

// Equivaut à module.exports.CONSTANTE = ...
exports.CONSTANTE = 123;

// --- Autre fichier ---
const raccourci = require('./raccourci');
raccourci.direBonjour("Monde"); // Bonjour, Monde depuis raccourci.js
console.log(raccourci.CONSTANTE);  // 123

Le Piège : Le problème survient si vous essayez de réassigner directement la variable `exports` pour exporter un seul élément (comme une fonction ou une classe). Si vous faites `exports = maFonction;`, vous changez simplement ce vers quoi la variable locale `exports` pointe. Elle ne pointe plus vers l'objet `module.exports` original. Or, c'est toujours la valeur de `module.exports` que `require()` retourne.

// Fichier: piege.js

function maFonctionInterne() {
  console.log("Fonction interne");
}

// !! CECI NE FONCTIONNE PAS COMME ATTENDU !!
// 'exports' pointe maintenant vers maFonctionInterne,
// mais 'module.exports' pointe toujours vers l'objet initial (vide).
exports = maFonctionInterne; 

// Pour que cela fonctionne, il faut assigner à module.exports:
// module.exports = maFonctionInterne;

// --- Autre fichier ---
const resultatPiege = require('./piege');
console.log(resultatPiege); // Affiche {} (l'objet vide initial de module.exports)
// resultatPiege(); // TypeError: resultatPiege is not a function

Règle générale :

  • Pour exporter un seul élément (fonction, classe, objet principal), assignez-le directement à `module.exports`.
  • Pour exporter plusieurs éléments nommés, vous pouvez soit ajouter des propriétés à `module.exports` (ex: `module.exports.maFonction = ...`), soit ajouter des propriétés à `exports` (ex: `exports.maFonction = ...`), soit assigner un objet littéral à `module.exports`.
  • Evitez de réassigner `exports` (`exports = ...`). Préférez toujours travailler avec `module.exports` pour éviter toute confusion.

En maîtrisant `require()` et `module.exports`, vous pouvez efficacement structurer vos applications Node.js en utilisant le système CommonJS, en important des fonctionnalités externes et en exposant les vôtres de manière contrôlée.