Saltar al contenido

El componente Code: resaltar desde el servidor

Las vallas de Markdown resaltan contenido estático, pero a veces el código llega en una variable, en una respuesta de red o en un fichero importado con ?raw. Para esos casos Astro expone el mismo motor Shiki como un componente invocable, <Code />, importado de astro:components. Renderiza en el servidor con props code, lang, theme y themes, ofrece inline para incrustar código en el texto, y nunca envía JavaScript al cliente.

Por qué existe <Code />

En un fichero .astro no puedes escribir una valla de Markdown; y aun en un .mdx, la valla solo sirve para código literal escrito en el sitio. Cuando el código proviene de un valor —una cadena calculada, un fragmento traído de una API, el contenido de un fichero cargado con ?raw— necesitas un resaltado programático, capaz de aceptar ese valor como entrada. Ese es el hueco que llena <Code />: recibe el código en una prop y devuelve el mismo HTML coloreado que produciría una valla, pero desde un valor dinámico.

ASTRO
---import { Code } from 'astro:components';---<Code code={`const respuesta = 42;`} lang="js" />

La prop code es la única obligatoria: la cadena que quieres resaltar. El resto tiene valores por defecto sensatos —lang es 'plaintext', theme es 'github-dark'—, así que el ejemplo de arriba ya produce un bloque JavaScript coloreado. Lo importante es el cambio de naturaleza: donde la valla es declarativa —escribes el código y aparece—, <Code /> es invocable —le pasas el código como dato y lo procesa—, y esa diferencia es la que abre los casos que la valla no puede cubrir.

Los escenarios donde esa diferencia paga son concretos y frecuentes. Recorrer un array de ejemplos y renderizar cada uno con <Code /> dentro de un bucle. Mostrar el contenido de un fichero de configuración real leído del proyecto. Resaltar la respuesta de una API en una página bajo demanda. Pintar el mismo fragmento con dos temas distintos, lado a lado, para comparar paletas. En todos, el código es un dato que fluye por tu componente, no un literal que tecleaste en el sitio, y solo un resaltador invocable puede aceptarlo como entrada.

ASTRO
---import { Code } from 'astro:components';const ejemplos = [  { lang: 'js', code: `const a = 1;` },  { lang: 'py', code: `a = 1` },  { lang: 'rs', code: `let a = 1;` },];---{ejemplos.map((e) => <Code code={e.code} lang={e.lang} />)}

Ahí se ve el salto con claridad: un mismo bucle recorre una lista de datos y produce tres bloques coloreados, cada uno en su lenguaje, sin que hayas escrito una sola valla. Eso es imposible con Markdown, que solo conoce literales; y es trivial con <Code />, que trata el código como lo que aquí es, un valor más que se itera.

Props esenciales: code, lang, theme

Cada instancia de <Code /> puede fijar su propio lenguaje y su propio tema, con independencia de la configuración global de markdown.shikiConfig. Eso permite que un bloque concreto use una paleta distinta sin afectar al resto.

ASTRO
---import { Code } from 'astro:components';---<Code code={`SELECT * FROM usuarios;`} lang="sql" theme="nord" /><Code code={`print("hola")`} lang="python" theme="dracula" />

También acepta themes y defaultColor, exactamente igual que la configuración global, de modo que un componente puede servir su propio par claro/oscuro. Y admite wrap por instancia, para que ese bloque en particular ajuste líneas aunque el resto del sitio no lo haga. La lógica es de anulación local: la configuración global marca el estándar, y cada <Code /> puede desviarse de él cuando tenga una razón.

Hay aquí un matiz que ahorra sorpresas. Cuando no fijas una prop, <Code /> toma el valor por defecto del propio componente —github-dark—, no el que declaraste en markdown.shikiConfig. Es decir, la configuración global gobierna las vallas de Markdown, pero no se propaga sola a los <Code />. Si quieres que un <Code /> siga la paleta global, pásale explícitamente el mismo tema; y si vas a repetir esa elección muchas veces, la solución limpia es escribir un componente propio que envuelva a <Code /> con tus valores por defecto, de modo que la decisión viva en un solo sitio.

ASTRO
---// src/components/Codigo.astroimport { Code } from 'astro:components';const { code, lang = 'ts' } = Astro.props;---<Code code={code} lang={lang} theme="dracula" wrap />

Con ese envoltorio, el resto del proyecto importa Codigo en lugar de Code y obtiene siempre el mismo tema y el mismo ajuste de línea, sin repetir props en cada uso. Es el patrón habitual para no diseminar decisiones de estilo por cientos de llamadas: centralizas la configuración de tus bloques en un componente, igual que centralizas la de las vallas en shikiConfig.

El caso más valioso es alimentar el componente con un fichero real importado como texto crudo. Astro entiende el sufijo ?raw en una importación y entrega el contenido del fichero como una cadena, que pasas directa a code.

ASTRO
---import { Code } from 'astro:components';import fuente from '../ejemplos/demo.ts?raw';---<Code code={fuente} lang="ts" />

Ese patrón tiene una virtud enorme para la documentación técnica: el código mostrado es el mismo fichero que compilas y pruebas, no una copia pegada que se desincroniza con el tiempo. Si el ejemplo cambia, el documento cambia con él, porque ambos leen la única fuente de verdad. La documentación deja de mentir por omisión.

inline y código dentro del texto

Por defecto <Code /> envuelve el resultado en un <pre>, que es un bloque de nivel de párrafo. Pero a veces quieres un fragmento coloreado dentro de una frase —un nombre de función, un comando, un identificador— sin romper el flujo del texto. Para eso está inline: activa un render sin <pre>, produciendo solo un <code> que encaja en línea.

ASTRO
---import { Code } from 'astro:components';---<p>Ejecuta <Code code={`npm run build`} lang="bash" inline /> para compilar el sitio.</p>

Con inline, el comando aparece coloreado en medio de la oración, como una pieza tipográfica más. Es la manera de llevar la precisión de Shiki al texto corrido, no solo a los bloques aislados. La prop meta, por su parte, transporta la cadena que en una valla iría tras el lenguaje —rangos de líneas, banderas— para que los transformadores que la leen funcionen también aquí; la verás en acción en la próxima lección.

Servidor siempre, cero cliente

Lo decisivo, y lo que emparenta a <Code /> con todo lo visto en el nivel, es dónde corre. <Code /> se resuelve por completo en el servidor —en el build para páginas estáticas, en la petición para las de bajo demanda— y jamás se hidrata en el cliente. No existe una versión de navegador de este componente, ni una directiva client: que tenga sentido ponerle. Su salida es HTML terminado, y su coste en JavaScript de cliente es exactamente cero.