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');
