ESM en 2026: El Fin de CommonJS y la Era del JavaScript Moderno
Hola HaWkers, 2026 es oficialmente el año de la adopción completa de ES Modules (ESM) en el ecosistema JavaScript. Con Node.js 20+ permitiendo importar ESM desde código CommonJS, la última gran barrera cayó.
Vamos a entender esta transición y cómo afecta tu código.
Qué Cambió en 2026
Node.js Unificó los Mundos
// El gran cambio: CJS puede importar ESM
// Antes (hasta 2025): Esto daba error
// archivo.cjs
const esModule = require('./modulo-esm.mjs'); // ❌ No funcionaba
// Ahora (2026): ¡Funciona!
// archivo.cjs
const esModule = require('./modulo-esm.mjs'); // ✅ Funciona
// Node.js 20+ (backportado) permite esto
// Esto elimina el principal dolor de cabeza de migraciónPor Qué Esto Es Grande
// El problema histórico
const esmAdoptionBlocker = {
before: {
problem: 'Bibliotecas ESM no podían usarse en proyectos CJS',
symptom: 'ERR_REQUIRE_ESM',
workaround: 'Dynamic import() - feo y asíncrono',
result: 'Muchas libs mantenían dual publish'
},
after: {
solution: 'CJS puede require() ESM directamente',
impact: 'Libs pueden ser ESM-only sin romper a usuarios',
trend: 'Nuevas libs son ESM-only por defecto'
},
timeline: {
node22: 'Feature añadida',
node20: 'Backportado',
node18: 'Backportado (LTS)',
adoption: 'Todos los Node.js no-EOL lo soportan'
}
};
CommonJS vs ESM: Las Diferencias
Sintaxis
// CommonJS (la forma antigua)
// Exportar
module.exports = { foo, bar };
// o
exports.foo = foo;
// Importar
const { foo, bar } = require('./module');
const fs = require('fs');
// ESM (la forma moderna)
// Exportar
export { foo, bar };
export default myFunction;
// Importar
import { foo, bar } from './module.js';
import fs from 'node:fs';Diferencias Importantes
// 1. Timing de carga
// CommonJS: Síncrono, en runtime
const a = require('./a'); // Ejecuta ahora
if (condition) {
const b = require('./b'); // Ejecuta condicionalmente
}
// ESM: Asíncrono, antes de runtime
import a from './a.js'; // Cargado antes de ejecutar código
// import condicional requiere dynamic import
const b = condition ? await import('./b.js') : null;
// 2. __dirname y __filename
// CommonJS: Disponible automáticamente
console.log(__dirname); // /path/to/folder
console.log(__filename); // /path/to/folder/file.js
// ESM: Necesita crear
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 3. JSON imports
// CommonJS
const config = require('./config.json'); // Funciona
// ESM
import config from './config.json' with { type: 'json' }; // Sintaxis nueva
// o
import { readFileSync } from 'node:fs';
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
// 4. Top-level await
// CommonJS: No permitido
// await fetch(...); // ❌ SyntaxError
// ESM: ¡Funciona!
const data = await fetch('...'); // ✅ Top-level await
export const result = await processData(data);
Por Qué Migrar a ESM
Beneficios Técnicos
// Ventajas de ESM
const esmBenefits = {
// 1. Tree shaking
treeShaking: {
what: 'Bundlers eliminan código no usado',
requirement: 'Imports estáticos (ESM)',
impact: 'Bundles más pequeños',
example: `
// Importas solo lo que usas
import { debounce } from 'lodash-es';
// Bundler incluye solo debounce, no lodash entero
`
},
// 2. Static analysis
staticAnalysis: {
what: 'Herramientas entienden imports antes de ejecutar',
benefits: [
'Mejor autocomplete en IDEs',
'Detección de imports rotos',
'Refactorización más segura'
]
},
// 3. Browser-native
browserNative: {
what: 'ESM funciona en browser sin bundler',
use: 'Desarrollo local, apps pequeñas',
syntax: '<script type="module" src="app.js"></script>'
},
// 4. Top-level await
topLevelAwait: {
what: 'await fuera de funciones async',
useful: 'Inicialización de módulos',
example: 'export const db = await connectDB();'
},
// 5. Future-proof
futureProof: {
what: 'ESM es el estándar oficial',
cjs: 'CommonJS es específico de Node.js',
trend: 'Ecosistema migrando a ESM'
}
};El Ecosistema Está Cambiando
// Estado del ecosistema en 2026
const ecosystem2026 = {
// Libs que son ESM-only ahora
esmOnly: [
'chalk 5+',
'node-fetch 3+',
'execa 6+',
'got 12+',
'globby 13+',
'p-limit 4+',
'nanoid 4+',
// Y muchas otras...
],
// Frameworks que prefieren ESM
frameworks: {
vite: 'ESM-first',
nuxt3: 'ESM por defecto',
astro: 'ESM por defecto',
sveltekit: 'ESM por defecto'
},
// Lo que todavía es CommonJS
stillCJS: {
express: 'Funciona en ambos',
lodash: 'Tiene lodash-es para ESM',
moment: 'Usa dayjs o date-fns',
legacy: 'Proyectos no mantenidos'
}
};
Cómo Migrar a ESM
Paso 1: Configura el package.json
// Añade "type": "module"
// package.json
{
"name": "mi-proyecto",
"version": "1.0.0",
"type": "module", // ← Esto hace que .js sea tratado como ESM
"main": "index.js",
"exports": {
".": "./index.js",
"./utils": "./utils/index.js"
}
}
// Con "type": "module":
// - archivos .js son ESM
// - archivos .cjs son CommonJS (si lo necesitas)
// Sin "type": "module" (o "type": "commonjs"):
// - archivos .js son CommonJS
// - archivos .mjs son ESMPaso 2: Actualiza los Imports
// Transformaciones necesarias
// De:
const path = require('path');
const { readFile } = require('fs/promises');
const myModule = require('./my-module');
// A:
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import myModule from './my-module.js'; // ¡Nota el .js!
// IMPORTANTE: ESM requiere extensión de archivo
import utils from './utils'; // ❌ No funciona
import utils from './utils.js'; // ✅ Funciona
// Para módulos de Node, usa prefijo node:
import fs from 'node:fs'; // ✅ Recomendado
import fs from 'fs'; // Funciona, pero node: es más claroPaso 3: Actualiza los Exports
// Transformaciones de export
// De:
module.exports = myFunction;
module.exports = { foo, bar };
exports.something = something;
// A:
export default myFunction;
export { foo, bar };
export const something = something;
// Exports mixtos
export default mainFunction;
export { helper1, helper2 };Paso 4: Corrige __dirname/__filename
// Crea un helper o añade al inicio de los archivos que lo necesitan
// esm-utils.js
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
export function getDirname(importMetaUrl) {
return dirname(fileURLToPath(importMetaUrl));
}
// uso en cualquier archivo:
import { getDirname } from './esm-utils.js';
const __dirname = getDirname(import.meta.url);
// O inline (más común):
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Paso 5: Actualiza JSON Imports
// Opción 1: Import assertion (Node 20+)
import config from './config.json' with { type: 'json' };
// Opción 2: Lectura manual (más compatible)
import { readFileSync } from 'node:fs';
const config = JSON.parse(
readFileSync(new URL('./config.json', import.meta.url), 'utf8')
);
// Opción 3: createRequire (hack útil)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const config = require('./config.json');Estrategias de Migración
Migración Gradual
// Para proyectos grandes, migra gradualmente
const migrationStrategy = {
// Fase 1: Prepara
phase1: {
actions: [
'Identifica dependencias ESM-only',
'Verifica versión de Node (18+)',
'Configura ESLint para ESM'
]
},
// Fase 2: Modo dual
phase2: {
how: 'Usa .mjs para nuevos archivos ESM',
keep: 'Archivos existentes .js como CJS',
benefit: 'Migración incremental'
},
// Fase 3: El cambio
phase3: {
action: 'Añade "type": "module"',
rename: 'Archivos CJS restantes a .cjs',
test: 'Ejecuta todos los tests'
},
// Fase 4: Limpieza
phase4: {
actions: [
'Renombra .mjs a .js',
'Elimina workarounds',
'Actualiza documentación'
]
}
};Para Bibliotecas
// Si mantienes una lib npm
const libraryMigration = {
// Opción 1: ESM-only (recomendado en 2026)
esmOnly: {
packageJson: {
"type": "module",
"exports": {
".": "./dist/index.js"
}
},
pros: 'Simple, moderno',
cons: 'Rompe usuarios CJS en Node < 20',
when: 'Nueva lib o major version'
},
// Opción 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: 'Compatibilidad máxima',
cons: 'Build más complejo, dual package hazard',
when: 'Lib popular con usuarios legacy'
}
};
Herramientas Que Ayudan
Codemods y Linters
// Herramientas para migración
const migrationTools = {
// ESLint
eslint: {
plugin: 'eslint-plugin-import',
rules: {
'import/extensions': ['error', 'always'],
'import/no-commonjs': 'error'
}
},
// Codemod
codemod: {
tool: 'jscodeshift',
transforms: 'cjs-to-esm transforms disponibles',
command: 'npx cjs-to-esm ./src/**/*.js'
},
// TypeScript
typescript: {
config: {
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext"
}
},
benefit: 'TS genera ESM automáticamente'
},
// Bundlers
bundlers: {
vite: 'ESM nativo',
rollup: 'ESM-first',
esbuild: 'Soporte excelente'
}
};Errores Comunes y Soluciones
Troubleshooting
// Errores comunes en la migración
const commonErrors = {
// Error 1: Cannot use import statement outside a module
error1: {
message: 'Cannot use import statement outside a module',
cause: 'Archivo tratado como CJS',
solutions: [
'Añade "type": "module" en package.json',
'Usa extensión .mjs',
'Verifica que no haya package.json padre'
]
},
// Error 2: ERR_MODULE_NOT_FOUND
error2: {
message: 'ERR_MODULE_NOT_FOUND',
cause: 'Falta extensión en el import',
solution: 'import x from "./file.js" (no "./file")'
},
// Error 3: __dirname is not defined
error3: {
message: '__dirname is not defined',
cause: '__dirname no existe en ESM',
solution: 'Usa import.meta.url + fileURLToPath'
},
// Error 4: require is not defined
error4: {
message: 'require is not defined',
cause: 'Usando require en módulo ESM',
solution: 'Usa import o createRequire'
},
// Error 5: JSON import no funciona
error5: {
message: 'Unknown file extension ".json"',
cause: 'JSON necesita assertion',
solution: 'import x from "./x.json" with { type: "json" }'
}
};
Conclusión
2026 marca el punto de inflexión para ESM en JavaScript. Con Node.js eliminando la última gran barrera (CJS importando ESM), ya no hay excusa para no migrar.
Acciones recomendadas:
- Proyectos nuevos: Usa ESM desde el inicio
- Proyectos existentes: Planifica migración gradual
- Bibliotecas: Considera ESM-only para la próxima major
- Actualiza Node.js: 20+ para mejor experiencia
CommonJS sirvió bien por 15 años, pero su tiempo está llegando al fin. ESM es el futuro - y el futuro es ahora.
Para entender más sobre el ecosistema JavaScript moderno, lee: VoidZero 2026.

