Tema
Módulos: CommonJS vs ESM
Un proyecto real tiene decenas o cientos de archivos. Los módulos te permiten dividir el código en piezas reutilizables. En Node conviven dos sistemas: CommonJS (require) y ES Modules (import). Aprenderás ambos y cuál elegir hoy.
Concepto
Un módulo es un archivo que exporta cosas (funciones, clases, valores) para que otros archivos las importen. Es como una caja de herramientas: cada caja tiene su etiqueta y sólo sacas lo que necesitas.
| CommonJS (CJS) | ES Modules (ESM) | |
|---|---|---|
| Sintaxis | require() / module.exports | import / export |
| Origen | Creado por Node (2009) | Estándar oficial de JavaScript |
| Carga | Síncrona | Asíncrona, analizable estáticamente |
await en el nivel superior | ❌ | ✅ |
| Navegador | ❌ | ✅ |
| Uso hoy | Proyectos y librerías antiguas | ✅ Recomendado para proyectos nuevos |
Ejemplo con CommonJS
js
// Exportar varias cosas
function sumar(a, b) {
return a + b
}
function restar(a, b) {
return a - b
}
const PI = 3.1416
module.exports = { sumar, restar, PI }js
// Importar con require (sin extensión también funciona)
const { sumar, PI } = require('./matematicas')
const fs = require('node:fs')
console.log(sumar(2, 3)) // 5
console.log(PI) // 3.1416
console.log(typeof fs.readFileSync) // functionEn CommonJS tienes disponibles __dirname (carpeta del archivo) y __filename (ruta del archivo).
Ejemplo con ES Modules
Para que Node trate tus .js como ESM, agrega "type": "module" a package.json:
json
{
"name": "modulos-demo",
"type": "module"
}js
// Exportaciones con nombre
export function sumar(a, b) {
return a + b
}
export const PI = 3.1416
// Exportación por defecto (una por archivo)
export default function multiplicar(a, b) {
return a * b
}js
// La exportación por defecto se nombra como quieras
import multiplicar, { sumar, PI } from './utils/matematicas.js'
// Importar todo como objeto
import * as mate from './utils/matematicas.js'
// Módulos internos con el prefijo node:
import { readFile } from 'node:fs/promises'
console.log(sumar(2, 3)) // 5
console.log(multiplicar(4, 5)) // 20
console.log(mate.PI) // 3.1416
// await en el nivel superior (top-level await)
const contenido = await readFile('./package.json', 'utf8')
console.log(JSON.parse(contenido).name)Error común
En ESM la extensión es obligatoria en importaciones relativas: './utils/matematicas.js'. Si escribes './utils/matematicas' obtendrás ERR_MODULE_NOT_FOUND.
__dirname en ESM
En ESM no existen __dirname ni __filename. Desde Node 20.11 tienes alternativas directas:
js
// rutas.js (ESM)
import path from 'node:path'
console.log(import.meta.dirname) // carpeta del archivo actual
console.log(import.meta.filename) // ruta completa del archivo
const rutaDatos = path.join(import.meta.dirname, 'datos', 'usuarios.json')
console.log(rutaDatos)Cómo decide Node qué sistema usar
- Archivos
.mjs→ siempre ESM. - Archivos
.cjs→ siempre CommonJS. - Archivos
.js→ depende delpackage.jsonmás cercano:"type": "module"→ ESM."type": "commonjs"o sin campo → CommonJS (aunque Node 22 detecta sintaxisimporty lo ejecuta como ESM con un aviso).
Interoperabilidad
js
// ESM puede importar paquetes CommonJS (el module.exports llega como default)
import express from 'express'
import lodash from 'lodash'
// En Node 22+, CommonJS puede hacer require() de módulos ESM síncronos
// (sin top-level await)
// const { sumar } = require('./matematicas.mjs')Para importar JSON en ESM:
js
import config from './config.json' with { type: 'json' }
console.log(config.puerto)Importación dinámica
Carga un módulo sólo cuando lo necesitas (útil para dependencias pesadas):
js
const formato = process.argv[2]
if (formato === 'pdf') {
const { generarPDF } = await import('./generadores/pdf.js')
await generarPDF()
}Caso real: organizar un proyecto con módulos
js
export const config = {
appNombre: 'Tienda API',
moneda: 'MXN',
iva: 0.16
}js
import { config } from '../config.js'
export function conIva(precio) {
return +(precio * (1 + config.iva)).toFixed(2)
}
export function formatear(cantidad) {
return new Intl.NumberFormat('es-MX', {
style: 'currency',
currency: config.moneda
}).format(cantidad)
}js
// "Barrel": reexporta para importar desde un solo lugar
export * from './precios.js'js
import { config } from './config.js'
import { conIva, formatear } from './servicios/index.js'
const productos = [
{ nombre: 'Teclado', precio: 899 },
{ nombre: 'Mouse', precio: 349 }
]
console.log(`== ${config.appNombre} ==`)
for (const p of productos) {
console.log(`${p.nombre}: ${formatear(conIva(p.precio))}`)
}json
{
"name": "tienda",
"type": "module",
"scripts": {
"start": "node src/index.js"
}
}bash
npm start
# == Tienda API ==
# Teclado: $1,042.84
# Mouse: $404.84Alias de rutas con imports
Evita los ../../../ usando el campo imports de package.json (los alias deben empezar con #):
json
{
"type": "module",
"imports": {
"#config": "./src/config.js",
"#servicios/*": "./src/servicios/*"
}
}js
import { config } from '#config'
import { conIva } from '#servicios/precios.js'Buena práctica
En proyectos nuevos usa ESM ("type": "module"), el prefijo node: para módulos internos (node:fs, node:path) y exportaciones con nombre: facilitan el autocompletado y refactorizar.
Error común
Mezclar sintaxis: usar require en un archivo ESM da ReferenceError: require is not defined in ES module scope. Elige un sistema por proyecto.
Ejercicios
- Crea un proyecto ESM con un módulo
validaciones.jsque exporteesEmail(texto)yesTelefonoMX(texto)(10 dígitos). Úsalos desdeindex.js. - Convierte este código CommonJS a ESM:
js
const path = require('path')
const { saludar } = require('./saludos')
console.log(path.join(__dirname, 'archivo.txt'))
module.exports = { version: '1.0.0' }Ver solución
js
export function esEmail(texto) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(texto)
}
export function esTelefonoMX(texto) {
return /^\d{10}$/.test(texto.replace(/[\s-]/g, ''))
}js
import { esEmail, esTelefonoMX } from './validaciones.js'
console.log(esEmail('ana@correo.com')) // true
console.log(esEmail('ana@correo')) // false
console.log(esTelefonoMX('55 1234 5678')) // truejs
import path from 'node:path'
import { saludar } from './saludos.js'
console.log(path.join(import.meta.dirname, 'archivo.txt'))
export const version = '1.0.0'Resumen
- CommonJS:
require/module.exports. ESM:import/export, el estándar. - Activa ESM con
"type": "module"o la extensión.mjs. - En ESM: extensión obligatoria,
import.meta.dirnameen lugar de__dirname, top-levelawait. - Usa
import()para carga dinámica yimportsenpackage.jsonpara alias.