Aventuras con resaltado de sintaxis en el blog

Publicado el 14 de noviembre de 2024

Una de las cosas que me llevó investigación en los principios del blog fue cómo mostrar código fuente con resaltado de sintaxis en los posts. Desde un principio quería compartir cosas que iba aprendiendo a lo largo del camino con programación. Así que mostrar código de manera amigable, era uno de los requisitos.

En ese entonces ya había varias soluciones, la mayoría metiendo kilos de código JavaScript y CSS extra en el sitio, cosa que no quiero hacer. Incluso estos días, encontré que la situación no es mucho mejor. Sigo buscando la manera de hacer las cosas en este sitio de manera un poco más minimalista y performante. Y todavía no encontré un plugin o similar que me sirva para esto como yo quiero hacerlo.

Parece que el editor de bloques de WordPress tiene algo parecido a resaltado de sintaxis, y varios de los plugins mantenidos actualmente dependen de ese sistema. Así que no son una opción. Otros no se actualizan hace más de 6 meses. Estaba buscando algo que no dependa de que un plugin se siga manteniendo en el futuro, y en lo posible, una solución más genérica que si cambio de plugin o incluso sistema CMS no tenga que pasar por este proceso de nuevo.

WP-Syntax

En su momento opté por WP-Syntax, un plugin de WordPress que permite resaltado de sintaxis entre los tags HTML pre , con el atributo lang como parámetro para el lenguaje de programación: <pre lang="lenguaje">código fuente</pre>. Éste estuvo instalado en este blog por más de 17 años.

El plugin funcionaba bien, e incluso colaboré código con la opción de usarlo para los comentarios, lo que se incorporó en la versión 0.9 del plugin. Pero estaba desconforme por varias razones. En principio, no se actualiza hace mucho tiempo, por lo que no se agreguen lenguajes de programación nuevos. También resulta un poco una vulnerabilidad si el proyecto no está muy activo.

A nivel funcional era el que me resultaba más práctico, pero la apariencia del código en los posts es bastante anticuada y no pega con el diseño general del blog. Y no tenía mucha iniciativa como para ponerme a mirar cómo personalizar la apariencia.

Otro tema no menor, como comentaba en WordPress en 2024, estoy buscando formas de hacer el sitio más liviano y ligero. WP-Syntax carga un archivo JavaScript y un archivo CSS por tener el plugin cargado. Ya sea que se esté usando su funcionalidad o no. Por otro lado, el log de errores de mi servidor estaba lleno de  advertencias de código roto:

PHP Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in wp-content/plugins/wp-syntax/wp-syntax.php on line 380

Esto intenté ver si podía corregirlo, pero miré el código medio rápido y no tengo ni idea por qué está pasando por valor y no por referencia. Tampoco era el problema principal, sólo sumaba a reivindicar mi decisión de dejar de usar el plugin. Así que no le dediqué más tiempo.

Decidí eliminar el plugin y usar algo más "a mano", pero con mejores resultados en cuanto a rendimiento y carga de las páginas y el blog en general. Opté por pasar el código fuente por un resaltador de sintaxis a mano y pegar el HTML en cada post. Puede sonar a mucho trabajo, pero no me resulta tanto y me da mucho más control. Encontré que podía generar HTML con los estilos CSS en línea, por lo que lo puedo copiar y pegar en cualquier fuente HTML y se va a ver con los mismos colores.

Es la solución que voy a usar ahora, si me encuentro con una mejor en el futuro, volveré a pasar por todo este proceso de nuevo. No es fácil mantener un sitio web activo y actualizado por más de 17 años 😅

Aprendiendo sobre Rouge

Siguiendo con los viajes en el tiempo de este blog, en un momento escribí sobre source-highlight, una herramienta GNU para resaltado de sintaxis. Incluso en ese post lo usé para mostrar código resaltado. Al ser parte del proyecto GNU puedo asumir que va a funcionar por bastante tiempo, así que lo tengo anotado como respaldo.

Como Ruby es mi lenguaje de cabecera, busqué una herramienta similar y encontré casi instantáneamente rouge. Es la biblioteca que usa Jekyll por defecto para el resaltado de sintaxis. Entre sus formateadores, está Rouge::Formatters::HTMLInline, que renderiza el código sin clases CSS, y todo en la misma línea con etiquetas HTML span y los colores definidos en atributos style=. Ideal para correos electrónicos y otros lugares, así que el resultado en teoría se vería bien incluso en lectores RSS y con suerte para suscriptores por correo.

Al usar esta opción donde el HTML mismo se da su propio estilo (¡a la manera que hacíamos webs en los 90's! 😎), no necesito cargar archivos CSS o JavaScript extra en cada página del blog. Solamente se va a cargar ese HTML extra en cada entrada. O sea que elimino pedidos HTTP en todas las entradas al blog, y agrego unos poquitos KB de estilo en las entradas específicas que lleven código fuente resaltado.

Después de jugar un rato y probar cosas con rouge, escribí un script que iba modificando para probar distintos temas y código. Me entusiasmé y me armé un proyectito que me permitiera copiar y pegar más rápido y elegir lenguajes de programación y temas distintos fácilmente. Levanté una aplicación web mínima Sinatra con una sóla vista ERB y una ruta sola con GET para ver la vista y post para enviar el código fuente a través de un formulario con POST y procesarlo.

Por un momento me invadió ese pensamiento -a veces tan dañino- de sobre-optimizar (¿"over engineering"?) y usar algo más liviano que Sinatra. Porque es una aplicación mega sencilla que sólo necesita servir esa vista con pedidos GET y POST. Pero el tiempo que me hubiera tomado usar otra cosa de repente me distraía de terminar este proyecto. Sinatra es lo suficientemente liviano, y es un viejo conocido, así que era lo más rápido de usar para salir andando.

Mientras empezaba imaginé que debía existir algo ya hecho. Si bien no es exactamente lo mismo, encontré rouge.jneen.net, el sitio web archivado de rouge. En el código fuente aprendí algunas cosas prácticas que no encontré en la documentación. Encontré cómo definir un Lexer en base al lenguaje como parámetro. Como tengo un select HTML, me venía perfecto para definir los elementos option dinámicamente y que rouge supiera de qué le estoy hablando cuando le mando el valor elegido. En esa aplicación Rails, usan rouge::Lexer.find(@params[:language]), así que usé en mi aplicación Sinatra lexer = Rouge::Lexer.find(params['language']).

Otro código que saqué fue para listar todos los lenguajes de programación en el select de manera dinámica:

LANGS = Rouge::Lexer.all.sort_by(&:tag).map do |lexer|
  [lexer.respond_to?(:title) ? lexer.title : lexer.tag, lexer.tag]
end

Esto me da una colección de nombre/valor para usar el título para mostrar en el Select y el tag como valor que Rouge después interpreta para encontrar el lenguaje.

Así que los elementos que necesitaba fueron quedando:

  • Una textarea para pegar código fuente
  • Un menú de selección del tema: rouge provee varios temas, y si usamos el formateador HTML las clases CSS son compatibles con Pygments, un resaltador de sintaxis de Python.
  • Un menú de selección del lenguaje de programación del código fuente
  • Botón para enviar el formulario y ver tanto el resultado de cómo se ve el código fuente, como el código fuente HTML para mostrar el código fuente ingresado con resaltado de sintaxis 🤔

El resultado lo tiré en GitHub y se ve algo así:

Después de grabar este video le agregué alguna cosa más, incluyendo un input de texto para ingresar a mano un valor hexadecimal para el color de fondo de la capa de vista previa.

Actualizando el contenido

El siguiente paso (todavía en proceso) es reemplazar cada post donde usé el plugin con el nuevo formato. Y no puedo escribir sobre postear código fuente sin postear código fuente. Abrí mi gestor de base de datos y ejecuté la siguiente consulta:

SELECT * FROM `wp_posts`
WHERE post_content LIKE "%pre lang%"
AND post_type != "revision"
AND post_status = "publish";

De la tabla wp_posts, elegí los registros donde el contenido tenga pre lang, que es como funcionaba el plugin, excluir revisiones y sólo mostrar los posts publicados. Los que estén en borradores ya serán actualizados si alguna vez llego a editarlos para publicar...

Más de 150 para actualizar 🤔
¡Eso va a llevar un rato! risas de la audiencia.

Pero cambié el * en la sentencia SQL por guid, que nos da el "Post Global Unique Identifier" en WordPress. Parece un link pero WordPress nos dice que no debe usarse como un enlace (por si cambiamos el dominio o lo que sea). Pero es un link al post 😉
Desde phpMyAdmin mismo exporté los resultados a csv y ahora tengo un archivo local con una lista de enlaces, perdón guid's, de posts con código fuente en el viejo formato. Los iré actualizando de a poco hasta que no quede ni uno.

Mismo para los comentarios:

SELECT * FROM `wp_comments` WHERE comment_content LIKE "%pre lang%";

Cerca de 100... Otro CSV para exportar.

Últimos pasos y conclusión

El último paso va a ser la satisfacción de desactivar y desinstalar el plugin WP-Syntax. Está bueno depender menos de algo de terceros, y tener un poco más de control. Aparte que se siente como que le saqué algo de peso al blog. Encima, me entretuve programando algo de Ruby y aprendiendo sobre Rouge. A veces extraño SQL, así que disfruté de escribir unas consultas, por más sencillas que fueran, para encontrar los posts que debía actualizar. Y me dió algo para compartir por acá.

Eventualmente me gustaría automatizar un poco más el proceso, ya estoy pensando cómo podría hacerlo. Pero por ahora me sirve así.

No hay comentarios en este post

Feed de comentarios

Dejar un comentario

Toasty!