Ir al contenido principal

Módulos ES6

Introducción

Los módulos ES6 representan una de las características más importantes introducidas en ECMAScript 2015 (ES6), ya que proporcionan un mecanismo nativo para organizar y compartir código entre diferentes archivos JavaScript. Antes de su introducción, los desarrolladores utilizaban diferentes patrones y bibliotecas externas para simular la modularidad, lo que frecuentemente resultaba en código menos mantenible y propenso a errores. Con los módulos ES6, JavaScript incorpora finalmente un sistema de módulos estandarizado directamente en el lenguaje, permitiendo estructurar aplicaciones complejas de manera más clara y profesional.

En este artículo aprenderás a utilizar el sistema de módulos ES6, incluyendo cómo importar y exportar código, las diferencias entre exportaciones por defecto y nombradas, y técnicas avanzadas como las importaciones dinámicas. Dominar los módulos ES6 te permitirá construir aplicaciones JavaScript mejor organizadas, más mantenibles y escalables.

Sintaxis de import y export

Los módulos ES6 se basan principalmente en dos declaraciones: export para compartir código desde un módulo e import para utilizar código de otros módulos.

Exportaciones básicas

Para hacer que una función, variable o clase esté disponible fuera de su archivo, usamos la palabra clave export:

// matematicas.js

// Exportando funciones individuales
export function sumar(a, b) {
  return a + b;
}

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

// Variables que también podemos exportar
export const PI = 3.14159;

También es posible declarar primero los elementos y exportarlos después:

// matematicas.js

function multiplicar(a, b) {
  return a * b;
}

function dividir(a, b) {
  if (b === 0) {
    throw new Error("No se puede dividir por cero");
  }
  return a / b;
}

const E = 2.71828;

// Exportación agrupada al final
export { multiplicar, dividir, E };

Importaciones básicas

Para utilizar código exportado desde otros módulos, usamos la palabra clave import:

// calculadora.js

// Importamos elementos específicos del módulo matemáticas
import { sumar, restar, PI } from './matematicas.js';

console.log(sumar(5, 3));  // 8
console.log(restar(10, 4));  // 6
console.log(PI);  // 3.14159

Exportaciones por defecto vs. nombradas

Los módulos ES6 ofrecen dos tipos de exportaciones: nombradas y por defecto.

Exportación por defecto

La exportación por defecto permite designar un valor principal para el módulo. Cada archivo puede tener solo una exportación por defecto:

// usuario.js

class Usuario {
  constructor(nombre, email) {
    this.nombre = nombre;
    this.email = email;
  }
  
  saludar() {
    return `¡Hola, soy ${this.nombre}!`;
  }
}

// Solo puede haber una exportación por defecto por archivo
export default Usuario;

Para importar una exportación por defecto:

// app.js

// No necesitamos llaves {} y podemos usar cualquier nombre
import User from './usuario.js';

const usuario = new User('Ana', 'ana@ejemplo.com');
console.log(usuario.saludar());  // ¡Hola, soy Ana!

Exportaciones nombradas

Las exportaciones nombradas permiten exportar múltiples valores desde un módulo:

// validaciones.js

export function validarEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

export function validarPassword(password) {
  return password.length >= 8;
}

Para importar exportaciones nombradas:

// formulario.js

// Debemos usar exactamente el mismo nombre y con llaves {}
import { validarEmail, validarPassword } from './validaciones.js';

console.log(validarEmail('test@example.com'));  // true
console.log(validarPassword('abc123'));  // false (menos de 8 caracteres)

Importación selectiva de componentes

Podemos importar solo los componentes específicos que necesitamos de un módulo, lo que hace nuestro código más eficiente:

// Solo importamos lo que necesitamos
import { sumar, PI } from './matematicas.js';

// Usamos solo lo que importamos
console.log(sumar(PI, 10));  // 13.14159

Alias en importaciones y exportaciones

Podemos utilizar alias para renombrar los elementos durante la importación o exportación, lo que nos ayuda a evitar conflictos de nombres o a simplificar nombres largos.

Alias en exportaciones

// utilidades.js

function obtenerFechaActualFormateada() {
  const fecha = new Date();
  return `${fecha.getDate()}/${fecha.getMonth() + 1}/${fecha.getFullYear()}`;
}

// Exportamos con un alias más corto
export { obtenerFechaActualFormateada as fechaActual };

Alias en importaciones

// dashboard.js

// Importamos usando un alias
import { validarEmail as validarCorreo } from './validaciones.js';
import { fechaActual } from './utilidades.js';

console.log(validarCorreo('usuario@dominio.com'));  // true
console.log(fechaActual());  // Ejemplo: 18/4/2025

Importaciones dinámicas

Las importaciones dinámicas permiten cargar módulos bajo demanda, lo que puede mejorar significativamente el rendimiento de nuestra aplicación al cargar solo lo necesario cuando se necesita.

// app.js

const cargarModuloDeIdioma = async (idioma) => {
  try {
    // Importación dinámica basada en una variable
    const modulo = await import(`./idiomas/${idioma}.js`);
    return modulo.default || modulo;
  } catch (error) {
    console.error('Error al cargar el módulo:', error);
    // Cargamos un idioma por defecto si falla
    const moduloPorDefecto = await import('./idiomas/es.js');
    return moduloPorDefecto.default || moduloPorDefecto;
  }
};

// Uso de la importación dinámica
document.getElementById('cambiarIdioma').addEventListener('click', async () => {
  const idioma = document.getElementById('selectIdioma').value;
  const moduloIdioma = await cargarModuloDeIdioma(idioma);
  
  document.getElementById('saludo').textContent = moduloIdioma.saludo;
});

En este ejemplo, cargamos diferentes módulos de idioma dependiendo de la selección del usuario. Esto es especialmente útil para aplicaciones grandes donde no queremos cargar todo el código de una vez.

Ámbito de módulo

Cada módulo ES6 tiene su propio ámbito léxico, lo que significa que las variables, funciones y clases definidas en un módulo no están disponibles globalmente a menos que se exporten explícitamente.

// config.js

// Esta variable solo está disponible dentro de este módulo
const URL_API = 'https://api.ejemplo.com/v1';

// Esta función está disponible para otros módulos
export function obtenerUrlCompleta(endpoint) {
  return `${URL_API}/${endpoint}`;
}

// El resto del código no puede acceder a URL_API directamente

Esto proporciona un nivel de encapsulamiento similar al que encontramos en otros lenguajes orientados a objetos, ayudando a prevenir conflictos de nombres y a proteger detalles de implementación.

Compatibilidad entre navegadores

La compatibilidad con módulos ES6 varía entre navegadores, especialmente en versiones antiguas. Para utilizar módulos ES6 directamente en el navegador:

<!-- index.html -->
<script type="module" src="./app.js"></script>

El atributo type="module" indica al navegador que el script debe tratarse como un módulo ES6. Los módulos se ejecutan en modo estricto por defecto y son tratados como diferidos (similar al atributo defer).

Para navegadores más antiguos, típicamente necesitamos usar herramientas de transpilación como Babel junto con bundlers como Webpack, Rollup o Parcel.

Organización de proyectos con módulos ES6

Los módulos ES6 permiten diferentes estrategias de organización para proyectos JavaScript:

Enfoque por funcionalidad

Organizando los módulos por la funcionalidad que proporcionan:

proyecto/
├── src/
│   ├── auth/
│   │   ├── login.js
│   │   ├── registro.js
│   │   └── index.js
│   ├── utils/
│   │   ├── formateo.js
│   │   ├── validacion.js
│   │   └── index.js
│   └── app.js

Archivos de barril (barrel files)

Los "archivos de barril" son módulos que re-exportan desde varios otros módulos, simplificando las importaciones:

// utils/index.js

// Re-exportamos todo desde los módulos individuales
export * from './formateo.js';
export * from './validacion.js';

Esto nos permite importar de manera más limpia:

// Antes
import { formatearFecha } from './utils/formateo.js';
import { validarEmail } from './utils/validacion.js';

// Después, con archivos de barril
import { formatearFecha, validarEmail } from './utils';

Resumen

Los módulos ES6 representan un avance fundamental en JavaScript, proporcionando un sistema de módulos nativo que permite organizar el código de manera más estructurada y mantenible. Con la sintaxis de import y export, podemos compartir código entre archivos de manera clara y organizada, evitando la contaminación del ámbito global y facilitando la creación de aplicaciones complejas.

A través de características como las exportaciones por defecto y nombradas, los alias, y las importaciones dinámicas, los módulos ES6 ofrecen una gran flexibilidad para diferentes escenarios de desarrollo. En la actualidad, estos módulos son ampliamente compatibles con los navegadores modernos y, con las herramientas adecuadas, pueden utilizarse incluso en entornos más antiguos, convirtiéndose en una parte esencial del desarrollo JavaScript moderno.