Transformers de Shiki: líneas, foco, diff y palabras
Shiki no solo colorea: expone su árbol HAST como punto de extensión, y sobre él corren transformadores que anotan el resultado. Con el paquete @shikijs/transformers marcas líneas resaltadas, líneas en foco, líneas añadidas o eliminadas al estilo diff, y palabras concretas, todo mediante comentarios de notación que viven en el propio código. Se enchufan en shikiConfig.transformers y su apariencia final la decide tu CSS.
Qué es un transformer
Después de tokenizar el código, Shiki construye un árbol HAST —una representación en árbol del HTML que va a emitir, con un nodo por cada línea y por cada fragmento coloreado—. Un transformer es una función que recibe ese árbol y lo modifica antes de que se convierta en cadena: añade una clase a una línea, envuelve una palabra, marca un nodo con un atributo. No cambia los colores; añade estructura sobre la que después actuará tu CSS.
Los transformadores más útiles vienen en el paquete @shikijs/transformers, que instalas aparte y del que importas justo los que necesites. Cada uno reconoce una notación —un comentario especial que escribes en el código— y la traduce en una anotación del árbol, eliminando el comentario del resultado visible.
// astro.config.mjsimport { defineConfig } from 'astro/config';import { transformerNotationHighlight, transformerNotationFocus, transformerNotationDiff, transformerNotationWordHighlight,} from '@shikijs/transformers';export default defineConfig({ markdown: { shikiConfig: { transformers: [ transformerNotationHighlight(), transformerNotationFocus(), transformerNotationDiff(), transformerNotationWordHighlight(), ], }, },});Notaciones: highlight, focus, diff y palabra
La gracia de las notaciones es que viven dentro del código, como comentarios, de modo que la intención viaja con el propio fragmento y sobrevive a un copiar y pegar. Cada transformer reconoce la suya.
El más común es el resaltado de línea: un comentario // [!code highlight] al final de una línea marca esa línea con la clase .highlighted. El de foco, // [!code focus], marca la línea con .focused y pone .has-focused en el <pre>, para que puedas difuminar todo lo demás. Y el de diff, // [!code ++] y // [!code --], marca líneas añadidas y eliminadas con .diff.add y .diff.remove, imitando la lectura de un control de versiones.
const config = cargar();const puerto = 8080; config.legacyPort = 80; config.port = puerto; arrancar(config);En ese bloque, la línea del puerto queda resaltada, la línea vieja aparece marcada como eliminada y la nueva como añadida; los comentarios de notación no se ven en la salida, porque el transformer los consume. El de palabra, // [!code word:puerto], resalta cada aparición de puerto en el bloque con la clase .highlighted-word, útil para señalar un identificador concreto sin resaltar la línea entera.
Rangos por meta y transformers en <Code />
No toda marca cabe bien en un comentario dentro de la línea. A veces quieres resaltar un rango —“las líneas 2 a 4”— desde fuera del código, y para eso está el transformer de meta, transformerMetaHighlight, que lee la cadena que escribes tras el lenguaje en la valla. Con él activado, abrir una valla con js seguido de {2,4-6} resalta esas líneas sin ensuciar el código con comentarios.
Los transformadores no son exclusivos de la configuración global: <Code /> acepta una prop transformers para aplicarlos por instancia, y una prop meta para pasarle esa misma cadena de rango que en una valla iría tras el lenguaje.
---import { Code } from 'astro:components';import { transformerMetaHighlight } from '@shikijs/transformers';---<Code code={`linea uno\nlinea dos\nlinea tres`} lang="txt" meta="{2}" transformers={[transformerMetaHighlight()]}/>La diferencia de alcance es la misma que ya conoces: lo que pones en shikiConfig.transformers cubre cada bloque del sitio, mientras que lo que pasas a un <Code /> concreto vale solo para esa instancia. Se combinan bien: una política global para lo habitual, y ajustes locales donde un bloque pida algo especial.
El CSS que da vida a las clases
Las clases que los transformadores añaden no significan nada hasta que tu CSS las interpreta. Este es el paso que muchos olvidan y por el que “no funciona el resaltado”: el marcado está, pero falta el estilo. Escribirlo es directo, apuntando a .astro-code y a las clases que cada transformer genera.
/* Linea resaltada: una banda de fondo que ocupa todo el ancho */.astro-code .highlighted { background-color: #ffffff14; display: inline-block; width: 100%;}/* Diff: verde para lo anadido, rojo atenuado para lo eliminado */.astro-code .diff.add { background-color: #a6e3a11a; }.astro-code .diff.remove { background-color: #f38ba81a; opacity: 0.6; }/* Foco: difumina lo que no esta en foco cuando hay foco */.astro-code.has-focused .line:not(.focused) { filter: blur(2px); opacity: 0.5; transition: filter 0.2s, opacity 0.2s;}/* Palabra resaltada: un realce con esquinas suaves */.astro-code .highlighted-word { background-color: #f9e2af33; border-radius: 3px; padding: 0 2px;}