ESM en 2026 : La Fin de CommonJS et l'Ère du JavaScript Moderne
Salut HaWkers, 2026 est officiellement l'année de l'adoption complète des ES Modules (ESM) dans l'écosystème JavaScript. Avec Node.js 20+ permettant d'importer ESM depuis du code CommonJS, la dernière grande barrière est tombée.
Comprenons cette transition et comment elle affecte votre code.
Ce Qui a Changé en 2026
Node.js a Unifié les Mondes
// Le grand changement : CJS peut importer ESM
// Avant (jusqu'en 2025) : Cela donnait une erreur
// fichier.cjs
const esModule = require('./module-esm.mjs'); // ❌ Ne fonctionnait pas
// Maintenant (2026) : Ça fonctionne !
// fichier.cjs
const esModule = require('./module-esm.mjs'); // ✅ Fonctionne
// Node.js 20+ (rétro-porté) permet cela
// Cela supprime le principal mal de tête de migrationPourquoi C'est Important
// Le problème historique
const esmAdoptionBlocker = {
before: {
problem: 'Les bibliothèques ESM ne pouvaient pas être utilisées dans les projets CJS',
symptom: 'ERR_REQUIRE_ESM',
workaround: 'Dynamic import() - moche et asynchrone',
result: 'Beaucoup de libs maintenaient dual publish'
},
after: {
solution: 'CJS peut require() ESM directement',
impact: 'Les libs peuvent être ESM-only sans casser les utilisateurs',
trend: 'Les nouvelles libs sont ESM-only par défaut'
},
timeline: {
node22: 'Fonctionnalité ajoutée',
node20: 'Rétro-portée',
node18: 'Rétro-portée (LTS)',
adoption: 'Tous les Node.js non-EOL le supportent'
}
};
CommonJS vs ESM : Les Différences
Syntaxe
// CommonJS (l'ancienne façon)
// Exporter
module.exports = { foo, bar };
// ou
exports.foo = foo;
// Importer
const { foo, bar } = require('./module');
const fs = require('fs');
// ESM (la façon moderne)
// Exporter
export { foo, bar };
export default myFunction;
// Importer
import { foo, bar } from './module.js';
import fs from 'node:fs';Différences Importantes
// 1. Timing de chargement
// CommonJS : Synchrone, à l'exécution
const a = require('./a'); // S'exécute maintenant
if (condition) {
const b = require('./b'); // S'exécute conditionnellement
}
// ESM : Asynchrone, avant l'exécution
import a from './a.js'; // Chargé avant l'exécution du code
// import conditionnel nécessite dynamic import
const b = condition ? await import('./b.js') : null;
// 2. __dirname et __filename
// CommonJS : Disponible automatiquement
console.log(__dirname); // /chemin/vers/dossier
console.log(__filename); // /chemin/vers/dossier/fichier.js
// ESM : Besoin de créer
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 3. Imports JSON
// CommonJS
const config = require('./config.json'); // Fonctionne
// ESM
import config from './config.json' with { type: 'json' }; // Nouvelle syntaxe
// ou
import { readFileSync } from 'node:fs';
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
// 4. Top-level await
// CommonJS : Non autorisé
// await fetch(...); // ❌ SyntaxError
// ESM : Fonctionne !
const data = await fetch('...'); // ✅ Top-level await
export const result = await processData(data);
Pourquoi Migrer Vers ESM
Avantages Techniques
// Avantages d'ESM
const esmBenefits = {
// 1. Tree shaking
treeShaking: {
what: 'Les bundlers suppriment le code inutilisé',
requirement: 'Imports statiques (ESM)',
impact: 'Bundles plus petits',
example: `
// Vous importez seulement ce que vous utilisez
import { debounce } from 'lodash-es';
// Le bundler inclut seulement debounce, pas lodash entier
`
},
// 2. Analyse statique
staticAnalysis: {
what: 'Les outils comprennent les imports avant l\'exécution',
benefits: [
'Meilleur autocomplete dans les IDEs',
'Détection des imports cassés',
'Refactoring plus sûr'
]
},
// 3. Natif au navigateur
browserNative: {
what: 'ESM fonctionne dans le navigateur sans bundler',
use: 'Développement local, petites apps',
syntax: '<script type="module" src="app.js"></script>'
},
// 4. Top-level await
topLevelAwait: {
what: 'await en dehors des fonctions async',
useful: 'Initialisation de modules',
example: 'export const db = await connectDB();'
},
// 5. Pérenne
futureProof: {
what: 'ESM est le standard officiel',
cjs: 'CommonJS est spécifique à Node.js',
trend: 'L\'écosystème migre vers ESM'
}
};L'Écosystème Change
// État de l'écosystème en 2026
const ecosystem2026 = {
// Libs qui sont ESM-only maintenant
esmOnly: [
'chalk 5+',
'node-fetch 3+',
'execa 6+',
'got 12+',
'globby 13+',
'p-limit 4+',
'nanoid 4+',
// Et beaucoup d'autres...
],
// Frameworks qui préfèrent ESM
frameworks: {
vite: 'ESM-first',
nuxt3: 'ESM par défaut',
astro: 'ESM par défaut',
sveltekit: 'ESM par défaut'
},
// Ce qui est encore CommonJS
stillCJS: {
express: 'Fonctionne dans les deux',
lodash: 'A lodash-es pour ESM',
moment: 'Utilisez dayjs ou date-fns',
legacy: 'Projets non maintenus'
}
};
Comment Migrer Vers ESM
Étape 1 : Configurer le package.json
// Ajoutez "type": "module"
// package.json
{
"name": "mon-projet",
"version": "1.0.0",
"type": "module", // ← Cela fait que .js est traité comme ESM
"main": "index.js",
"exports": {
".": "./index.js",
"./utils": "./utils/index.js"
}
}
// Avec "type": "module" :
// - les fichiers .js sont ESM
// - les fichiers .cjs sont CommonJS (si nécessaire)
// Sans "type": "module" (ou "type": "commonjs") :
// - les fichiers .js sont CommonJS
// - les fichiers .mjs sont ESMÉtape 2 : Mettre à Jour les Imports
// Transformations nécessaires
// De :
const path = require('path');
const { readFile } = require('fs/promises');
const myModule = require('./my-module');
// Vers :
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import myModule from './my-module.js'; // Notez le .js !
// IMPORTANT : ESM requiert l'extension de fichier
import utils from './utils'; // ❌ Ne fonctionne pas
import utils from './utils.js'; // ✅ Fonctionne
// Pour les modules Node, utilisez le préfixe node:
import fs from 'node:fs'; // ✅ Recommandé
import fs from 'fs'; // Fonctionne, mais node: est plus clairÉtape 3 : Mettre à Jour les Exports
// Transformations d'export
// De :
module.exports = myFunction;
module.exports = { foo, bar };
exports.something = something;
// Vers :
export default myFunction;
export { foo, bar };
export const something = something;
// Exports mixtes
export default mainFunction;
export { helper1, helper2 };Étape 4 : Corriger __dirname/__filename
// Créez un helper ou ajoutez en haut des fichiers qui en ont besoin
// esm-utils.js
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
export function getDirname(importMetaUrl) {
return dirname(fileURLToPath(importMetaUrl));
}
// utilisation dans n'importe quel fichier :
import { getDirname } from './esm-utils.js';
const __dirname = getDirname(import.meta.url);
// Ou inline (plus courant) :
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Étape 5 : Mettre à Jour les Imports JSON
// Option 1 : Import assertion (Node 20+)
import config from './config.json' with { type: 'json' };
// Option 2 : Lecture manuelle (plus compatible)
import { readFileSync } from 'node:fs';
const config = JSON.parse(
readFileSync(new URL('./config.json', import.meta.url), 'utf8')
);
// Option 3 : createRequire (hack utile)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const config = require('./config.json');Stratégies de Migration
Migration Graduelle
// Pour les grands projets, migrez graduellement
const migrationStrategy = {
// Phase 1 : Préparer
phase1: {
actions: [
'Identifier les dépendances ESM-only',
'Vérifier la version de Node (18+)',
'Configurer ESLint pour ESM'
]
},
// Phase 2 : Mode dual
phase2: {
how: 'Utilisez .mjs pour les nouveaux fichiers ESM',
keep: 'Fichiers existants .js comme CJS',
benefit: 'Migration incrémentale'
},
// Phase 3 : Le basculement
phase3: {
action: 'Ajoutez "type": "module"',
rename: 'Fichiers CJS restants en .cjs',
test: 'Exécutez tous les tests'
},
// Phase 4 : Nettoyage
phase4: {
actions: [
'Renommez .mjs en .js',
'Supprimez les workarounds',
'Mettez à jour la documentation'
]
}
};Pour les Bibliothèques
// Si vous maintenez une lib npm
const libraryMigration = {
// Option 1 : ESM-only (recommandé en 2026)
esmOnly: {
packageJson: {
"type": "module",
"exports": {
".": "./dist/index.js"
}
},
pros: 'Simple, moderne',
cons: 'Casse les utilisateurs CJS sur Node < 20',
when: 'Nouvelle lib ou version majeure'
},
// Option 2 : Dual package (CJS + ESM)
dual: {
packageJson: {
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
},
pros: 'Compatibilité maximale',
cons: 'Build plus complexe, dual package hazard',
when: 'Lib populaire avec utilisateurs legacy'
}
};
Outils Qui Aident
Codemods et Linters
// Outils pour la migration
const migrationTools = {
// ESLint
eslint: {
plugin: 'eslint-plugin-import',
rules: {
'import/extensions': ['error', 'always'],
'import/no-commonjs': 'error'
}
},
// Codemod
codemod: {
tool: 'jscodeshift',
transforms: 'Transformations cjs-to-esm disponibles',
command: 'npx cjs-to-esm ./src/**/*.js'
},
// TypeScript
typescript: {
config: {
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext"
}
},
benefit: 'TS génère ESM automatiquement'
},
// Bundlers
bundlers: {
vite: 'ESM natif',
rollup: 'ESM-first',
esbuild: 'Excellent support'
}
};Erreurs Courantes et Solutions
Dépannage
// Erreurs courantes lors de la migration
const commonErrors = {
// Erreur 1 : Cannot use import statement outside a module
error1: {
message: 'Cannot use import statement outside a module',
cause: 'Fichier traité comme CJS',
solutions: [
'Ajoutez "type": "module" dans package.json',
'Utilisez l\'extension .mjs',
'Vérifiez qu\'il n\'y a pas de package.json parent'
]
},
// Erreur 2 : ERR_MODULE_NOT_FOUND
error2: {
message: 'ERR_MODULE_NOT_FOUND',
cause: 'Extension manquante dans l\'import',
solution: 'import x from "./file.js" (pas "./file")'
},
// Erreur 3 : __dirname is not defined
error3: {
message: '__dirname is not defined',
cause: '__dirname n\'existe pas en ESM',
solution: 'Utilisez import.meta.url + fileURLToPath'
},
// Erreur 4 : require is not defined
error4: {
message: 'require is not defined',
cause: 'Utilisation de require dans un module ESM',
solution: 'Utilisez import ou createRequire'
},
// Erreur 5 : L\'import JSON ne fonctionne pas
error5: {
message: 'Unknown file extension ".json"',
cause: 'JSON nécessite une assertion',
solution: 'import x from "./x.json" with { type: "json" }'
}
};
Conclusion
2026 marque le point de basculement pour ESM en JavaScript. Avec Node.js supprimant la dernière grande barrière (CJS important ESM), il n'y a plus d'excuse pour ne pas migrer.
Actions recommandées :
- Nouveaux projets : Utilisez ESM dès le départ
- Projets existants : Planifiez une migration graduelle
- Bibliothèques : Envisagez ESM-only pour la prochaine majeure
- Mettez à jour Node.js : 20+ pour la meilleure expérience
CommonJS a bien servi pendant 15 ans, mais son temps touche à sa fin. ESM est l'avenir - et l'avenir c'est maintenant.
Pour en savoir plus sur l'écosystème JavaScript moderne, lisez : VoidZero 2026.

