Avancé20 min de lecture

Modules ES

Apprends à organiser ton code avec les modules ES — syntaxe import/export, exports nommés et par défaut, et modèles de modules.

Pourquoi des modules ?

À mesure que les applications grandissent, garder tout le code dans un seul fichier devient ingérable. Les modules te permettent de diviser le code en fichiers séparés, chacun avec sa propre portée.

Avantages des modules :

  • Organisation du code — regroupe les fonctionnalités liées ensemble.
  • Éviter la pollution de l'espace de noms global — les variables des modules ont une portée limitée, elles ne sont pas globales.
  • Réutilisabilité — importe le même module dans plusieurs fichiers.
  • Encapsulation — exporte uniquement ce qui est nécessaire ; garde les détails internes privés.
  • Gestion des dépendances — déclare clairement de quoi dépend chaque fichier.

Avant les modules ES, les développeurs utilisaient divers modèles pour obtenir de la modularité :

IIFE (Immediately Invoked Function Expressions) :

javascript
const myModule = (function() {
  const privateVar = 'hidden';
  return { publicMethod: () => privateVar };
})();

CommonJS (Node.js) :

javascript
const fs = require('fs');
module.exports = { myFunction };

Les modules ES (ESM) sont maintenant le standard officiel pour les modules JavaScript, supportés nativement dans les navigateurs et Node.js.

Exports et imports nommés

Les exports nommés te permettent d'exporter plusieurs valeurs depuis un module. Chaque export a un nom spécifique qui doit être utilisé lors de l'import.

Exporter — utilise le mot-clé export avant une déclaration :

javascript
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }

Ou exporte plusieurs valeurs en une fois en utilisant une liste d'exports :

javascript
const PI = 3.14159;
function add(a, b) { return a + b; }
export { PI, add };

Importer — utilise des accolades avec les noms d'exports exacts :

javascript
import { PI, add } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI);        // 3.14159

Renommer lors de l'import avec as :

javascript
import { add as sum } from './math.js';
console.log(sum(2, 3)); // 5

Importer tout comme un objet namespace :

javascript
import * as math from './math.js';
console.log(math.PI);       // 3.14159
console.log(math.add(2, 3)); // 5

Exports par défaut

Un export par défaut est la chose "principale" qu'un module fournit. Chaque module peut avoir au maximum un export par défaut.

Exporter une valeur par défaut :

javascript
// greet.js
export default function greet(name) {
  return 'Hello, ' + name + '!';
}

Ou avec une expression :

javascript
// config.js
export default {
  theme: 'dark',
  language: 'en'
};

Importer un export par défaut — pas besoin d'accolades, et tu peux utiliser n'importe quel nom :

javascript
import greet from './greet.js';
import myConfig from './config.js';

console.log(greet('World'));    // 'Hello, World!'
console.log(myConfig.theme);    // 'dark'

Combiner exports par défaut et nommés :

javascript
// utils.js
export default function main() { /* ... */ }
export function helper() { /* ... */ }
export const VERSION = '1.0';
javascript
import main, { helper, VERSION } from './utils.js';

Les exports par défaut sont couramment utilisés pour l'export principal d'un fichier — une classe, un composant, ou la fonction principale.

Fonctionnalités des modules

Les modules ES ont plusieurs fonctionnalités importantes qui les distinguent des scripts normaux.

Mode strict par défaut — les modules s'exécutent automatiquement en mode strict. Tu n'as pas besoin de 'use strict';.

Portée au niveau du module — les variables déclarées dans un module ont une portée limitée à ce module, elles ne sont pas ajoutées à l'objet global :

javascript
// Dans un module
const secret = 'hidden'; // Non accessible depuis d'autres modules

Les imports sont remontés — les instructions import sont déplacées vers le haut lors de l'analyse, peu importe où tu les écris.

Structure statique — les imports et exports sont analysés au moment de la compilation, pas à l'exécution. Cela permet le tree-shaking (suppression du code non utilisé) dans les bundlers.

Import dynamique — pour les cas où tu as besoin de charger un module conditionnellement ou à la demande, utilise import() comme une fonction. Elle retourne une Promise :

javascript
const button = document.getElementById('load');
button.addEventListener('click', async () => {
  const module = await import('./heavy-feature.js');
  module.init();
});

Les imports dynamiques sont utiles pour le code splitting — charger des parties de ton application uniquement quand elles sont nécessaires, améliorant le temps de chargement initial.

Dans le navigateur, utilise <script type="module"> pour charger les modules ES :

html
<script type="module" src="app.js"></script>

Référence de la syntaxe des modules

javascript
// ========== Exports nommés ==========

// Export en ligne
export const name = 'Kodojo';
export function greet(who) { return `Hello, ${who}!`; }

// Liste d'exports
const a = 1;
const b = 2;
export { a, b };

// Renommer lors de l'export
export { a as alpha, b as beta };


// ========== Export par défaut ==========

export default class App {
  constructor() { this.name = 'Kodojo'; }
}


// ========== Imports ==========

// Imports nommés
import { name, greet } from './module.js';

// Renommer lors de l'import
import { name as appName } from './module.js';

// Import namespace
import * as mod from './module.js';

// Import par défaut
import App from './module.js';

// Défaut + nommés
import App, { name, greet } from './module.js';

// Import dynamique (retourne une Promise)
const mod2 = await import('./module.js');

Combien d'exports par défaut un module peut-il avoir ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.