Saltar al contenido principal
10Módulos

Importaciones Dinámicas

Clase 3 de 45 min de lectura

¿Para qué descargar un editor de texto entero si el usuario quizá nunca llegue a abrirlo? Las importaciones que conoces, las estáticas, se cargan siempre, las uses o no en ese momento.

Las importaciones dinámicas cambian eso: cargan un módulo bajo demanda, justo cuando lo necesitas.

Import estático vs dinámico#

Import estático (lo que ya conoces)

// Se carga SIEMPRE al inicio, antes de ejecutar nadaimport { sumar } from './operaciones.js'

Sus reglas son estrictas:

  • Va en el nivel superior del archivo (y, por convención, al principio).
  • Se carga antes de ejecutar cualquier código del módulo.
  • No puede ir dentro de un if, un for o una función.
  • Se puede analizar sin ejecutar: el navegador conoce las dependencias de antemano.

Import dinámico: import()

El import dinámico se escribe import(), como si fuera una función, y devuelve una Promesa:

// Se carga solo cuando ejecutas esta líneaconst modulo = await import('./operaciones.js')modulo.sumar(2, 3) // -> 5

Aquí las reglas se relajan:

  • Puede ir en cualquier parte del código: dentro de funciones, condicionales...
  • Devuelve una Promesa que se resuelve con el módulo.
  • El módulo se carga solo cuando se ejecuta esa línea.

Sintaxis del import dinámico#

Como import() devuelve una Promesa, lo usas igual que cualquier otra: con async/await o con .then().

Con async/await

La promesa se resuelve con el módulo, así que puedes desestructurarlo directamente:

async function cargarModulo() {  const { sumar, restar } = await import('./operaciones.js')  console.log(sumar(2, 3))  // -> 5  console.log(restar(10, 4)) // -> 6}cargarModulo()

Con .then()

Lo mismo, recibiendo el módulo como parámetro:

import('./operaciones.js').then(modulo => {  console.log(modulo.sumar(2, 3))  // -> 5  console.log(modulo.restar(10, 4)) // -> 6})

Importar un default export dinámicamente

Ojo aquí: el export default está en la propiedad .default del módulo.

const modulo = await import('./Usuario.js')const Usuario = modulo.defaultconst user = new Usuario('Ana')

Puedes ahorrarte una línea desestructurando y renombrando a la vez:

const { default: Usuario } = await import('./Usuario.js')const user = new Usuario('Ana')

Casos de uso prácticos#

1. Carga condicional

Cargar un módulo solo si se cumple una condición. Por ejemplo, las traducciones según el idioma del navegador:

const idioma = navigator.languageif (idioma.startsWith('es')) {  const { saludos } = await import('./i18n/es.js')  console.log(saludos.bienvenida) // -> '¡Hola!'} else {  const { saludos } = await import('./i18n/en.js')  console.log(saludos.bienvenida) // -> 'Hello!'}

Con imports estáticos tendrías que cargar todos los idiomas, aunque cada usuario solo use uno.

2. Carga bajo demanda (lazy loading)

Cargar una funcionalidad pesada solo cuando el usuario la pide. Fíjate en que el import() está dentro del manejador del clic:

// Solo cargamos el editor cuando el usuario hace clicconst boton = document.querySelector('#abrir-editor')boton.addEventListener('click', async () => {  const { crearEditor } = await import('./editor.js')  crearEditor()})

Quien no pulse el botón no descarga el editor. Ideal para editores de texto, gráficas complejas o funcionalidades que no usan todos los usuarios.

3. Rutas dinámicas

Construir la ruta del módulo con variables, algo imposible con el import estático:

async function cargarPagina(nombrePagina) {  const modulo = await import(`./paginas/${nombrePagina}.js`)  modulo.renderizar()}cargarPagina('inicio')    // carga ./paginas/inicio.jscargarPagina('contacto')  // carga ./paginas/contacto.js

4. Módulos opcionales con fallback

Si la carga falla, la promesa se rechaza. Y eso se captura con un try/catch, como cualquier otro error asíncrono:

async function cargarAnalytics() {  try {    const { iniciar } = await import('./analytics.js')    iniciar()  } catch (error) {    console.warn('Analytics no disponible:', error.message)    // La aplicación sigue funcionando sin analytics  }}

Import dinámico y rendimiento#

Todo lo anterior busca que el usuario descargue menos código al entrar. La técnica se llama code splitting (división de código):

Infografía
Cargar código solo cuando hace falta
5.graficas.js y admin.js no se descargan nunca si nadie los necesita. Eso es el code splitting.
Paso 5 de 5

Diferencias clave entre import estático y dinámico#

Característicaimport estáticoimport() dinámico
Sintaxisimport { x } from './mod.js'await import('./mod.js')
UbicaciónSolo en el nivel superior del archivoEn cualquier parte
CargaSiempre, antes de ejecutarSolo cuando se ejecuta la línea
ResultadoLos valores directamenteUna Promesa
RutaSolo strings literalesPuede usar variables
AnálisisEstático (optimizable)Dinámico (en runtime)

Cuándo usar cada uno#

  • Import estático: para lo que necesitas siempre. Es la opción por defecto y la más sencilla.
  • Import dinámico: para código que no siempre hace falta, como funcionalidades opcionales, traducciones o módulos pesados.

En resumen

  • import() carga un módulo bajo demanda y devuelve una Promesa.
  • Puede ir en cualquier parte del código y su ruta puede usar variables.
  • El default export llega en la propiedad .default.
  • Sirve para carga condicional, lazy loading y code splitting.
  • El import estático sigue siendo la opción por defecto.