CommonJS vs ES Modules
Node tiene dos sistemas de módulos que conviven, con sintaxis y reglas de resolución distintas. La mayoría de la confusión viene de mezclarlos sin saber cuál está activo en cada archivo.
CommonJS (require)
Sección titulada «CommonJS (require)»El sistema original de Node, todavía el default salvo que se indique lo contrario.
function sumar(a, b) { return a + b}
module.exports = { sumar }const { sumar } = require("./math.cjs")- Síncrono:
require()carga y ejecuta el módulo en el momento, bloqueando hasta terminar. module.exportses el objeto completo que se exporta;exports.algo = ...es un atajo que apunta a lo mismo (pero reasignarexports = ...entero rompe esa referencia — usarmodule.exportspara eso).
ES Modules (import/export)
Sección titulada «ES Modules (import/export)»El estándar del lenguaje JavaScript (no específico de Node), soportado nativamente desde Node 12+.
export function sumar(a, b) { return a + b}import { sumar } from "./math.mjs"- Asíncrono por diseño (aunque en la práctica top-level
awaitlo hace sentir síncrono) — permite optimizaciones que CommonJS no puede (tree-shaking real, análisis estático de qué se importa). export defaultpara un único valor principal por módulo;export { algo }(named exports) para varios.
Cómo Node decide cuál usar
Sección titulada «Cómo Node decide cuál usar»Tres formas, en este orden de precedencia:
- Extensión del archivo:
.mjssiempre es ES Modules,.cjssiempre es CommonJS, sin importar nada más. - Campo
"type"enpackage.json:"type": "module"hace que los.jsdel proyecto se traten como ES Modules; sin ese campo (o"type": "commonjs"), los.jsson CommonJS. - Dentro de un proyecto TypeScript, el
tsconfig.json(module/moduleResolution) además decide qué sintaxis compila hacia cuál — eso es una capa aparte de lo que Node ejecuta en runtime.
{ "type": "module"}Interop: usar uno desde el otro
Sección titulada «Interop: usar uno desde el otro»// Desde ESM, importar un paquete CommonJS: funciona directoimport express from "express"
// ESM puede importar este paquete CommonJS
// Desde CommonJS, importar un módulo ESM: NO funciona con require()// require('paquete-esm-only'); // TypeError: no soporta require de ESM síncronamente
// Alternativa desde CommonJS: import() dinámico (siempre devuelve una promesa)const paqueteEsm = await import("paquete-esm-only")ESM puede consumir CommonJS casi siempre sin fricción; CommonJS no puede hacer require() de un paquete que solo publica ESM — necesita import() dinámico, que es asíncrono.
Cómo Node resuelve un import/require
Sección titulada «Cómo Node resuelve un import/require»// ruta relativa, extensión obligatoria en ESMimport "express"// busca en node_modules/express, sube por el árbol de carpetasimport "node:fs"import "./archivo.js"
// módulo nativo de Node, el prefijo "node:" es explícito y recomendadoPara paquetes (no rutas relativas), Node busca node_modules/ en la carpeta actual, y si no está, sube un nivel y repite, hasta la raíz del sistema de archivos — así funciona que un paquete instalado en la raíz del proyecto sea visible desde cualquier archivo más adentro.
Comparación rápida
Sección titulada «Comparación rápida»| CommonJS | ES Modules | |
|---|---|---|
| Sintaxis | require() / module.exports | import / export |
| Carga | Síncrona | Asíncrona (pero import estático se siente síncrono) |
| Activar con | Default, o .cjs, o sin "type" en package.json | "type": "module" en package.json, o .mjs |
| ESM → CJS | Funciona directo | — |
| CJS → ESM | Necesita import() dinámico | — |
Errores comunes
Sección titulada «Errores comunes»SyntaxError: Cannot use import statement outside a module— el archivo usaimportpero Node lo está tratando como CommonJS (falta"type": "module", o la extensión es.jssin ese campo).ERR_REQUIRE_ESM— unrequire()intentando cargar un paquete que solo publica ESM. Solución:import()dinámico, o migrar el proyecto entero a"type": "module".- Mezclar
.mjsy.cjs“porque sí” en el mismo proyecto sin necesidad real — generalmente es mejor elegir uno ("type": "module"es la dirección donde va el ecosistema hoy) y ser consistente.