Saltar al contenido

Por qué Astro tiene un compilador propio

Por qué Astro no pudo reutilizar un compilador existente y necesitó el suyo. El formato .astro como dos zonas —el frontmatter de servidor y la plantilla, un superset de HTML— y por qué eso no es JSX ni se compila con la cadena de JavaScript. Qué produce ese compilador además de una función de render, y el salto de Go a Rust que estrena Astro 7.

Dos zonas separadas por una valla

Un componente .astro se parte en dos mitades de naturaleza opuesta, y la frontera entre ellas es sintáctica, no decorativa. Arriba, entre dos vallas de tres guiones, vive el frontmatter: código que corre una sola vez, en el servidor, durante el render —imports, await de nivel superior, lectura de props, cálculo de datos—. Debajo vive la plantilla: marcado que el componente emite, un superset de HTML donde las expresiones de JavaScript entran acotadas entre llaves.

ASTRO
---// zona de frontmatter: corre en el servidor, una vezimport Boton from '../components/Boton.astro';const usuario = await fetch('/api/yo').then((r) => r.json());---<h1>Hola, {usuario.nombre}</h1><Boton>Entrar</Boton>

La valla --- no es un adorno prestado del Markdown: es un token que el compilador reconoce para saber dónde termina el mundo del servidor y empieza el del marcado. Cada zona sufre un destino opuesto en la compilación:

  • El frontmatter se convierte en el cuerpo de una función que corre en el servidor y luego desaparece: ni una línea suya llega al navegador.
  • La plantilla se convierte en salida: HTML casi literal con huecos donde se interpolan las expresiones que evaluó el frontmatter.

Esa asimetría —un lado que corre y otro que se pinta— es imposible de expresar en un lenguaje donde todo es la misma clase de expresión, y es la primera razón por la que Astro necesitaba analizar el archivo a su manera. Un compilador de JavaScript no tiene ni el concepto de una zona que se ejecuta y otra que se emite: para él, todo es código o todo es dato, nunca las dos cosas cosidas por una valla.

Por qué esto no es JSX

La tentación de asimilar .astro a JSX se deshace en cuanto se mira la dirección de cada uno. En JSX, el anfitrión es JavaScript y el marcado es el invitado: cada etiqueta se compila a una llamada como jsx() o React.createElement, y el archivo entero es, al final, una expresión de JavaScript. En .astro la relación se invierte: el anfitrión es HTML y JavaScript es el invitado que entra solo dentro de las llaves. No hay createElement que valga; hay HTML de verdad que se conserva casi tal cual.

JSX
// JSX: el marcado ES JavaScript, se traduce a llamadas de funcionfunction Saludo({ nombre }) {  return <h1>Hola, {nombre}</h1>; // -> jsx('h1', { children: ['Hola, ', nombre] })}

En JSX ese <h1> no sobrevive como texto: es azúcar sintáctico sobre una llamada, y el compilador de JSX lo demuestra convirtiéndolo en jsx('h1', ...). En .astro, el mismo <h1> de la plantilla se conserva como HTML y solo {nombre} se trata como código. Dos gramáticas con el peso repartido al revés.

Las diferencias no son cosméticas. En .astro el espacio en blanco es significativo como en HTML, los comentarios son comentarios de HTML, el doctype se respeta, y las etiquetas <script> y <style> no son elementos cualquiera sino piezas que el compilador iza y procesa aparte. Nada de eso cabe en la semántica de JSX, donde <style> sería un nodo React más y el espacio se colapsa. Un compilador de JSX aplicado a un .astro no produciría un error: produciría algo semánticamente equivocado, que es peor.

Un compilador dedicado, y qué produce

Reutilizar tsc o Babel era inviable porque ninguno sabe analizar una gramática con anfitrión HTML, valla de frontmatter, estilos con ámbito e islas incrustadas. Pero hay una razón más profunda: el compilador de Astro no emite solo una función de render. De cada .astro extrae además un puñado de metadatos que el resto del framework necesita. Un transformador de JavaScript devuelve código; el de Astro devuelve código y un informe sobre lo que ese código significa.

Ese informe es justo lo que ninguna herramienta ajena sabría producir:

  • Los estilos del componente, izados y sellados con su hash de ámbito.
  • Los scripts que hay que procesar y empaquetar aparte.
  • La lista de islas —los componentes con directiva client:— que habrá que hidratar.
  • El identificador del componente, la semilla del hash que aísla sus estilos.

Por eso el compilador es la pieza que define qué es un componente .astro. No hay una especificación del formato al margen del programa que lo procesa: el lenguaje es lo que el compilador acepta y traduce. Cambiar una regla de la gramática es cambiar el compilador, y ninguna herramienta ajena podría seguir ese contrato sin reimplementarlo entero. Tener compilador propio no es un lujo de ingeniería: es la única forma de que .astro exista como formato con identidad.

La consecuencia práctica es que el editor, el formateador y el servidor de lenguaje que usas con Astro no adivinan la sintaxis: se apoyan en ese mismo compilador para analizarla. Cuando tu editor resalta un error en un .astro antes de compilar, por debajo está corriendo el parse del compilador sobre tu archivo. El compilador no es solo una etapa del build; es la fuente única de verdad sobre el formato, compartida por todas las herramientas que lo tocan.

Lo que el compilador deja fuera

Para no atribuirle poderes que no tiene, conviene marcar su frontera. El compilador de Astro traduce un .astro en un módulo con sus metadatos, y ahí acaba su mandato. No hace varias cosas que suelen confundirse con su trabajo:

  • No empaqueta: reunir módulos, resolver node_modules y producir los bundles finales es tarea de Vite y su bundler Rolldown.
  • No minifica ni optimiza el JavaScript de salida; solo lo genera legible.
  • No resuelve los imports a rutas finales con hash; eso ocurre después, en la fase de build.

Distinguir esa frontera evita un malentendido caro: cuando algo va mal, saber si el problema es del compilador —la traducción de tu .astro— o del bundler —el empaquetado posterior— es la mitad del diagnóstico. Cada pieza tiene su territorio, y este nivel trata solo de la primera.

Del Go al Rust en Astro 7

Durante casi toda su historia ese compilador estuvo escrito en Go y se distribuía compilado a WebAssembly, para poder invocarlo desde el mundo de Node. Funcionaba, pero pagaba un peaje en cada archivo al cruzar la frontera entre JavaScript y WASM. Astro 7 lo reescribe en Rust, el lenguaje hacia el que converge todo el tooling nativo de la web —los parsers, los bundlers, los linters de nueva generación—. La sintaxis .astro no cambia ni una coma; lo que cambia es la velocidad con la que se analiza y el hecho de compartir cimientos con el resto de la cadena de build.

El cambio, invisible en tu código, se nota en tres frentes que las próximas lecciones desarrollan:

  • Distribución: de un .wasm universal a binarios nativos por plataforma, con WASM como red de seguridad.
  • Rendimiento: desaparece el coste de serializar cada .astro al cruzar hacia WebAssembly.
  • Integración: el compilador comparte lenguaje y estructuras con Rolldown y el resto del tooling nativo.

Ninguno de esos tres frentes te pide reaprender nada; los tres, sumados, son la razón de que un sitio de decenas de miles de páginas pase de tardar minutos a tardar segundos.