ESM avanzado en JavaScript: dynamic import(), import.meta, import maps y module federation

El sistema de módulos ES (ESM) se ha convertido en el estándar nativo de JavaScript, soportado en todos los navegadores modernos y en Node.js desde la versión 12. Más allá del import y export básico, ESM ofrece herramientas para cargar código bajo demanda, conocer la ubicación del módulo en ejecución, mapear dependencias sin bundler y compartir código entre aplicaciones.

Dynamic import(): carga bajo demanda

import() es una función (no una palabra clave) que carga un módulo de forma asíncrona y devuelve una promesa que resuelve al objeto de exportaciones. Permite dividir tu aplicación en trozos que solo se cargan cuando se necesitan.

// Carga el módulo de gráficos solo cuando el usuario abre el panel
document.getElementById('btn-graficos').addEventListener('click', async () => {
  const { renderizarGrafico } = await import('./modulos/graficos.js');
  renderizarGrafico(datos);
});

// Importación condicional según idioma
const locale = navigator.language.startsWith('es') ? 'es' : 'en';
const { t } = await import(`./i18n/${locale}.js`);
console.log(t('bienvenida'));

En bundlers como Vite o Webpack, cada import() dinámico genera un chunk separado que el navegador descarga solo cuando lo necesita, reduciendo el tiempo de carga inicial.

import.meta: metadatos del módulo

import.meta es un objeto especial que contiene información sobre el módulo en ejecución. La propiedad más útil en el navegador es import.meta.url, que contiene la URL absoluta del módulo actual.

// Construir rutas relativas al módulo, no a la página
const workerUrl = new URL('./worker.js', import.meta.url);
const worker = new Worker(workerUrl, { type: 'module' });

// Cargar un asset junto al módulo
const svgUrl = new URL('./icono.svg', import.meta.url).href;
img.src = svgUrl;

// En Vite: acceso a variables de entorno
console.log(import.meta.env.VITE_API_URL);
console.log(import.meta.env.MODE); // "development" o "production"

// En Node.js: equivalente a __dirname
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rutaConfig = join(__dirname, 'config.json');

Import maps: dependencias sin bundler

Los import maps permiten controlar cómo el navegador resuelve los especificadores de módulo, mapeando nombres como 'react' a URLs reales. Esto elimina la necesidad de un bundler para proyectos simples o para desarrollo.

// En HTML, antes del primer módulo:
// <script type="importmap">
// {
//   "imports": {
//     "lodash": "https://cdn.skypack.dev/lodash",
//     "lodash/": "https://cdn.skypack.dev/lodash/",
//     "utils": "./src/utils.js"
//   }
// }
// </script>

// Ahora en cualquier módulo puedes usar:
import { debounce } from 'lodash';
import { formatearFecha } from 'utils';

Top-level await y módulos diferidos

Los módulos ESM se evalúan en orden de dependencia. Con top-level await puedes inicializar un módulo de forma asíncrona antes de que los módulos que lo importan lo usen:

// db.js — inicialización asíncrona al nivel raíz
const config = await fetch('/api/config').then(r => r.json());
export const db = await conectar(config.dbUrl);
// db está completamente inicializado cuando otros módulos lo importan

// app.js
import { db } from './db.js'; // espera a que db.js termine
const usuarios = await db.query('SELECT * FROM usuarios');

Import attributes: metadatos en la importación

La propuesta de Import Attributes (Stage 3 en TC39, ya disponible en Chrome y Node.js) permite indicar el tipo esperado del módulo al importarlo, habilitando la importación de JSON, CSS y WebAssembly como módulos nativos:

// Importar JSON como módulo (sin fetch manual)
import config from './config.json' with { type: 'json' };
console.log(config.apiUrl);

// Importar CSS como objeto CSSStyleSheet (para adoptedStyleSheets)
import estilos from './componente.css' with { type: 'css' };
document.adoptedStyleSheets = [estilos];

// Importar WebAssembly
import * as wasm from './calculo.wasm' with { type: 'webassembly-module' };

Los import attributes añaden un segundo argumento a import() dinámico con la misma sintaxis: await import('./data.json', { with: { type: 'json' } }). La especificación reemplaza la sintaxis anterior con assert, que quedó obsoleta, así que asegúrate de usar with en código nuevo.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP