Contactez-nous

Modules ES (ECMAScript Modules) : import et export

Adoptez le standard moderne des modules JavaScript (ESM) en Node.js. Apprenez la syntaxe import et export pour une organisation de code statiquement analysable.

ESM : Le standard moderne des modules JavaScript

Alors que CommonJS a servi de système de modules de facto pour Node.js pendant des années, le comité ECMAScript (TC39) a travaillé à la standardisation d'un système de modules natif pour le langage JavaScript lui-même. Le résultat est ECMAScript Modules (ES Modules ou ESM), introduit avec ES6 (ES2015). ESM est conçu pour fonctionner aussi bien dans les navigateurs que côté serveur comme dans Node.js.

ESM offre plusieurs avantages par rapport à CommonJS :

  • Syntaxe déclarative : Utilise les mots-clés `import` et `export`, qui sont plus explicites et intégrés au langage.
  • Analyse statique : La structure des imports et exports peut être déterminée à l'avance (sans exécuter le code), ce qui permet des optimisations comme le "tree shaking" (élimination du code mort par les bundlers) et des vérifications plus rapides par les outils de développement.
  • Chargement asynchrone : Le chargement et la résolution des modules ESM sont conçus pour être asynchrones, ce qui est mieux adapté aux environnements comme les navigateurs (bien qu'en Node.js, le comportement puisse sembler synchrone dans de nombreux cas après la première résolution).
  • Support du Top-Level `await` : Permet d'utiliser `await` directement au niveau supérieur d'un module ESM (sans l'envelopper dans une fonction `async`).

Node.js supporte désormais pleinement les ES Modules, et c'est le système recommandé pour les nouveaux projets, bien que CommonJS reste largement utilisé.

Exporter des fonctionnalités avec `export`

Le mot-clé `export` est utilisé pour rendre des variables, fonctions ou classes d'un module accessibles depuis d'autres modules. Il existe deux principaux types d'exports :

1. Exports Nommés (Named Exports) : Permet d'exporter plusieurs valeurs distinctes depuis un module. Chaque valeur exportée a un nom.

  • En ligne : Placer `export` directement devant la déclaration.
// Fichier: maths.mjs (ou .js avec "type": "module" dans package.json)

export const PI = 3.14159;

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

export class Calculatrice {
  constructor() { /* ... */ }
  multiplier(a,b) { return a * b; }
}
  • Liste d'exports à la fin : Déclarer les éléments normalement, puis les exporter en groupe à la fin du fichier. Permet également de renommer les exports avec `as`.
// Fichier: utils.mjs
const MAX_USERS = 100;
function formaterDate(date) {
  // ... logique de formatage
  return date.toISOString();
}

// Exportation groupée
export { MAX_USERS, formaterDate as formatDate }; // formaterDate est exporté sous le nom formatDate

2. Export par Défaut (Default Export) : Permet d'exporter une seule valeur principale depuis un module (souvent une classe, une fonction ou un objet). Il ne peut y avoir qu'un seul `export default` par module. L'élément exporté par défaut n'a pas besoin d'avoir un nom lors de l'exportation.

// Fichier: MonComposant.mjs

class MonComposant {
  // ... définition de la classe
  render() { /* ... */ }
}

export default MonComposant; // Exporte la classe comme valeur par défaut

// --- Autre exemple : export d'une fonction par défaut ---
// Fichier: faireQuelqueChose.mjs
export default function() {
  console.log("Action par défaut effectuée.");
}

// --- Autre exemple : export d'un objet par défaut ---
// Fichier: config.mjs
const config = {
    port: 3000,
    dbUrl: "mongodb://localhost/mydb"
};
export default config;

Un module peut avoir à la fois des exports nommés et un (unique) export par défaut.

Importer des fonctionnalités avec `import`

Le mot-clé `import` est utilisé pour charger des fonctionnalités exportées par d'autres modules.

1. Importer des Exports Nommés : Utilise des accolades `{}` pour spécifier les exports à importer par leur nom. On peut renommer avec `as`.

// Fichier: main.mjs

// Importer PI et addition depuis maths.mjs
import { PI, addition } from './maths.mjs';

// Importer formatDate (qui était exporté sous ce nom) depuis utils.mjs
// et renommer MAX_USERS en LIMITE_UTILISATEURS
import { formatDate, MAX_USERS as LIMITE_UTILISATEURS } from './utils.mjs';

console.log(`Pi vaut ${PI}`);
console.log(`Somme: ${addition(2, 3)}`);
console.log(`Limite: ${LIMITE_UTILISATEURS}`); 
console.log(`Date formatée: ${formatDate(new Date())}`);

2. Importer l'Export par Défaut : Le nom que vous donnez lors de l'importation n'a pas besoin de correspondre au nom original (s'il en avait un) lors de l'exportation. Vous choisissez le nom de la variable qui recevra la valeur par défaut.

// Fichier: app.mjs

// Importer l'export par défaut de MonComposant.mjs
import ComposantUI from './MonComposant.mjs';

// Importer la fonction par défaut de faireQuelqueChose.mjs
import effectuerAction from './faireQuelqueChose.mjs';

// Importer l'objet de configuration par défaut
import parametres from './config.mjs';

const comp = new ComposantUI();
effectuerAction();
console.log(`Port de config: ${parametres.port}`);

3. Importer à la fois le Défaut et les Nommés :

// Supposons que module.mjs exporte default et des nommés
import ValeurParDefaut, { exportNomme1, exportNomme2 as alias } from './module.mjs';

4. Import d'Espace de Noms (Namespace Import) : Importe tous les exports nommés d'un module dans un unique objet.

// Fichier: main_namespace.mjs
import * as MathsAPI from './maths.mjs';

console.log(`Pi via namespace: ${MathsAPI.PI}`);
const calc = new MathsAPI.Calculatrice();
console.log(`Produit: ${calc.multiplier(5, 4)}`);

Note : Cela n'importe pas l'export par défaut.

5. Import pour Effets de Bord (Side Effects) : Parfois, vous voulez juste exécuter le code d'un module (par exemple, pour appliquer un polyfill ou exécuter une configuration globale) sans importer de valeur spécifique.

// Fichier: polyfills.mjs
// (Contient du code qui modifie des objets globaux, par exemple)
console.log("Polyfills chargés !");

// Fichier: app_side_effect.mjs
import './polyfills.mjs'; // Exécute le code de polyfills.mjs

console.log("Application démarrée.");

// Output:
// Polyfills chargés !
// Application démarrée.

Utilisation d'ES Modules dans Node.js

Pour que Node.js interprète un fichier comme un ES Module, vous avez deux options principales :

  1. Utiliser l'extension de fichier `.mjs` : Si un fichier a l'extension `.mjs`, Node.js le traitera toujours comme un ES Module.
  2. Définir `"type": "module"` dans `package.json` : Ajoutez la ligne `"type": "module"` dans le fichier `package.json` le plus proche (dans le même dossier ou un dossier parent). Tous les fichiers `.js` dans ce dossier et ses sous-dossiers (sauf si un autre `package.json` le redéfinit) seront alors traités comme des ES Modules. Si vous choisissez cette option et que vous avez besoin d'utiliser du code CommonJS, vous devrez utiliser l'extension `.cjs` pour ces fichiers spécifiques.
// package.json
{
  "name": "mon-projet-esm",
  "version": "1.0.0",
  "type": "module", // Indique que les fichiers .js sont des ES Modules
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {}
}

Interopérabilité :

  • Importer du CommonJS depuis ESM : C'est généralement possible. Vous pouvez `import`er un module CommonJS. Souvent, l'objet `module.exports` sera disponible comme export par défaut.
// Fichier: main.mjs ("type": "module")
import _ from 'lodash'; // lodash est généralement en CJS
import monModuleCJS from './ancien_module.cjs'; // Importer un module .cjs

console.log(_.capitalize('hello'));
monModuleCJS.fonctionCJS();
  • Importer de l'ESM depuis CommonJS : C'est plus complexe car `require()` est synchrone et ESM est asynchrone. Vous ne pouvez pas utiliser `require()` pour importer directement un ES Module. La solution est d'utiliser l'importation dynamique `import()`, qui retourne une Promesse.
// Fichier: main.cjs (CommonJS)

async function chargerModuleESM() {
  try {
    // Import() est asynchrone et retourne une promesse
    const monModuleESM = await import('./nouveau_module.mjs');
    monModuleESM.fonctionESM(); // Accès via la promesse résolue
    console.log(monModuleESM.default); // Accès à l'export par défaut
  } catch (err) {
    console.error("Erreur d'import dynamique:", err);
  }
}

chargerModuleESM();

Les ES Modules représentent l'avenir de la modularité en JavaScript et Node.js. Leur syntaxe déclarative, leur analysabilité statique et leur intégration native au langage en font le choix privilégié pour les nouveaux développements, tout en nécessitant une compréhension des mécanismes d'interopérabilité avec l'écosystème CommonJS existant.