Importar librerías dinámicamente en JavaScript

Cómo importar librerías dinámicamente en JavaScript y Node.js

imports-dinamicos-javascript

En algunos proyectos, nos encontramos con necesidades particulares: quizás no queremos cargar una librería pesada en el cliente, o tal vez una dependencia solo tiene sentido en el entorno del servidor. En esos casos, la importación dinámica nos permite tener más control sobre el código que se ejecuta y cuándo lo hace.

JavaScript, desde la especificación de los módulos ECMAScript (ESM), nos da una herramienta muy útil para esto: import(). Esta función nos permite cargar módulos de forma asíncrona, solo cuando los necesitamos.

¿Qué significa importar dinámicamente?

Cuando usamos import de forma tradicional, todo el contenido del módulo se carga al inicio del programa. Pero si usamos la versión dinámica (import()), el módulo se carga en tiempo de ejecución.

Esto abre la puerta a comportamientos condicionales y a mejoras reales en rendimiento o separación de entornos.

// Importación estática (siempre se ejecuta al cargar el archivo)
import fs from 'fs';
// Importación dinámica (solo si se cumple la condición)
if (import.meta.env.SSR) {
const fs = await import('fs');
}

¿Por qué nos interesa?

Hay varios motivos por los que podríamos querer cargar una librería de forma dinámica:

  • No queremos que una dependencia llegue al cliente si solo la usamos en el backend.
  • Algunas librerías dependen de características que solo existen en el entorno de Node.js.
  • A veces una librería es pesada y solo se usa en casos específicos. Mejor cargarla solo cuando sea necesario.

Una situación común es trabajar con frameworks como Vite, donde import.meta.env.SSR nos indica si estamos del lado del servidor. Podemos usarlo para condicionar la carga de ciertos paquetes que sólo queremos ejecutar en el lado del servidor:

let logger;
if (import.meta.env.SSR) {
const winston = await import('winston');
logger = winston.createLogger({
transports: [new winston.transports.Console()],
});
}

De esta manera, winston no se incluye en el bundle del cliente.

Algunas precauciones

Aunque suena práctico, hay detalles que conviene tener presentes:

  • import() devuelve una Promesa, así que debemos usar await o .then().
  • No podemos usarlo fuera de funciones async o bloques que acepten await.
  • No se puede hacer destructuring directamente sobre la importación:
// Esto no funciona
const { readFile } = await import('fs');
// Esto sí
const fs = await import('fs');
const readFile = fs.readFile;

También es útil recordar que no todos los entornos están preparados para import() si trabajamos con CommonJS o si no hemos activado ESM correctamente.

En resumen

Importar módulos dinámicamente en JavaScript no es una técnica nueva, pero sí una que a menudo pasamos por alto. Nos da flexibilidad, nos ayuda a optimizar nuestros proyectos, y nos permite escribir código adecuado según el entorno en que se ejecuta.