W3docs

JavaScript export e import

Aprende export e import en JavaScript: exportaciones con nombre, por defecto, alias con as, importaciones dinámicas, reexportación y errores comunes, con ejemplos ejecutables.

En el JavaScript moderno, las palabras clave export e import son la forma de dividir un programa en varios archivos y luego unirlos. Un archivo que las usa es un módulo ES: en lugar de volcar todo en un único script gigante, mantienes cada funcionalidad en su propio archivo, declaras lo que comparte con el mundo exterior (export) y solo importas lo que necesitas en otro lugar (import).

Esta guía cubre todas las formas que encontrarás en la práctica — exportaciones con nombre, exportaciones por defecto, renombrado con as, importaciones de espacio de nombres, reexportación e import() dinámico — y los errores comunes que suelen confundir. Cada ejemplo es pequeño y ejecutable.

¿Por qué usar módulos?

Antes de los módulos ES, compartir código significaba adjuntar cosas al objeto global y esperar que los nombres no colisionaran. Los módulos resuelven esto:

  • Encapsulación — cualquier cosa que no exportes permanece privada al archivo.
  • Dependencias explícitas — las líneas import al inicio de un archivo son una lista precisa de lo que necesita.
  • Evaluación única — el código de nivel superior de un módulo se ejecuta una sola vez, sin importar cuántos archivos lo importen; el resultado se almacena en caché y se comparte.

Para ejecutar módulos en el navegador, carga tu archivo de entrada con type="module":

<script src="app.js" type="module"></script>

En Node.js, nombra los archivos con .mjs o establece "type": "module" en package.json.

Exportaciones con nombre

Una exportación con nombre publica un enlace bajo su propio nombre. Un módulo puede tener tantas exportaciones con nombre como desees.

// math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

// You can also list exports at the bottom:
const subtract = (a, b) => a - b;
export { subtract };

Las exportaciones con nombre se importan dentro de llaves, usando exactamente los mismos nombres:

// app.js
import { add, PI } from './math.js';

console.log(add(2, 3)); // 5
console.log(PI);        // 3.14159
Información

Los nombres deben coincidir con la exportación. import { Add } cuando la exportación es add lanza un SyntaxError — no existe tal exportación con nombre.

Renombrado con as

Cuando dos módulos exportan el mismo nombre, o un nombre resulta incómodo, puedes renombrarlo al importar (o al exportar) con as:

// Rename on import
import { add as sum } from './math.js';
console.log(sum(1, 1)); // 2
// Rename on export
const internalAdd = (a, b) => a + b;
export { internalAdd as add };

Exportaciones por defecto

Una exportación por defecto es la única cosa "principal" que proporciona un módulo. Un módulo puede tener como máximo una exportación por defecto.

// multiply.js
export default function multiply(a, b) {
  return a * b;
}

Al importar un valor por defecto, no se usan llaves y tú eliges el nombre local:

// app.js
import multiply from './multiply.js'; // any name works
console.log(multiply(4, 5)); // 20

Dado que el importador elige el nombre, las exportaciones por defecto se usan habitualmente cuando un archivo representa una sola cosa — una única clase, un único componente React, un único objeto de configuración.

Combinar exportaciones por defecto y con nombre

Un módulo puede tener ambas. La exportación por defecto va fuera de las llaves, y las exportaciones con nombre van dentro:

// user.js
export default class User { /* ... */ }
export const ADMIN_ROLE = 'admin';
// app.js
import User, { ADMIN_ROLE } from './user.js';
Información

Prefiere las exportaciones con nombre por defecto. Hacen que las importaciones se autodocumenten y permiten que las herramientas (autocompletado, "buscar referencias", tree-shaking) funcionen de forma fiable. Recurre a una exportación por defecto solo cuando un archivo realmente exporta una sola cosa.

Técnicas de importación

Importación de espacio de nombres (import * as)

Para obtener todo lo que exporta un módulo como un único objeto, usa una importación de espacio de nombres:

// app.js
import * as math from './math.js';

console.log(math.add(5, 3)); // 8
console.log(math.PI);        // 3.14159

Cada exportación con nombre se convierte en una propiedad de math. La exportación por defecto, si la hay, aparece como math.default. Usa esto cuando un módulo agrupa muchos helpers relacionados.

Importación dinámica (import())

La sentencia import anterior es estática — se ejecuta antes que cualquier otra cosa y su ruta debe ser un literal de cadena. A veces quieres cargar un módulo bajo demanda: solo cuando el usuario abre una funcionalidad, o basándote en una condición en tiempo de ejecución. Para eso, usa import() como una función. Devuelve una Promise que se resuelve con el objeto de espacio de nombres del módulo:

// Load a heavy module only when needed
async function openEditor() {
  const editor = await import('./editor.js');
  editor.init();
}

La importación dinámica funciona dentro de funciones y condicionales — lugares donde no se permite un import estático — lo que la convierte en la base de la división de código y la carga diferida.

Reexportación

Un archivo "barrel" puede recopilar exportaciones de varios módulos y reenviarlas, de modo que los consumidores importen desde un único lugar:

// shapes/index.js
export { Circle } from './circle.js';
export { Square } from './square.js';
export { default as Triangle } from './triangle.js'; // re-export a default as a name
// app.js
import { Circle, Square, Triangle } from './shapes/index.js';

Errores comunes

  • Las extensiones importan en el navegador y en Node.js ESM. import { x } from './math' falla; escribe './math.js'.
  • import/export solo funcionan en el nivel superior de un módulo — no dentro de un bloque if o una función. Usa import() dinámico para la carga condicional.
  • Las importaciones son enlaces en vivo de solo lectura. Puedes leer un valor importado, pero no reasignarlo; add = 5 sobre una importación lanza un error.
  • Una exportación por defecto no tiene nombre mágico. export default add exporta el valor, no el nombre add; el importador elige el nombre local.
  • Los módulos se difieren y se ejecutan en modo estricto automáticamente — no es necesario usar 'use strict'.

Un ejemplo completo: un sistema de biblioteca mini

Aquí varios módulos exportan cada uno un array de objetos de libros, y un archivo principal los importa y combina.

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Interactive Library App</title>
</head>
<body>
  <h1>Interactive Library App</h1>
  <button id="loadFiction">Load Fiction</button>
  <button id="loadSciFi">Load Sci-Fi</button>
  <div id="bookList"></div>
  <script src="app.js" type="module"></script>
</body>
</html>

fiction.js:

export const fictionBooks = [
  { title: 'Pride and Prejudice', author: 'Jane Austen' },
  { title: 'To Kill a Mockingbird', author: 'Harper Lee' }
];

sciFi.js:

export const sciFiBooks = [
  { title: 'Dune', author: 'Frank Herbert' },
  { title: 'Neuromancer', author: 'William Gibson' }
];

app.js:

import { fictionBooks } from './fiction.js';
import { sciFiBooks } from './sciFi.js';

function displayBooks(books) {
  const list = document.getElementById('bookList');
  list.innerHTML = '';
  books.forEach(book => {
    const item = document.createElement('div');
    item.textContent = `${book.title} by ${book.author}`;
    list.appendChild(item);
  });
}

document
  .getElementById('loadFiction')
  .addEventListener('click', () => displayBooks(fictionBooks));
document
  .getElementById('loadSciFi')
  .addEventListener('click', () => displayBooks(sciFiBooks));

Cada categoría vive en su propio módulo y exporta una función o datos, y app.js los une. Dividir los datos y el comportamiento de esta manera mantiene cada archivo pequeño y hace que agregar nuevas categorías sea trivial.

Buenas prácticas

  1. Prefiere las exportaciones con nombre para que las importaciones se autodocumenten y el tree-shaking funcione.
  2. Una responsabilidad por módulo — un archivo debería ser fácil de describir en una oración.
  3. Usa los archivos barrel con moderación — ordenan las importaciones pero pueden perjudicar el tree-shaking si se abusa de ellos.
  4. Carga de forma diferida el código pesado o poco usado con import() dinámico.
  5. Incluye siempre las extensiones de archivo en las rutas relativas.

Conclusión

Las exportaciones con nombre te dan múltiples enlaces por archivo, las exportaciones por defecto te dan un único export "principal", e import (con as, * as e import() dinámico) te da control preciso sobre qué entra y cuándo. Juntos, convierten un montón de scripts en un grafo de módulos mantenible y optimizable con tree-shaking. A continuación, descubre cómo se cargan los módulos en el navegador en Módulos, introducción.

Práctica

Práctica
¿Cuáles de las siguientes afirmaciones sobre export e import en JavaScript son correctas?
¿Cuáles de las siguientes afirmaciones sobre export e import en JavaScript son correctas?
Was this page helpful?