Módulos

Organiza tu código con el sistema de módulos ES: export, import, módulos dinámicos y por qué importan.

Antes de los módulos, todo el JavaScript de una página compartía un único ámbito global. Dos scripts con una variable usuario colisionaban silenciosamente. Los módulos ES solucionan esto: cada archivo tiene su propio ámbito y controla explícitamente qué expone.


export nombrado

Exporta uno o varios valores por nombre. El importador debe usar el mismo nombre (o un alias):

// utils/matematicas.js
export function sumar(a, b) {
  return a + b;
}

export function restar(a, b) {
  return a - b;
}

export const PI = 3.14159;
// Importar solo lo que necesitas
import { sumar, PI } from './utils/matematicas.js';

console.log(sumar(2, 3));  // 5
console.log(PI);           // 3.14159

// Con alias
import { sumar as add } from './utils/matematicas.js';
add(2, 3);

export default

Cada módulo puede tener un único export default. No necesita nombre al importarlo:

// componentes/Boton.js
export default function Boton({ texto, onClick }) {
  const btn = document.createElement('button');
  btn.textContent = texto;
  btn.addEventListener('click', onClick);
  return btn;
}
// Importar default — el nombre lo eliges tú
import Boton from './componentes/Boton.js';
import MiBoton from './componentes/Boton.js';  // mismo módulo, nombre distinto

// Combinar default y nombrados en un import
import Boton, { TAMANIOS } from './componentes/Boton.js';

Re-exportar

Útil para crear un barrel file que centralice las exportaciones de un directorio:

// utils/index.js — punto de entrada del directorio
export { sumar, restar } from './matematicas.js';
export { formatearFecha } from './fechas.js';
export { validarEmail } from './validaciones.js';

// O re-exportar todo
export * from './matematicas.js';

// Re-exportar el default con nombre
export { default as Boton } from './componentes/Boton.js';
// Ahora el importador usa un solo import
import { sumar, formatearFecha, Boton } from './utils/index.js';

Importaciones y el ámbito de módulo

// Los módulos tienen su propio ámbito — esto es privado
const conexionBD = crearConexion();  // no se puede acceder desde fuera

// Solo lo exportado es accesible
export function buscarUsuario(id) {
  return conexionBD.query(`SELECT * FROM usuarios WHERE id = ?`, [id]);
}

Las importaciones son vistas en vivo del valor exportado, no copias. Si el módulo exportador cambia el valor, el importador ve el cambio:

// contador.js
export let cuenta = 0;
export function incrementar() { cuenta++; }
// main.js
import { cuenta, incrementar } from './contador.js';
console.log(cuenta);  // 0
incrementar();
console.log(cuenta);  // 1 — actualizado automáticamente

Importación dinámica

A veces no sabes qué módulo cargar hasta el momento de ejecución, o quieres cargar código solo cuando se necesita (lazy loading):

// import() es asíncrono — devuelve una promesa
async function cargarGrafica() {
  // El módulo no se descarga hasta que se llama a esta función
  const { dibujarGrafica } = await import('./lib/grafica.js');
  dibujarGrafica(datos);
}

// Cargar según la acción del usuario
btn.addEventListener('click', async () => {
  const modulo = await import('./modal.js');
  modulo.abrirModal();
});

Esto es fundamental en frameworks como Astro, React o Vue para dividir el bundle en trozos menores (code splitting).


Módulos en el navegador

<!-- Atributo type="module" activa el sistema de módulos -->
<script type="module" src="./main.js"></script>

<!-- Las importaciones dentro del script también funcionan -->
<script type="module">
  import { saludar } from './utils.js';
  saludar('mundo');
</script>

Diferencias clave con scripts normales:

  • Tienen defer por defecto (no bloquean el HTML)
  • Tienen su propio ámbito — no contaminan window
  • Las rutas deben ser explícitas (./utils.js, no utils)
  • Se cachean por separado en el navegador

Módulos en Node.js

Node usa módulos CommonJS (require/module.exports) por defecto. Para usar ES Modules:

// package.json
{
  "type": "module"
}

O usando la extensión .mjs en lugar de .js.

// ES Module en Node
import { readFile } from 'node:fs/promises';
const contenido = await readFile('./datos.txt', 'utf-8');

// CommonJS — sigue siendo válido en archivos .cjs
const { readFileSync } = require('fs');

Convenciones

src/
  utils/
    fechas.js          # funciones de fecha — export nombrado
    validaciones.js    # validadores — export nombrado
    index.js           # barrel file — re-exporta todo
  componentes/
    Boton.js           # componente — export default
    Modal.js
  servicios/
    api.js             # capa de comunicación con el servidor
  • Funciones utilitarias → export nombrado
  • Clases o componentes únicos → export default
  • Un directorio grande → barrel file con index.js

En la siguiente lección dominamos la desestructuración y el spread: extraer valores de objetos y arrays con claridad.