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
importal 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.14159Los 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)); // 20Dado 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';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.14159Cada 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/exportsolo funcionan en el nivel superior de un módulo — no dentro de un bloqueifo una función. Usaimport()dinámico para la carga condicional.- Las importaciones son enlaces en vivo de solo lectura. Puedes leer un valor importado, pero no reasignarlo;
add = 5sobre una importación lanza un error. - Una exportación por defecto no tiene nombre mágico.
export default addexporta el valor, no el nombreadd; 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
- Prefiere las exportaciones con nombre para que las importaciones se autodocumenten y el tree-shaking funcione.
- Una responsabilidad por módulo — un archivo debería ser fácil de describir en una oración.
- Usa los archivos barrel con moderación — ordenan las importaciones pero pueden perjudicar el tree-shaking si se abusa de ellos.
- Carga de forma diferida el código pesado o poco usado con
import()dinámico. - 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.