Resaltado nativo: Shiki integrado y cero JavaScript
Astro trae Shiki incrustado y enciende el resaltado de código sin instalar nada: escribes un bloque con tres backticks y un lenguaje, y la coloración ocurre en el build usando las mismas gramáticas TextMate que VS Code. El resultado es HTML estático con estilos en línea, sin una sola librería que se descargue ni un script que corra en el navegador del visitante.
El resaltado que ya está encendido
Lo primero que sorprende es lo que no hay que hacer. No instalas un paquete, no importas un tema, no añades una etiqueta de script al final del documento. Astro llega con Shiki dentro y con el resaltado activado por defecto para todo el Markdown y el MDX del proyecto. La opción que gobierna esa decisión es markdown.syntaxHighlight, y su valor de fábrica es 'shiki'.
// astro.config.mjsimport { defineConfig } from 'astro/config';export default defineConfig({ markdown: { syntaxHighlight: 'shiki', // valor por defecto, no hace falta escribirlo },});Ese ajuste admite tres formas. La cadena 'shiki' usa el resaltador nativo; 'prism' cambia al motor alternativo que verás en la última lección; y false apaga el resaltado por completo, dejando el código en texto plano. Existe además una forma de objeto que permite afinar el comportamiento sin renunciar a Shiki.
// astro.config.mjsimport { defineConfig } from 'astro/config';export default defineConfig({ markdown: { syntaxHighlight: { type: 'shiki', excludeLangs: ['mermaid', 'math'], }, },});Esa forma de objeto tiene un uso muy concreto en la práctica. Cuando integras diagramas o fórmulas, su fuente viaja también en bloques de código etiquetados con un lenguaje propio —mermaid, math—, pero no quieres que Shiki los coloree como si fueran código, porque otra herramienta los va a transformar después en un diagrama o una ecuación. Excluirlos con excludeLangs deja su contenido intacto para esa otra herramienta, sin renunciar al resaltado de todo lo demás. Pero salvo esos casos, lo normal es no tocar nada: el resaltado ya está, y está bien.
Conviene medir cuánto trabajo desaparece. En el mundo previo, dar color a un bloque de código exigía elegir un resaltador de cliente, incluir su fichero JavaScript, cargar además una hoja de estilos con el tema, y confiar en que ese script se ejecutara en cada visita para teñir el <pre> una vez cargada la página. Astro borra los cuatro pasos: ni script, ni CSS de tema, ni ejecución en el navegador, ni espera. El bloque llega coloreado en el propio HTML.
Ese resaltado cubre por igual el Markdown puro y el MDX, porque ambos comparten la misma tubería de contenido: la valla de código se procesa del mismo modo la escribas donde la escribas. Y conviene distinguirla desde ya del código en línea —el que envuelves con un solo backtick dentro de una frase—, que Astro emite como un simple <code> sin colorear, porque un fragmento suelto sin lenguaje no tiene estructura que analizar. El resaltado es cosa de los bloques con lenguaje; el código en línea es tipografía, no sintaxis.
Por defecto, ese resaltado usa el tema github-dark, una elección neutra que encaja en la mayoría de los diseños oscuros. No es una imposición: en la próxima lección cambiarás el tema, montarás un par claro/oscuro y afinarás cada detalle. Pero es revelador que el punto de partida ya sea razonable sin que hayas escrito una sola línea de configuración. Astro apuesta por defectos que sirven, de modo que configurar sea una mejora opcional y no un peaje de arranque; el resaltado bonito no es una recompensa que te ganas tras configurar, sino el estado inicial.
Tres backticks y un lenguaje
La interfaz para el autor es la más simple que existe en Markdown: una valla de tres backticks, y justo después el identificador del lenguaje. Ese identificador es la única señal que Shiki necesita para saber qué gramática cargar y cómo interpretar el texto.
const saludo: string = 'hola';console.log(saludo.toUpperCase());El bloque de arriba se abrió con la valla seguida de ts, y por eso Shiki lo lee como TypeScript: reconoce la anotación de tipo, la palabra const, la cadena y la llamada a método, y tiñe cada pieza según su papel. Si hubieras omitido el lenguaje, el bloque se trataría como texto plano —plaintext— y llegaría sin color, porque sin gramática no hay nada que interpretar. El identificador no es decorativo: es el que selecciona el analizador.
Los nombres de lenguaje admiten alias habituales —js y javascript, ts y typescript, sh, bash, shell—, todos resueltos contra el catálogo de gramáticas que Shiki empaqueta. Si escribes un identificador que Shiki no conoce, el resaltado degrada con elegancia a texto plano y te avisa, en lugar de romper el build. La regla práctica es sencilla: etiqueta siempre tus bloques con el lenguaje correcto, porque de esa etiqueta depende que el color sea exacto y no aproximado.
Lo que escribes tras el identificador —en la misma línea de apertura de la valla— es la cadena meta, un espacio para banderas y rangos que ciertos transformadores leen; la verás con detalle en la lección de transformadores. Por ahora basta con retener la anatomía completa de una valla: tres backticks, el lenguaje pegado a ellos, y opcionalmente una cadena meta después. De esos tres elementos, solo el lenguaje decide el color; los otros dos son estructura y metadatos.
La gramática detrás del color
Aquí está la diferencia técnica que separa a Shiki de los resaltadores clásicos. Muchos motores de cliente colorean con expresiones regulares: un puñado de patrones que buscan lo que parece una palabra clave o una cadena. Es rápido y ligero, pero impreciso, porque una regex no entiende de verdad la estructura del lenguaje. Shiki no adivina: usa gramáticas TextMate, las mismas que emplea VS Code, ejecutadas por el motor de expresiones regulares Oniguruma compilado a WebAssembly. El resultado es que el color que ves en tu documentación coincide, token a token, con el que verías en tu editor.
Una gramática TextMate no asigna colores; asigna ámbitos —scopes—, categorías semánticas como “esto es una palabra clave de control”, “esto es el nombre de una función”, “esto es una cadena literal”. El tema, que es un fichero JSON heredado del ecosistema de VS Code, es quien mapea cada ámbito a un color concreto. Esa separación en dos capas —una que entiende la estructura, otra que decide la apariencia— es la que da a Shiki su precisión y su flexibilidad, y será el eje de la próxima lección.
La precisión que esto compra se ve en los casos difíciles. Imagina un bloque de HTML con una etiqueta script que contiene JavaScript, que a su vez incrusta una plantilla con CSS dentro. Una expresión regular se pierde en ese anidamiento y colorea a ciegas; una gramática TextMate, en cambio, cambia de sub-gramática al entrar en cada región incrustada y tiñe cada capa con las reglas de su propio lenguaje. Esa capacidad de reconocer sintaxis dentro de sintaxis es la que separa un resaltador que entiende el código de uno que solo lo tantea, y es la razón por la que el color de Shiki coincide con el de tu editor incluso donde el código se complica.
Vale la pena nombrar las piezas con propiedad, porque reaparecerán en todo el nivel. Un token es cada fragmento contiguo al que la gramática asigna un mismo ámbito: la palabra const, el nombre de una variable o una cadena entre comillas son tokens distintos. Un ámbito es la etiqueta jerárquica que describe qué es ese token —algo como keyword.control, string.quoted o entity.name.function—, y su forma en segmentos separados por puntos permite que un tema apunte a categorías amplias o finas. Shiki produce, para cada línea, una secuencia de tokens con sus ámbitos, y el tema recorre esa secuencia asignando un color a cada uno. Ese es, desmontado, todo el mecanismo: tokenizar el texto en ámbitos, y mapear ámbitos a colores.
Coloreado en el build, cero JavaScript en el cliente
Llegamos al hecho arquitectónico que lo cambia todo. Shiki corre en el build —o en el render del servidor, para páginas bajo demanda—, nunca en el navegador. Cuando el visitante recibe la página, el bloque ya es HTML terminado: un <pre> con la clase .astro-code, y dentro un <span> por cada token con su color escrito directamente en un atributo style en línea. No viaja ningún fichero JavaScript de resaltado, ni ninguna hoja de estilos de tema. El color está cocido en el marcado.
Ese momento de cálculo se adapta al tipo de página sin cambiar su naturaleza. En una ruta estática el resaltado ocurre en el build y el HTML resultante se guarda tal cual, listo para servirse mil veces sin recomputar. En una ruta bajo demanda ocurre durante el render de la petición, en el servidor, justo antes de enviar la respuesta. En ninguno de los dos casos toca el navegador: la frontera que de verdad importa no es build frente a servidor, sino servidor frente a cliente, y el resaltado nativo se queda siempre, en cualquier modo, del lado del servidor.
Si abres el HTML generado, verás el color escrito en el propio marcado, sin ninguna clase que dependa de una hoja de estilos externa para tener sentido.
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8"> <code> <span class="line"><span style="color:#F97583">const</span> <span style="color:#79B8FF">x</span> = <span style="color:#79B8FF">1</span>;</span> </code></pre>La consecuencia se nota en cada métrica que importa. La página pesa menos porque no arrastra un resaltador. Se pinta antes porque no espera a que un script se descargue, se compile y recorra el DOM tiñendo nodos. Y funciona igual con JavaScript deshabilitado, porque el color nunca dependió de él. El coste de resaltar se pagó una vez, en tu máquina, durante el build; el dispositivo del visitante no gasta ni un ciclo en ello.