El grafo de módulos y el bundling
Cómo el bundler convierte tu proyecto en un artefacto de producción: el grafo de módulos como materia prima, el tree shaking que poda las exportaciones que nadie usa, el code splitting que reparte el código en chunks cacheables, el tratamiento de los assets con hash de contenido, y cómo leer la carpeta de salida del build de Astro 7 para entender qué se generó y por qué.
El grafo de módulos: la materia prima
Cada página de tu sitio es un punto de entrada del grafo. El bundler empieza ahí y sigue las aristas —cada import— hasta construir el conjunto completo de módulos alcanzables. Un módulo al que ninguna página llega, directa o indirectamente, sencillamente no existe para el build: no se emite. Ese grafo, con sus nodos y sus dependencias, es la estructura sobre la que operan todas las optimizaciones posteriores; no hay tree shaking ni splitting sin él.
En Astro este grafo tiene una particularidad que conviene fijar: la mayoría de tu marcado se resuelve en el servidor y no viaja al cliente. El grafo del navegador solo se puebla con las islas —los componentes con directiva client:— y su código. Por eso un sitio Astro típico emite muchísimo menos JavaScript de cliente que una aplicación equivalente que hidrate la página entera: buena parte de los nodos del grafo nunca cruzan al bundle del navegador.
Conviene distinguir, de hecho, dos grafos que el build maneja en paralelo:
- El grafo de servidor abarca todo lo necesario para producir el HTML: páginas, layouts, componentes
.astroy los datos que consultan en su frontmatter. - El grafo de cliente es mucho más pequeño y arranca solo en las islas: contiene el código de framework que se hidrata y lo que ese código importa.
Un mismo componente puede vivir en ambos —renderizado en el servidor, hidratado en el cliente—, pero solo su mitad interactiva engorda el bundle que descarga el visitante. Esta dualidad es la razón de fondo de que Astro pueda apoyarse en dependencias pesadas para generar HTML sin penalizar por ello al navegador.
Tree shaking: podar lo que nadie usa
El tree shaking es la eliminación de código muerto a escala de exportación. Si un módulo exporta diez funciones y tus páginas solo usan dos, el bundler descarta las otras ocho. La magia no es adivinación: se apoya en una propiedad estructural de los módulos ESM. Como import y export son estáticos —se resuelven leyendo el código, no ejecutándolo— el bundler puede saber con certeza qué exportaciones se consumen antes de correr una sola línea.
Para que la poda funcione tienen que cumplirse tres condiciones, y cada una explica un fallo típico cuando no se da:
- Los módulos deben ser ESM, con imports y exports estáticos y analizables sin ejecutar el código.
- Las importaciones deben ser específicas: traer solo lo que se usa, no el espacio de nombres entero por comodidad.
- El paquete debe estar libre de efectos secundarios o declararlo, para que el bundler se atreva a borrar lo que nadie usa.
Hay un matiz que separa a quien entiende el build de quien solo lo sufre: los efectos secundarios. Si un módulo, con solo importarse, hace algo observable —registrar un elemento global, mutar un objeto compartido— el bundler no puede eliminarlo aunque no uses ninguna de sus exportaciones, porque borrarlo cambiaría el comportamiento. Los paquetes bien empaquetados declaran su ausencia de efectos con el campo sideEffects para autorizar una poda agresiva.
// package.json de una libreria que promete no tener efectos secundarios{ "name": "mi-libreria", "sideEffects": false}Cuando una librería enorme se niega a adelgazar por más tree shaking que esperes, sospecha de efectos secundarios sin declarar: sin esa promesa explícita, el bundler juega a lo seguro y conserva el módulo entero por si acaso.
Code splitting y chunks
Si el bundler metiera todo el grafo en un único fichero, cada página cargaría el código de todas las demás y cualquier cambio invalidaría la caché del sitio entero. El code splitting evita ambas cosas repartiendo el grafo en varios chunks: unidades de salida que el navegador descarga por separado y cachea de forma independiente.
La cuarta tarjeta es la más importante para el rendimiento real. Al nombrar cada chunk con un hash derivado de su contenido, editar una sola página cambia el nombre de su chunk y del común si lo tocó, pero deja intactos los nombres de todos los demás. El navegador de un visitante recurrente solo redescarga lo que cambió y sirve el resto desde su caché. Esa es la razón por la que verás nombres como Contador.a1b2c3d4.js en la salida: el hash no es ruido, es la clave de una caché que dura.
En Astro esta lógica se aplica isla por isla. Cada componente hidratado es un punto de entrada del grafo de cliente, y el bundler lo emite como su propio chunk con las dependencias que solo él usa. Un import() dinámico refuerza el efecto: marca a mano una frontera de división para diferir código hasta que de verdad se necesite.
// un import dinamico crea un punto de division explicitoasync function abrirEditor() { const { montarEditor } = await import('./editor-pesado.js'); montarEditor();}Aquí el módulo editor-pesado.js no entra en el chunk inicial: el bundler lo aísla en uno aparte que solo se descarga cuando alguien llama a abrirEditor, y no un instante antes.
Leer el output del build
Cuando ejecutas astro build, la carpeta de salida cuenta una historia legible si sabes su alfabeto. En un sitio estático encuentras el HTML de cada ruta ya renderizado y una carpeta de assets con hash donde viven los chunks de JavaScript, los estilos y las imágenes procesadas.
astro build# dist/# index.html ruta renderizada en el build# blog/primero/index.html otra ruta prerenderizada# _astro/# Contador.a1b2c3d4.js chunk de una isla hidratada# client.e5f6a7b8.js runtime compartido de cliente# index.9c0d1e2f.css estilos con hash de contenido# portada.7a8b9c0d.webp imagen optimizada y con hashLos assets —imágenes, CSS, fuentes— siguen la misma lógica de hash que los chunks. Astro extrae los estilos con scope de cada componente, los optimiza y los emite con un nombre versionado; las imágenes que pasan por su pipeline salen convertidas y con hash. Si construyes para SSR, la salida se parte en dos carpetas: una con lo que sirve el cliente y otra con el código de servidor que ejecuta el adapter en cada petición. La terminal, además, imprime un resumen con las rutas generadas y sus tiempos: leerlo te dice de un vistazo qué páginas se prerenderizaron y cuáles quedaron para render bajo demanda.
Al leer una carpeta de build conviene fijarse en tres señales concretas:
- El número y el tamaño de los chunks de cliente: si son muchos y grandes en un sitio de contenido, algo se está hidratando de más.
- La presencia de un chunk compartido dominante: revela qué código arrastran en común todas las páginas y merece una auditoría.
- La división en cliente y servidor cuando hay SSR: confirma qué se resolvió en el build y qué se dejó para la petición.