CAP 10 · LEC 03·Módulos

Dynamic imports: carga código cuando lo necesitas

Los static imports se resuelven antes de que el código corra. Los dynamic imports devuelven una Promise y permiten cargar módulos en el momento exacto que los necesitas — clave para mejorar el tiempo de carga inicial de tu app.

● INTERMEDIO7 min lectura3 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

Imports estáticos vs dinámicos

Los imports estáticos que ya conoces se procesan en tiempo de compilación: el bundler los analiza y el navegador los descarga al inicio. Los dinámicos se resuelven en tiempo de ejecución.

// ESTÁTICO — se carga siempre, al inicio, sin importar si se usa import { parse } from "./csv-parser.js"; // 200 KB que quizás no usas // DINÁMICO — se carga solo cuando se llama esta función async function exportarCSV(datos) { // El módulo se descarga aquí, la primera vez que el usuario exporta const { parse } = await import("./csv-parser.js"); return parse(datos); } // Comparación de timing console.log("Inicio de app"); // Se ejecuta inmediatamente // El import dinámico es una Promise const modulo = import("./matematicas.js"); modulo.then(m => { console.log(m.suma(1, 2)); // 3 }); console.log("Esta línea se ejecuta ANTES que la del módulo");
// ESTÁTICO — siempre en el bundle inicial import { parse } from "./csv-parser.js"; // DINÁMICO — carga bajo demanda async function exportarCSV(datos: string[][]): Promise<string> { // TypeScript infiere el tipo del módulo correctamente const { parse } = await import("./csv-parser.js"); return parse(datos); } // La sintaxis import() devuelve Promise<modulo> async function demo(): Promise<void> { const modulo = await import("./matematicas.js"); console.log(modulo.suma(1, 2)); // 3 } demo();
SalidaInicio de app Esta línea se ejecuta ANTES que la del módulo 3

import() — la función de carga asíncrona

import() acepta cualquier expresión de string, no solo literales. Esto permite carga condicional y dinámica real.

// Carga condicional según entorno o configuración async function cargarLogger(entorno) { if (entorno === "desarrollo") { const { DevLogger } = await import("./loggers/dev-logger.js"); return new DevLogger(); } else { const { ProdLogger } = await import("./loggers/prod-logger.js"); return new ProdLogger(); } } // Carga activada por evento de usuario document.getElementById("btnGrafico")?.addEventListener("click", async () => { // chart.js pesa ~200KB — no tiene sentido cargarlo hasta que el usuario lo pida const { Chart } = await import("https://cdn.jsdelivr.net/npm/chart.js"); const ctx = document.getElementById("miCanvas"); new Chart(ctx, { type: "bar", data: { /* ... */ } }); }); // Carga de módulo cuyo nombre se construye dinámicamente async function cargarTema(nombre) { const tema = await import(`./temas/${nombre}.js`); return tema.default; }
type Entorno = "desarrollo" | "produccion"; async function cargarLogger(entorno: Entorno) { if (entorno === "desarrollo") { const { DevLogger } = await import("./loggers/dev-logger.js"); return new DevLogger(); } else { const { ProdLogger } = await import("./loggers/prod-logger.js"); return new ProdLogger(); } } // Tipado explícito del módulo dinámico type ModuloPago = typeof import("./pagos/stripe.js"); async function procesarPago(monto: number): Promise<void> { const modulo: ModuloPago = await import("./pagos/stripe.js"); await modulo.cobrar(monto); } // Tema dinámico con tipo conocido interface Tema { colores: Record<string, string>; tipografia: string; } async function cargarTema(nombre: string): Promise<Tema> { const tema = await import(`./temas/${nombre}.js`); return tema.default as Tema; }
Bundlers y strings dinámicos

Webpack y Vite pueden analizar patrones de template literals simples como `./temas/${nombre}.js` y pre-generar los chunks. Evita construir rutas con lógica compleja para mantener esta optimización.

Code splitting — divide para cargar más rápido

El code splitting es la técnica de dividir el bundle en fragmentos (chunks) que se cargan solo cuando se necesitan. Los dynamic imports son el mecanismo que lo hace posible.

// router.js — cada ruta carga su módulo solo cuando se visita const rutas = { "/": () => import("./paginas/Inicio.js"), "/perfil": () => import("./paginas/Perfil.js"), "/editor": () => import("./paginas/Editor.js"), // pesado: Monaco Editor "/reportes": () => import("./paginas/Reportes.js"), // pesado: Chart.js + D3 }; async function navegar(ruta) { const cargador = rutas[ruta]; if (!cargador) { console.error("Ruta no encontrada"); return; } // Muestra skeleton mientras carga document.body.innerHTML = '<div class="skeleton">Cargando...</div>'; const modulo = await cargador(); modulo.default.render(document.body); } // Solo al visitar /editor se descarga Monaco Editor navegar("/editor");
// router.ts — enrutador con lazy loading tipado interface Pagina { render(contenedor: HTMLElement): void; } type CargadorPagina = () => Promise<{ default: Pagina }>; const rutas: Record<string, CargadorPagina> = { "/": () => import("./paginas/Inicio.js"), "/perfil": () => import("./paginas/Perfil.js"), "/editor": () => import("./paginas/Editor.js"), "/reportes": () => import("./paginas/Reportes.js"), }; async function navegar(ruta: string): Promise<void> { const cargador = rutas[ruta]; if (!cargador) { throw new Error(`Ruta no encontrada: ${ruta}`); } document.body.innerHTML = '<div class="skeleton">Cargando...</div>'; const { default: pagina } = await cargador(); pagina.render(document.body); } export { navegar };

Dynamic imports en TypeScript — inferencia de tipos

TypeScript infiere automáticamente el tipo del módulo cargado dinámicamente. No necesitas anotaciones manuales en la mayoría de los casos.

// modulo-pesado.ts export function procesarImagenes(rutas: string[]): Promise<Blob[]> { return Promise.resolve(rutas.map(() => new Blob())); } export const VERSION = "2.0.0"; // --- main.ts --- async function cargarYProcesar(archivos: string[]) { // TypeScript infiere: { procesarImagenes: Function, VERSION: string } const modulo = await import("./modulo-pesado.js"); // Autocompletado y verificación de tipos funcionan correctamente const blobs = await modulo.procesarImagenes(archivos); console.log(`Versión: ${modulo.VERSION}`); return blobs; } // Importar el TIPO del módulo sin cargarlo en runtime type ModuloPesado = typeof import("./modulo-pesado.js"); // Útil para anotar variables que recibirán el módulo más tarde let moduloCacheado: ModuloPesado | null = null; async function obtenerModulo(): Promise<ModuloPesado> { if (!moduloCacheado) { moduloCacheado = await import("./modulo-pesado.js"); } return moduloCacheado; }
Error handling en dynamic imports

Un import dinámico puede fallar (red caída, módulo no encontrado). Siempre envuelve await import() en un bloque try/catch en código de producción.

Practica