Añadir un lenguaje que Shiki no conoce
بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ
El problema que no se nota
Este sitio tiene dos posts sobre Caddy. Ambos usan bloques de código con ```caddy para mostrar configuración de Caddyfile. Expressive Code los renderiza, Shiki los tokeniza, la página compila sin errores.
Solo que cada bloque de Caddy se renderizaba como texto plano. Sin resaltado de sintaxis, sin color, sin estructura visual. Solo caracteres monocromos sobre un fondo oscuro.
Los bloques parecían código. Tenían números de línea, el frame, el botón de copiar. Todo lo que añade Expressive Code estaba ahí. Lo que faltaba era lo que añade Shiki: el resaltado en sí. Directivas, strings, comentarios, valores, todos del mismo color.
La diferencia salta a la vista al comparar el blog con VS Code. La misma configuración de Caddy, a todo color en el editor, monocroma en la página.
Por qué pasa
Expressive Code usa Shiki para el resaltado de sintaxis, y Shiki incluye gramáticas para más de 250 lenguajes. JavaScript, Python, TypeScript, Bash, HTML, CSS, Go, Rust. Los lenguajes sobre los que escribe la mayoría de blogs técnicos.
Caddy no es uno de ellos. Tampoco Traefik, HAProxy o Docker Compose, herramientas habituales en infraestructura self-hosted. Cuando Shiki encuentra un identificador de lenguaje que no reconoce, recurre a texto plano. Sin error, sin fallo de build. Solo una advertencia enterrada en la salida del build que probablemente no leas:
[astro-expressive-code] Error while highlighting code block using language "caddy".The language could not be found. Using "txt" instead.La palabra «error» es generosa. Nada se rompe. La página se renderiza. El bloque de código parece un bloque de código. Simplemente no hace lo único que se supone que hace un resaltador de sintaxis.
De dónde vienen las gramáticas
Shiki usa gramáticas TextMate, el mismo formato que VS Code para el resaltado de sintaxis. Cada extensión de lenguaje de VS Code lleva un archivo .tmLanguage.json que define cómo se tokeniza el lenguaje: qué es una palabra clave, qué es un string, qué es un comentario.
Si VS Code resalta tu lenguaje correctamente, la gramática ya existe. Está en tu carpeta de extensiones.
Para Caddy, la extensión oficial es caddyserver/vscode-caddyfile, publicada como matthewpi.caddyfile-support. Con licencia MIT.
El archivo de la gramática está en:
~/.vscode/extensions/matthewpi.caddyfile-support-0.4.0/syntaxes/caddyfile.tmLanguage.jsonEl mismo archivo que hace que VS Code resalte Caddyfiles puede hacer que Expressive Code los resalte también. Cópialo a tu proyecto, apunta la config a él, listo.
Cinco líneas de configuración
Expressive Code acepta gramáticas personalizadas a través de la opción shiki.langs en ec.config.mjs:
import { defineEcConfig } from 'astro-expressive-code'import fs from 'node:fs'
const caddyfile_Grammar = { ...JSON.parse(fs.readFileSync('./src/Scripts/Build/Grammars/caddyfile.tmLanguage.json', 'utf-8')), name: 'caddy',}
export default defineEcConfig({ shiki: { langs: [caddyfile_Grammar], langAlias: { caddyfile: 'caddy' }, },})Carga la gramática, añádela a langs y, si quieres, declara nombres alternativos como alias. El resto de la configuración no cambia.
Compila el sitio. Adiós a las advertencias. Los bloques de código se encienden.
Por qué seguía sin funcionar
Solo que no se encendían. No a la primera.
La gramática se cargaba sin errores. El build corría limpio. Los bloques de código seguían renderizándose como texto plano. Sin advertencias, sin indicio de que algo fuera mal.
El problema estaba en el campo name de la gramática. La gramática de Caddyfile se declara como name: "Caddyfile", con C mayúscula y F mayúscula. Pero los bloques de código en los archivos MDX usan ```caddy, en minúsculas. Shiki compara los nombres de lenguaje distinguiendo mayúsculas y minúsculas. "Caddyfile" no coincide con "caddy".
La opción langAlias parecía la solución. Mapear caddy a caddyfile. Pero el alias se resuelve antes de comprobar los lenguajes cargados, y la gramática estaba registrada como "Caddyfile", no como "caddyfile". El alias apuntaba a un nombre que no existía.
La solución: sobrescribir la propiedad name de la gramática al cargarla.
const caddyfile_Grammar = { ...JSON.parse(fs.readFileSync('./path/to/caddyfile.tmLanguage.json', 'utf-8')), name: 'caddy', // override "Caddyfile" to match ```caddy fences}Una línea. La gramática se registra ahora como "caddy", los bloques dicen caddy, Shiki encuentra coincidencia. Todo funciona.
Es el tipo de bug que no se anuncia. Sin stack trace, sin mensaje de error, sin build fallido. La gramática se carga correctamente. Shiki simplemente no la conecta nunca con tus bloques de código. Hay que saber que la comparación de nombres en Shiki distingue mayúsculas y minúsculas, y hay que saber que el nombre interno de la gramática puede no coincidir con el identificador que usas en tu Markdown. Ninguna de las dos cosas está documentada en la guía de configuración de Expressive Code.
El patrón
Añadir un lenguaje personalizado a Expressive Code es un proceso de tres pasos:
-
Encuentra la gramática. Mira en tu carpeta de extensiones de VS Code. Si tu editor resalta el lenguaje, el
.tmLanguage.jsonexiste. Cópialo a tu proyecto para que el build no dependa de la configuración local de tu editor. Comprueba la licencia. La copia vendored es tuya y tendrás que mantenerla: las correcciones upstream no llegarán a tu build hasta que vuelvas a copiar el archivo. -
Regístrala. Añádela a
shiki.langsen tu configuración de Expressive Code. UsalangAliassi quieres que varios identificadores resuelvan a la misma gramática. -
Haz que el nombre coincida. Sobrescribe la propiedad
namede la gramática para que coincida con lo que uses en tus bloques de código. No des por hecho que el nombre interno de la gramática coincide con el identificador de tu bloque.
El proceso completo, una vez que lo conoces, es corto. Descubrir que tienes que hacerlo, y por qué el primer intento falla en silencio, es el trabajo de verdad.
Cómo se ve ahora
Antes:
handle_errors { rewrite * /404.html file_server}Después:
handle_errors { rewrite * /404.html file_server}Mismo contenido. La diferencia está en si la mirada del lector puede captar la estructura de un vistazo o tiene que leer cada palabra. En un post que pretende enseñar a alguien a configurar Caddy, esa diferencia es inshallah la distancia entre útil y frustrante.
Los bloques de código en un blog técnico son una señal silenciosa de credibilidad. Cuando se renderizan con resaltado completo, nadie se da cuenta. Cuando no, las personas que sí se darían cuenta son justo las que quieres que lean tu trabajo.
Ingeniero aeroespacial
Emprendedor ético en público
Tú te ocupas de tu negocio
Yo me encargo del lado digital
Trabaja conmigo
- IA con honestidad
- Infraestructura privada
- Sitios web que rinden
Cuéntame sobre tu situación:
javed@javedab.com Más sobre mí