Cuando tus bloques de código se quedan mudos
Añadir un lenguaje que Shiki no conoce
بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ
Tamaño
Espaciado
Fuente
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.
Definiciones rápidas
¿Qué es Expressive Code?
La herramienta que da estilo a los ejemplos de código de este sitio: añade números de línea, un botón de copiar y un marco limpio alrededor de cada bloque. El coloreado en sí de palabras clave y strings se delega a Shiki.
¿Qué es Shiki?
El componente que da color al código para que palabras clave, strings y comentarios se distingan a simple vista. Trae soporte para más de 250 lenguajes de programación. Cuando encuentra un lenguaje que no reconoce, muestra el código como texto plano sin lanzar ningún error.
¿Qué es una gramática TextMate?
Un archivo que le dice a una herramienta qué buscar en un fragmento de código: palabras clave, strings, comentarios, etcétera. VS Code usa estos archivos para colorear código en el editor. Shiki usa el mismo formato, lo que significa que cualquier lenguaje que VS Code pueda colorear, Shiki también puede colorearlo.
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.
¿Funciona este enfoque para otros lenguajes además de Caddy?
Sí. Cualquier lenguaje que Shiki no soporte de fábrica puede añadirse de la misma manera. Lo único que hace falta es un archivo de gramática TextMate. Junto a Caddy, ejemplos comunes son Traefik, HAProxy y Docker Compose, herramientas habituales en infraestructuras self-hosted. Los tres pasos son los mismos independientemente del lenguaje que se añada.
¿Qué pasa si no hay ninguna extensión de VS Code para mi lenguaje?
El archivo de gramática no tiene por qué venir de una extensión de VS Code. Algunos proyectos publican sus propios archivos de gramática directamente en sus repositorios de GitHub. Si no existe nada, es posible escribir una gramática desde cero, pero eso queda fuera del alcance de este post.
¿Es necesario actualizar manualmente el archivo de gramática cuando la extensión se actualiza?
Sí, manualmente. La copia del proyecto no sigue los cambios de la fuente original de forma automática. Cuando la extensión de VS Code publica una corrección o mejora de la gramática, hay que copiar el nuevo archivo a mano. Para lenguajes estables como Caddyfile, eso pasa raramente. Para lenguajes en desarrollo activo, conviene revisarlo de vez en cuando.
Web 3 de 3
Volver a WebIngeniero 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í