wandres.dev
RSS, SITEMAP Y DATOS · feeds y datos derivados

import.meta.glob: datos y assets fuera de las colecciones

Importar muchos ficheros por patrón sin declararlos uno a uno: usar import.meta.glob para traer JSON de datos e imágenes que no viven en content collections, elegir entre la carga eager y la lazy, seleccionar una exportación concreta con import, y obtener imágenes procesadas de astro:assets desde una galería globada.

⏱ 15 min

No todo lo que un sitio deriva de sus ficheros es contenido. Hay JSON de configuración, carpetas de imágenes, fragmentos y datos auxiliares que no merecen una content collection pero que igual quieres importar en bloque. Para eso está import.meta.glob, una función de Vite que trae todos los ficheros que casan con un patrón en un solo gesto, resuelta en el build con análisis estático completo. Es la ergonomía de una carpeta convertida en estructura de datos, pero al nivel del sistema de módulos: el sistema de ficheros pasa a ser una fuente que consultas sin leer nada en tiempo de ejecución.

🎯 Al terminar esta lección sabrás
  • Importar muchos ficheros con import.meta.glob y un patrón literal.
  • Distinguir la carga eager de la lazy y cuándo conviene cada una.
  • Seleccionar una exportación concreta con la opción import.
  • Traer imágenes procesadas de astro:assets desde una galería globada.

import.meta.glob: importar por patrón

import.meta.glob es una función de Vite, no exclusiva de Astro, disponible en cualquier módulo del proyecto. Le pasas un patrón glob relativo al fichero actual y te devuelve un objeto cuyas claves son las rutas encontradas y cuyos valores son cargadores. El patrón tiene que ser una cadena literal: Vite lo analiza en el build para enumerar las coincidencias, así que no puede salir de una variable calculada en tiempo de ejecución.

const modulos = import.meta.glob('./datos/*.json');
// {
//   './datos/a.json': () => import('./datos/a.json'),
//   './datos/b.json': () => import('./datos/b.json'),
// }

Por defecto la carga es diferida: cada valor es una función que devuelve una promesa, y nada se importa hasta que la llamas. Ese diseño habilita el troceado de código —cada fichero puede acabar en su propio bloque, cargado solo cuando se necesita— y es la razón de que el valor no sea el módulo directamente, sino una puerta hacia él.

Las claves del objeto son las rutas tal como casaron, en orden alfabético estable, lo que te da un recorrido predecible sin ordenar nada. Y como todo se resuelve en el build, el patrón admite comodines de carpeta —./datos/**/*.json recorre subdirectorios— y varias extensiones a la vez, como en *.{jpg,png,webp}. Lo que no admite es incertidumbre en tiempo de ejecución: el conjunto de ficheros queda fijado al compilar.

eager frente a lazy

La opción eager invierte ese comportamiento. Con { eager: true }, Vite importa todos los ficheros en el build y los valores del objeto son ya los módulos, accesibles de forma síncrona sin await. Conviene para conjuntos pequeños que siempre necesitas —un puñado de JSON de configuración—. La carga diferida, en cambio, brilla con conjuntos grandes o condicionales, donde importar todo por adelantado hincharía el resultado y prefieres pagar el coste solo por lo que de verdad se use.

const datos = import.meta.glob('./datos/*.json', { eager: true });
for (const ruta in datos) {
  console.log(ruta, datos[ruta].default);
}

La decisión tiene consecuencias medibles cuando el glob se usa en una isla que viaja al cliente: eager mete todos los módulos en el bundle, mientras que la carga diferida deja que el empaquetador los parta y los sirva solo al pedirlos. En el servidor la diferencia es de tiempo de build; en el cliente, de kilobytes que el usuario descarga. Elige eager para lo que siempre necesitas y diferido para lo que solo a veces.

eager

Importa todo en el build. Valores síncronos, acceso directo. Para conjuntos pequeños que siempre usas.

💤

lazy

El valor es una función que devuelve una promesa. Trocea el código y carga bajo demanda. Para conjuntos grandes.

🎯

import y query

import elige una exportación; query con ?url devuelve la URL del asset en vez de su contenido.

ℹ️
eager en el servidor no es JS al cliente

Que la carga sea eager no significa que mandes más JavaScript al navegador. En el frontmatter de un .astro todo corre en el build, en el servidor; la elección entre eager y diferida decide cuándo se resuelve el grafo de módulos, no cuánto código llega al cliente. Para datos que consumes al generar HTML, eager es cómodo y no tiene coste en el navegador. La distinción diferida importa sobre todo cuando el módulo se cargaría en el cliente, dentro de una isla.

Elegir la exportación: import y query

Sin más opciones, cada valor es el espacio de nombres completo del módulo, y accedes a su exportación por defecto con .default. La opción import te ahorra ese paso: { import: 'default' } hace que el valor sea directamente la exportación por defecto, e { import: 'nombre' } una exportación nombrada. Combinada con eager, te da un mapa limpio de ruta a dato. Y { query: '?url', import: 'default' } devuelve la URL del fichero en vez de su contenido, útil para assets que solo quieres enlazar.

const jsons = import.meta.glob('./datos/*.json', {
  eager: true,
  import: 'default',
});
// { './datos/a.json': { ...contenido }, './datos/b.json': { ...contenido } }

Un patrón común es transformar ese objeto de ruta a módulo en un mapa indexado por algo útil —el nombre del fichero sin extensión, o un identificador de dentro del propio JSON— para consultarlo luego por clave. Así conviertes una carpeta en una tabla de búsqueda: pides datos[clave] en vez de recorrerlo todo. Es la misma idea de las colecciones un escalón más abajo, sin esquema ni validación, pero con la misma comodidad de que añadir un fichero baste para añadir una fila.

const modulos = import.meta.glob('./datos/*.json', {
  eager: true,
  import: 'default',
});
const porNombre = Object.fromEntries(
  Object.entries(modulos).map(([ruta, dato]) => [
    ruta.split('/').pop()?.replace('.json', ''),
    dato,
  ]),
);
// porNombre.madrid  ->  el contenido de madrid.json
💡
Tipa el glob con un genérico

Los valores que devuelve import.meta.glob llegan con un tipo laxo, porque Vite no sabe qué exporta cada fichero. Puedes estrecharlo pasando un genérico —import.meta.glob<MiTipo>(patron)— para que el objeto resultante quede tipado y el autocompletado funcione al consumirlo. Es el mismo instinto que con las colecciones: en cuanto los datos entran al proyecto, conviene darles forma para que el compilador los vigile en vez de confiar en la memoria.

Imágenes y astro:assets

El caso más vistoso es una galería. Globas una carpeta de imágenes y, como Astro procesa los assets importados, cada valor es un objeto ImageMetadata con src, ancho, alto y formato, listo para el componente <Image>. Con eager e import por defecto obtienes un mapa de metadatos que recorres para pintar la galería entera.

---
import { Image } from 'astro:assets';

const imagenes = import.meta.glob<{ default: ImageMetadata }>(
  './galeria/*.{jpg,png,webp}',
  { eager: true },
);
---
{Object.values(imagenes).map((mod) => (
  <Image src={mod.default} alt="" />
))}

Esto resuelve un problema real: un src de imagen construido desde una variable en tiempo de ejecución no se procesa, porque Astro debe conocer el asset en el build para optimizarlo. Globar la carpeta le da esa lista por adelantado, de modo que cada imagen entra en la canalización de optimización —formatos modernos, tamaños responsivos— aunque la pintes en un bucle.

El mismo mecanismo alimenta otros usos: un mapa de componentes por nombre para incrustar en MDX, un conjunto de fragmentos de texto, los datos de varios idiomas en carpetas paralelas. Siempre que tengas una carpeta homogénea y quieras tratarla como un todo, import.meta.glob te ahorra la lista de importaciones a mano y, con ella, el olvido inevitable de actualizarla cuando el conjunto cambia.

⚠️
El patrón debe ser literal

import.meta.glob(unaVariable) no funciona. Vite necesita el patrón como cadena escrita en el código para enumerar las coincidencias durante el build; si sale de una variable, no hay nada que analizar y el glob queda vacío. Por la misma razón el patrón es relativo al fichero actual, aunque se admite la raíz del proyecto con /src/.... Todo lo que import.meta.glob hace ocurre antes de que el sitio arranque: es análisis estático, no lectura de disco en tiempo de ejecución.

flowchart LR
PAT[patron literal] --> ENUM[build enumera coincidencias]
ENUM --> EAG[eager da modulos]
ENUM --> LAZ[lazy da cargadores]
EAG --> DATA[datos o ImageMetadata]
LAZ --> DEM[carga bajo demanda]
style PAT fill:#89b4fa,color:#11111b
style DATA fill:#a6e3a1,color:#11111b
style DEM fill:#a6e3a1,color:#11111b
El sistema de ficheros como fuente de datos resuelta antes de que llegue el usuario

Las content collections son la puerta principal para tratar el contenido como datos, pero el mundo real tiene JSON de configuración, carpetas de imágenes y fragmentos que no son “contenido” y que igual quieres manejar con la misma comodidad. import.meta.glob es esa comodidad al nivel del grafo de módulos, y esconde una idea que trasciende a Vite y a Astro: el grafo de módulos que el empaquetador construye en el build es, en sí mismo, una fuente de datos consultable. Cuando globas una carpeta no estás leyendo ficheros en tiempo de ejecución —no hay un fs.readdir esperando a que llegue una petición—; estás pidiéndole al empaquetador que convierta una porción del sistema de ficheros en valores tipados, resueltos antes de que el primer usuario exista. Esa inversión temporal es lo que hace que sea seguro y rápido: el análisis estático puede enumerar, optimizar y trocear porque todo ocurre en un momento en el que aún se conoce el conjunto completo. Y la palanca eager frente a diferida es la misma que ya viste entre consultar y renderizar: decidir cuándo se paga el coste. Cargar todo por adelantado o cargar bajo demanda no es una preferencia estética, es la misma pregunta de ingeniería —qué necesito siempre y qué solo a veces— aplicada al momento en que un módulo se resuelve. Interiorizar que el sistema de ficheros, visto a través del empaquetador, es una base de datos que se consulta en el build te cambia la forma de estructurar un proyecto: dejas de escribir listas de importaciones a mano, que se olvidan y se desincronizan, y empiezas a describir con un patrón la forma de los datos que quieres, dejando que la máquina los reúna. Una carpeta bien nombrada se vuelve una consulta, y añadir un fichero, una fila más que aparece sola.

⚔️ Convierte una carpeta en datos
  1. Crea varios src/data/*.json, impórtalos con import.meta.glob usando eager e import: 'default', y pinta una lista con su contenido.
  2. Cambia a la carga diferida y carga uno solo bajo demanda; observa la diferencia en cómo accedes al valor.
  3. Globa una carpeta de imágenes con eager y píntalas con <Image> de astro:assets, comprobando que se optimizan.
  4. Intenta pasar el patrón desde una variable y razona por qué el glob queda vacío y qué te dice eso sobre cuándo se resuelve.