Módulos ES
Sintaxis import/export, imports dinámicos, patrones de módulos y empaquetado
Módulos ES (ESM)
Los Módulos ES son el sistema de módulos oficial y estandarizado para JavaScript. Proporcionan una sintaxis limpia para organizar código en piezas reutilizables con imports y exports explícitos. ESM está soportado en todos los navegadores modernos y Node.js.
Características Clave
- Análisis estático — Los imports/exports se determinan en tiempo de compilación
- Modo estricto — Los módulos siempre están en modo estricto
- Ejecución diferida — Los scripts de módulo son diferidos por defecto
- Instancia única — Los módulos son singletons (cacheados después de la primera carga)
Exports Nombrados
// math.js — Exports nombrados
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// También se puede exportar al final
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;
export { subtract, divide };
// Renombrar al exportar
export { subtract as sub, divide as div };
// main.js — Imports nombrados
import { add, multiply, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
// Renombrar al importar
import { add as suma, multiply as mult } from "./math.js";
// Importar todo como namespace
import * as math from "./math.js";
console.log(math.add(1, 2));
console.log(math.PI);
Exports por Defecto
// logger.js — Export por defecto
export default function log(mensaje) {
console.log(`[LOG] ${mensaje}`);
}
// O con clase
export default class Logger {
log(msg) {
console.log(msg);
}
}
// O exportar al final
function log(mensaje) {
console.log(mensaje);
}
export default log;
// main.js — Import por defecto (cualquier nombre funciona)
import log from "./logger.js";
import miLogger from "./logger.js"; // Lo mismo, diferente nombre
log("¡Hola!");
// Mezclar default y nombrados
import log, { formatMessage, LogLevel } from "./logger.js";
Re-exportación
// utils/index.js — Archivo barrel (re-exports)
export { add, subtract } from "./math.js";
export { formatDate } from "./date.js";
export { default as Logger } from "./logger.js";
// Re-exportar todo
export * from "./math.js";
// Re-exportar con renombre
export { add as suma } from "./math.js";
// main.js — Imports limpios desde barrel
import { add, formatDate, Logger } from "./utils/index.js";
// o
import { add, formatDate, Logger } from "./utils"; // Con bundler
Imports Dinámicos
// Import estático (arriba del archivo, siempre carga)
import { funcionPesada } from "./modulo-pesado.js";
// Import dinámico (carga bajo demanda, retorna Promise)
async function cargarModulo() {
const modulo = await import("./modulo-pesado.js");
modulo.funcionPesada();
}
// Carga condicional
if (necesitaFuncion) {
const { funcion } = await import("./funcion.js");
funcion();
}
// Cargar basado en acción del usuario
boton.addEventListener("click", async () => {
const { mostrarModal } = await import("./modal.js");
mostrarModal();
});
// Con manejo de errores
try {
const modulo = await import(`./locales/${idioma}.js`);
aplicarTraducciones(modulo.traducciones);
} catch (error) {
console.error("Falló cargar locale:", error);
}
Usando Módulos en HTML
<!-- Script de módulo (diferido por defecto) -->
<script type="module" src="main.js"></script>
<!-- Módulo inline -->
<script type="module">
import { saludar } from "./utils.js";
saludar("Mundo");
</script>
<!-- Fallback para navegadores antiguos -->
<script nomodule src="legacy-bundle.js"></script>
<!-- Precargar módulos para rendimiento -->
<link rel="modulepreload" href="./utils.js">
CommonJS vs Módulos ES
| Característica | CommonJS (CJS) | Módulos ES (ESM) |
|---|---|---|
| Sintaxis | require() / module.exports |
import / export |
| Carga | Síncrona | Asíncrona |
| Análisis | Dinámico (runtime) | Estático (compile time) |
| Tree shaking | Difícil | Soporte integrado |
| Navegador | Necesita bundler | Soporte nativo |
Patrones de Módulos
// Patrón singleton
let instancia = null;
export function obtenerInstancia() {
if (!instancia) {
instancia = crearInstancia();
}
return instancia;
}
// Factory con estado privado
const datosPrivados = new WeakMap();
export class Usuario {
constructor(nombre) {
datosPrivados.set(this, { nombre });
}
obtenerNombre() {
return datosPrivados.get(this).nombre;
}
}
// Módulo de configuración
export const config = Object.freeze({
API_URL: "https://api.ejemplo.com",
TIMEOUT: 5000,
VERSION: "1.0.0"
});
// Import solo por efectos secundarios
// analytics.js
console.log("Analytics cargado");
window.analytics = { track: () => {} };
// main.js
import "./analytics.js"; // Solo ejecuta el módulo
ESM en Node.js
// package.json - Habilitar ESM para todo el proyecto
{
"type": "module"
}
// O usar extensión .mjs para archivos ESM
// utils.mjs
// Importar JSON (requiere aserción)
import datos from "./datos.json" with { type: "json" };
// Importar desde node_modules
import express from "express";
import { useState } from "react";
// Built-ins de Node
import fs from "node:fs";
import path from "node:path";
// Equivalente de __dirname en ESM
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// import.meta
console.log(import.meta.url); // file:///ruta/al/modulo.js
💡 Puntos Clave
- • Usa exports nombrados para múltiples valores, default para export principal
- • import() dinámico para code splitting y lazy loading
- • Archivos barrel (index.js) simplifican imports
- • ESM habilita tree shaking para bundles más pequeños
- • Los módulos son singletons y siempre usan modo estricto
- • Usa
"type": "module"en package.json para ESM en Node.js
Práctica y tips de migración
- • Convierte un archivo CommonJS a ESM y lista los cambios de ruta de import que necesitaste (extensión, default vs nombrado).
- • Añade un
import()dinámico para un modal o gráfico; mide tamaño de bundle y tiempo de carga inicial. - • Crea un archivo barrel (index.ts) para una carpeta y asegúrate de que tree-shaking aún elimina exports no usados.
- • En Node, experimenta con
"type": "module"vs extensiones.cjs/.mjsy nota las advertencias de interop.