Skip to content

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)
Sintaxisrequire() / module.exportsimport / export
OrigenCreado por Node (2009)Estándar oficial de JavaScript
CargaSíncronaAsíncrona, analizable estáticamente
await en el nivel superior❌✅
Navegador❌✅
Uso hoyProyectos 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) // function

En 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 ​

  1. Archivos .mjs → siempre ESM.
  2. Archivos .cjs → siempre CommonJS.
  3. Archivos .js → depende del package.json más cercano:
    • "type": "module" → ESM.
    • "type": "commonjs" o sin campo → CommonJS (aunque Node 22 detecta sintaxis import y 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.84

Alias 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 ​

  1. Crea un proyecto ESM con un módulo validaciones.js que exporte esEmail(texto) y esTelefonoMX(texto) (10 dígitos). Úsalos desde index.js.
  2. 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')) // true
js
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.dirname en lugar de __dirname, top-level await.
  • Usa import() para carga dinámica y imports en package.json para alias.

Academia Web — aprende, copia y construye.