Módulos ES en JavaScript: import, export y módulos dinámicos con import()

Los módulos ES (ESM) son el sistema oficial de módulos de JavaScript, estandarizado en ES2015. Permiten dividir el código en archivos separados con ámbito propio, importar y exportar solo lo necesario, y cargar dependencias de forma estática o dinámica. Hoy en día son el estándar tanto en navegadores como en Node.js.

Named exports y default export

Un módulo puede exportar múltiples named exports y un único default export. Ambos pueden coexistir en el mismo archivo:

// utils.js — named exports
export function sumar(a, b) { return a + b; }
export function restar(a, b) { return a - b; }

export const PI = 3.14159;

export class Vector {
  constructor(x, y) { this.x = x; this.y = y; }
  magnitud() { return Math.sqrt(this.x ** 2 + this.y ** 2); }
}

// También se puede exportar al final:
function multiplicar(a, b) { return a * b; }
export { multiplicar };

// formato.js — default export (solo uno por módulo)
export default function formatearMoneda(cantidad, moneda = 'EUR') {
  return new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency: moneda
  }).format(cantidad);
}

Importar: named, default, alias y namespace

// Named imports
import { sumar, restar, PI } from './utils.js';
console.log(sumar(2, 3));  // 5

// Alias para evitar colisiones
import { sumar as add, Vector as Vec } from './utils.js';

// Default import (el nombre puede ser cualquiera)
import formatearPrecio from './formato.js';
console.log(formatearPrecio(29.99));  // '29,99 €'

// Combinar default y named
import formatearPrecio, { sumar, PI } from './modulo.js';

// Namespace import: todo en un objeto
import * as Matematicas from './utils.js';
console.log(Matematicas.sumar(1, 2));  // 3
console.log(Matematicas.PI);           // 3.14159

Reexportación: crear puntos de entrada centralizados

Puedes reexportar desde un módulo índice para que los consumidores importen desde un único lugar:

// componentes/Boton.js
export default class Boton { ... }
export function crearBoton(config) { ... }

// componentes/Formulario.js
export default class Formulario { ... }

// componentes/index.js — punto de entrada centralizado
export { default as Boton, crearBoton } from './Boton.js';
export { default as Formulario } from './Formulario.js';
export * from './helpers.js';  // Reexportar todo

// En el código de aplicación, importar desde el índice:
import { Boton, Formulario } from './componentes/index.js';

import() dinámico: lazy loading

import() como función devuelve una Promise y permite cargar módulos de forma dinámica, solo cuando se necesitan:

// Cargar un módulo solo cuando el usuario hace clic
boton.addEventListener('click', async () => {
  const modulo = await import('./editor-pesado.js');
  modulo.inicializar(contenedor);
});

// Código splitting: cargar la ruta solo cuando se navega
async function cargarRuta(ruta) {
  switch (ruta) {
    case '/dashboard':
      const { Dashboard } = await import('./vistas/Dashboard.js');
      return new Dashboard();

    case '/perfil':
      const { Perfil } = await import('./vistas/Perfil.js');
      return new Perfil();
  }
}

// Import condicional:
const modulo = await import(
  isDev ? './utilidades-debug.js' : './utilidades.js'
);

Top-level await en módulos

En módulos ES, await puede usarse en el nivel superior del archivo. Los módulos que lo importen esperarán a que se resuelva:

// config.js — top-level await
const respuesta = await fetch('/api/config');
export const config = await respuesta.json();

// app.js — espera a que config.js esté listo
import { config } from './config.js';
console.log(config.tema);  // Disponible sin await adicional

Soporte para navegadores antiguos: module/nomodule

El atributo type="module" solo lo entienden los navegadores modernos. El patrón module/nomodule permite servir código moderno a navegadores modernos y código compilado (bundles) a los antiguos:

// En HTML:
// <script type="module" src="app.modern.js"></script>
// <script nomodule src="app.legacy.js"></script>
// Los navegadores modernos ignoran nomodule; los antiguos ignoran type="module"

// Detectar si el entorno soporta módulos ES:
const supModulos = 'noModule' in HTMLScriptElement.prototype;

Diferencias clave de los módulos ES respecto a los scripts normales: tienen su propio scope (no contaminan el global), se ejecutan en modo estricto automáticamente, los imports se resuelven de forma estática antes de ejecutar, y cada módulo se carga y evalúa solo una vez independientemente de cuántas veces se importe.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP