Matias49
head

Passer de GRAV à Jekyll, et inversement

16-06-2024

Ce que j'aime en tant que développeur, c'est que parfois on casse (malencontreusement) quelque chose qui fonctionnait. Et pour réparer, on va de temps à autre trop loin. Mini récit d'un court passage de GRAV à Jekyll.

Note : cet article est aussi un fourre-tout pour moi. Un mini bloc-notes sur toutes les actions que j'ai pu effectuer. Certaines parties sont très techniques, mais c'est bien d'être curieux.

À l'origine, je voulais un site pour y héberger mes propres avis, trop longs pour les 7000 caractères maximums de Steam. Et je pourrais y rajouter des images, des vidéos… Une espace sur laquelle je pouvais faire ce que je souhaitais. L'avantage en tant que développeur, c'est que je peux créer mon propre site, mon propre thème… ma propre plateforme. Ici par exemple : ce n’est peut-être pas le plus joli, c'est tout blanc (ou tout noir si vous êtes en mode sombre), mais le site est rapide à charger et consomme très peu.

Ce que je voulais

Un développeur doit faire beaucoup de veille. Et de tests. Avoir son propre site allait être très utile pour tout ça. Pour créer cette plateforme, je cherchais :

  • Quelque chose que je n’avais pas encore testé.
  • Qui gère le Markdown pour la rédaction d'articles. Je voulais pouvoir écrire sur un simple fichier texte sur téléphone, pas avoir à charger un Word ou autre, et le convertir d'une manière ou d'une autre pour le publier sur Internet. Je voulais pouvoir copier le Markdown sur le site et basta. (Spoiler : ce n’est malheureusement pas si facile).
  • De préférence en PHP, pour pouvoir l'héberger simplement sur n'importe quelle offre d'hébergement partagé, par exemple l'offre gratuite d'OVH.
  • Lié au point du dessus, de préférence qui ne nécessite pas une base de données, l'offre gratuite n'en fournissant pas.

GRAV

Lors de mes recherches en 2021, je suis tombé sur GRAV.

  • Gère le Markdown
  • Écrit en PHP
  • Ne nécessite pas de base de données
  • Le thème gère le TWIG, que je connais (et je ne voulais pas vraiment m'embêter sur la partie visuelle)

Et en bonus :

  • Une partie administration visuelle facultative
  • Lié au point au-dessus, pas de compte utilisateur obligatoire, limitant donc fortement la surface d'attaque
  • À un système d'optimisation des images (Spoiler : maintenant désactivé)
  • À une gestion du cache. Pourquoi pas, mais en tout cas idéal pour avoir un site performant.

Tous mes voyants étaient au vert. Il n'y avait plus qu'à l'installer, tester et publier un article. Et c'est ainsi qu'est sorti mon avis sur Temtem : 35 000 caractères, des images, des vidéos, le tout sur une page ultralégère (2,5 Mo alors qu'il y a 25 images… Allez voir la taille que prend une page d'un article classique maintenant). Avec GRAV, j'avais réussi à faire ce que je désirais.

L'AVIF, nouveau format…

Passons les quelques articles toujours en brouillon et sautons quelques années pour arriver en 2024. Je veux que mon avis sur "A Realm Reborn" ne soit plus que sur mon PC. Mais pour l'embellir un peu, je dois rajouter des images et vidéos.

Temtem a des images en WebP. Depuis mars 2024, on peut considérer l'AVIF comme le nouveau format d'images à utiliser sur le Web (sans transparence, j'y reviendrai peut-être une autre fois) : plus léger que le WebP à qualité égale et fonctionne sur tous les navigateurs supportés. La conversion en AVIF se fait simplement avec ffmpeg, et par rapport au WebP, l'image est environ 30 % plus petite en AVIF. Idéal pour avoir une page encore plus légère.

L'AVIF est compatible avec tous les navigateurs utilisés en 2024

… Un peu trop récent ?

J'ai enfin toutes mes images en AVIF et vidéos en AV1 (ce sera aussi dans un autre article), il ne manque plus qu'à les rajouter sur l'article et vérifier que tout est OK. Et là....

C'est le drame.

Une erreur, ça fait toujours plaisir

Je ne rentrerai pas dans les détails techniques (mais si vous voulez, l'origine du problème est ici), mais le système d'optimisation des images ne gère pas le format AVIF. Le format est géré par les navigateurs, mais les librairies de code, elles, pas forcément.

J'étais un peu coincé. Rajouter moi-même le support n'a, semble-t-il, pas fonctionné pour une raison que j'ignore encore, mais je n'ai pas passé beaucoup de temps dessus. Je pouvais abandonner l'AVIF et repartir sur du WebP, au détriment d'images plus lourdes, mais je ne voulais pas. Je voulais vraiment publier avec de l'AVIF.

Quitte à changer de plateforme.

Tout plaquer et passer à Jekyll

GRAV est un CMS, un système de gestion de contenu, peut-être trop complet pour ce qui veut être un pauvre blog avec des pages et des images. Pour seulement ces usages, un simple générateur de site statique fait l'affaire.

Le fonctionnement d'un générateur de site statique est très simple : une application va regarder le contenu d'un dossier, générer des pages HTML… Et voilà ! De simples pages HTML prêtes à être hébergées. Pas de PHP ou tout autre langage à installer/configurer sur le serveur. Juste des pages que n'importe quel serveur peut fournir. En termes de performances, c'est simplement imbattable : le serveur à juste a juste à fournir la page demandée au navigateur. Un CMS va quant à lui devoir générer la page et possiblement la mettre en cache, ce qui est plus lent. Et en termes de sécurité, c'est imbattable aussi : pas d'administration, de compte ou de système pouvant être attaqué. Un site… statique

Et dans cette catégorie, il y en a un que j'ai toujours voulu tester : Jekyll

Ce générateur existe depuis 2008 et reste très utilisé. Son seul inconvénient ? Il est écrit en Ruby, un langage pas forcément le plus simple à installer ou apprendre. Mais comme je voulais vraiment publier avec de l'AVIF, j'étais plus à ça prêt.

Alors, je crée une machine virtuelle sous Debian. J'installe Ruby sans souci et me voilà prêt à jouer avec Jekyll…

An error occurred while installing sass-embedded (1.58.3), and Bundler cannot continue.

Quand ça veut pas, ça veut pas. La version de Ruby fournie par Debian ainsi qu'Ubuntu ne marche pas avec la dernière version de Jekyll, et le site ne peut pas se générer correctement (ce problème et une possible résolution sont sortis après). Jamais je ne vais réussir à sortir cet article…

Un dernier espoir quand même : réussir à enfin faire fonctionner Docker.

Je ne rentrerai pas dans les détails de Docker. Voilà le lien vers la page Wikipédia. Toujours est-il que mon dernier essai de l'installer en mode "rootless" n'avait pas marché. Mais mon système ayant été mis à jour, ça ne coûtait rien de le tenter à nouveau. Je n'étais plus à ça prêt.

Je retente l'installation de Docker… qui cette fois fonctionne. Je peux enfin avancer.

J'essaie d'adapter les instructions de la documentation dans un Dockerfile, monte un volume… Et ça marche. Jekyll se lance et me génère les pages de base.

# Jekyll Dockerfile
FROM ruby

RUN gem install jekyll bundler
WORKDIR /usr/src/app
COPY Gemfile ./
RUN bundle install

# Commande docker-compose
command: bash -c "bundle exec jekyll serve --trace"

Migrer le site

Jekyll est donc un générateur de site Web et se base sur des fichiers texte au format Markdown ou HTML pour générer les pages. Au contraire de GRAV, la notion de thème n'existe pas vraiment. GRAV a tout un système avec du Twig, et où un template est utilisé par rapport au nom du fichier. Jekyll, lui, a juste le système de template et chaque fichier indique quel template utiliser pour générer la page. Les deux ont néanmoins la méthode d'include, et la migration s'est finalement avérée très simple.

Le thème

Adapter le thème à Liquid, le moteur utilisé par Jekyll, était très facile :

  • Copier les fichiers
  • Remplacer les instructions Twig par les instructions Jekyll. Leur syntaxe est quasiment identique
  • Adapter les appels CSS et JS

Là où GRAV a le système de blocs TWIG, Jekyll ne l'a pas. Il faut donc improviser.

{# GRAV : article.html.twig #}

{% extends 'partials/base.html.twig' %}
{% block content %}
<div class="max-w-none prose dark:prose-invert">
    {% if config.get('plugins.page-toc.active') or attribute(page.header, 'page-toc').active %}
        {% set table_of_contents = toc(page.content) %}
        {% if table_of_contents is not empty %}
            <div class="w-full border-2 border-gray-200">
                {{ table_of_contents|raw }}
            </div>
        {% endif %}
    {% endif %}
    <div class="px-2 article lg:px0">
        {{ page.content|slice(page.summary|length)|raw }}
    </div>
</div>
{% endblock %}
{# Jekyll #}
{# default.html #}
<body id="top" class="dark:bg-black dark:text-white">
    {{ content }}
</body>

{# article.html #}
# {{ content }} sera remplacé par le contenu
---
layout: default
---
<div class="max-w-none prose dark:prose-invert">
    <div class="w-full border-2 border-gray-200">
        {% toc %}
    </div>
    <div class="px-2 article lg:px0">
        {{ page.content | remove: page.excerpt }}
    </div>
</div>

Pour la génération du CSS et JS, j'ai copié le système maison que j'avais avec Tailwind. Il faut néanmoins modifier la configuration pour cibler les chemins Jekyll à analyser (par exemple le dossier "_posts").

Les articles

Ici également, c'était facile. Les deux systèmes utilisent Markdown. Le texte peut être copié-collé et la page générée sera presque identique. Presque, puisqu'il faut adapter l'entête du fichier ainsi que son nom : Jekyll attend un nom de fichier du type "ANNEE-MOIS-JOUR-TITRE.md".

Pour l'entête, aussi appelé "Frontmatter", il faut indiquer sur Jekyll quel template utilisé, ainsi que quelques données SEO pour les modules. Rien de bien compliqué.

# GRAV: 20240426_ffxiv_arr/article.md
# Notez l'indentation à partir de metadata. C'est principalement celle-ci qu'on supprime
---
published: true

title: "Final Fantasy XIV Online: A Realm Reborn"
slug: ffxiv-a-realm-reborn
date: 11.05.2024

page-toc:
  active: true

#'theme-color': #002A9E
metadata:
  'description' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
  'og:title' : "Final Fantasy XIV Online: A Realm Reborn"
  'og:site_name' : Matias49
  'og:description' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
  'og:image' : https://matias49.eu/user/pages/02.articles/20240426_ffxiv_arr/head.webp
  'twitter:card' : summary_large_image
  'twitter:site' : MMatias49
  'twitter:creator' : MMatias49
  'twitter:title' : "Final Fantasy XIV Online: A Realm Reborn"
  'twitter:description ' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
  'twitter:image' : https://matias49.eu/user/pages/02.articles/20240426_ffxiv_arr/head.webp
---
# Jekyll: 2024-05-11-ffxiv-a-realm-reborn.md
# "layout" est nécessaire pour définir quel template utiliser pour générer la page, ainsi que 'image' et 'image_avif' qui arriveront après.
---
layout: post
published: true
image: "/assets/articles/ffxiv-arr/head.webp"
image_avif: "/assets/articles/ffxiv-arr/head.avif"
title: "Final Fantasy XIV Online: A Realm Reborn"
date: 11.05.2024

toc: true

#'theme-color': #002A9E
'description' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
'og:title' : "Final Fantasy XIV Online: A Realm Reborn"
'og:site_name' : Matias49
'og:description' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
'og:image' : "https://matias49.eu/assets/articles/ffxiv-arr/head.avif"
'twitter:card' : summary_large_image
'twitter:site' : MMatias49
'twitter:creator' : MMatias49
'twitter:title' : "Final Fantasy XIV Online: A Realm Reborn"
'twitter:description ' : "3 ans que ce pavé attend d'être publié. Faut que j'arrête les pavés en fait. Trop longs, pas lus. Mais ça doit sortir : le début de FFXIV ne m'a pas plu."
'twitter:image' : "https://matias49.eu/assets/articles/ffxiv-arr/head.avif"
---

Le cas des médias

Les images et vidéos auront été le sujet tout au long de cet article, et le passage à Jekyll n'en aura pas fait exception.

Jekyll ne gère pas les fichiers média au format relatif (au même niveau que l'article). Sur GRAV, j'avais un dossier par article, avec le texte et un sous-dossier d'image. Mon thème allait rechercher certaines images de ce dossier, et le texte faisait référence à ce sous-dossier de média pour aller chercher les images et vidéos.

Sur Jekyll, le dossier "_posts" ne peut contenir que des fichiers texte. Il faut mettre les médias ailleurs (un dossier "assets" à la racine est préconisé dans la documentation). Un module existe, "jekyll-postfiles", mais j'ai préféré ne pas m'en servir et rester sur de l'officiel.

Pour la recherche d'image par le thème, j'ai dû indiquer dans l'entête de l'article quelles images afficher.

{# GRAV: article.html.twig #}
{% if page.media['head.avif'] is not empty %}
    {{ page.media['head.avif'].html('head', 'head', 'mx-auto')|raw }}
{% else %}
    {{ page.media['head.webp'].resize(900,506).cache.html('head', 'head', 'mx-auto')|raw }}
{% endif %}
{# Jekyll: post.html #}
{# C'est ici qu'on fait référence au 'image' et 'image_avif' du frontmatter. #}
{% if page.image_avif %}
    <img class="mx-auto" src="{{ page.image_avif }}" loading="eager"/>
{% else %}
    <img class="mx-auto" src="{{ page.image }}" loading="eager"/>
{% endif %}

Pour l'affichage des médias dans l'article, c'est là où je me retrouve coincé par mes besoins un peu spéciaux.

Si je veux rajouter une légende à une image, l'option n'existe pas nativement en Markdown. Je peux écrire le bloc HTML "figure" et "figcaption" entier à chaque image, mais c'est chiant. GRAV a un module qui permet de faciliter la saisie en Markdown, mais un lecteur Markdown classique n'affichera plus l'image. Et ce plugin n'existant pas sur Jekyll, il a fallu… écrire le bloc HTML entier à chaque image !

Néanmoins, quitte à avoir l'affichage cassé sur un lecteur Markdown, autant profiter du système d'includes proposé par Jekyll.

<!-- GRAV: affichage d'une image avec classes CSS et légende -->
![images/hotbar.avif](images/hotbar.avif?classes=caption,mx-auto "Ceci est une barre d'action complète pour un des rôles (samourai). Avec de l'entrainement, les actions deviennent fluides.")
{# Jekyll: inclusion du bloc HTML complet #}
{% include figure.html image="/assets/articles/ffxiv-arr/hotbar.avif" caption="Ceci est une barre d'action complète pour un des rôles (samourai). Avec de l'entrainement, les actions deviennent fluides." %}

{# _includes/figure.html #}
<figure>
    <img class="caption mx-auto" loading="lazy" src="{{ include.image }}" alt="{{ include.caption }}"/>
    <figcaption class="text-center">{{ include.caption }}</figcaption>
</figure>

Les vidéos vont avoir un principe similaire, à une différence assez notable : les navigateurs gèrent mal le "lazy-loading" sur les vidéos. Impossible d'utiliser la syntaxe GRAV pour afficher une vidéo, il faut écrire le bloc HTML en entier, et utiliser un peu de Javascript pour effectuer ce "lazy-loading".

<!-- GRAV: On force la taille, et pour avoir le lazyload, il faut utiliser du Javascript qui va renommer les "data-src" en juste "src" -->
<!-- L'histoire des deux "source" sera dans un autre article ! -->
<video width="900" height="506" class="lazy caption mx-auto" autoplay muted loop playsinline alt="Écran de démarrage du jeu" preload="none" data-poster="/user/pages/02.articles/20240426_ffxiv_arr/images/boot-poster.avif">
    <source data-src="/user/pages/02.articles/20240426_ffxiv_arr/images/boot.webm" type='video/webm; codecs="av01.0.04M.08"' />
    <source data-src="/user/pages/02.articles/20240426_ffxiv_arr/images/boot-vp9.webm" type='video/webm; codecs="vp9"' />
    <p>Your browser does not support the video tag.</p>
</video>
{# Jekyll: inclusion de ce même bloc vidéo #}
{% include video.html av1="/assets/articles/ffxiv-arr/boot.webm" vp9="/assets/articles/ffxiv-arr/boot-vp9.webm" poster="/assets/articles/ffxiv-arr/boot-poster.avif" alt="Écran de démarrage du jeu" %}

{# _includes/video.html #}
<video preload="none" alt="{{ include.alt }}" autoplay class="lazy mx-auto" loop muted playsinline {% if include.poster %} data-poster="{{ include.poster }}" {% endif %}>
    {% if include.av1 %}
        <source data-src="{{ include.av1 }}" type='video/webm; codecs="av01.0.04M.08"' />
    {% endif %}
    {% if include.vp9 %}
        <source data-src="{{ include.vp9 }}" type='video/webm; codecs="vp9"' />
    {% endif %}
    <p>
    Your browser does not support the video tag.
    </p>
</video>

L'AVIF en lui-même ne pose aucun souci : Jekyll ne fournit aucun système d'optimisation d'image. Il affiche simplement l'image qui a été envoyée avec l'article.

Les modules

J'utilise sur GRAV certains modules. Pour avoir une table des matières, les métadonnées SEO…

J'ai heureusement retrouvé des modules similaires sur Jekyll : "jekyll-seo-tag" et "jekyll-toc".

Changer de système est aussi l'occasion de regarder quelles fonctionnalités peuvent être rajoutées : un flux RSS ? Un Sitemap ? Et pourquoi s'arrêter là ? J'ai même trouvé un plugin pour forcer tous les liens externes à s'ouvrir dans un nouvel onglet. Aucun risque d'en oublier.

GRAV peut être configuré afin de configurer les images à se charger en "lazy-load", juste avant leur affichage, ce qui permet d'améliorer le changement initial. Jekyll n'a pas cette option, et même si mon bloc HTML d'affichage des images le fait déjà, il y a là aussi un module.

J'ai donc retrouvé sur Jekyll toutes les fonctionnalités que j'avais sur GRAV, notamment grâce aux différents modules. Sa popularité aide sûrement à avoir cette communauté derrière et toutes ces extensions.

Retour arrière

Mais si mon problème avec GRAV n'était que de la configuration ? Je n'arrive pas à utiliser l'AVIF sur GRAV à cause de l'optimisation d'image que j'ai activé. Si je la désactivais et optimisais moi-même ?

Créer des médias vraiment optimisés soi-même prend du temps, mais je ne dépendrai plus d'un système intermédiaire en réalité douteux : sur l'article Temtem, les images optimisées par GRAV étaient finalement plus lourdes que celles que j'avais moi-même mises sur l'article, et je ne l'ai remarqué qu'au moment de migrer sur Jekyll.

J'ai donc commencé par supprimer la configuration pour tester… L'article s'affichait ainsi que les images AVIF. Il suffisait de refaire la configuration globale de GRAV en désactivant le système d'optimisation des images. Tout ça pour ça, mais on adore se compliquer la tâche

Quitte à repartir sur GRAV, autant vérifier que les nouvelles fonctionnalités sont bien possibles sur le système. Et, heureusement, c'est le cas :

Bonus : Les blocs de code

Cet article est le premier ici à afficher des blocs de code. L'occasion de voir comment GRAV et Jekyll gèrent leur affichage. Et il n'y a heureusement pas eu de grande surprise.

Le Markdown gère sans soucis les blocs de code, avec les repères ```, les moteurs utilisés par GRAV et Jekyll les convertissent facilement et je n'ai rien à modifier (contrairement aux médias). Mais GRAV n'a pas par défaut de système proposant une coloration syntaxique du code, contrairement à Jekyll, qui inclut "Rouge", mais qu'il faut paramétrer.

GRAV n'a rien par défaut, mais il existe deux modules : un officiel, mais pour lequel j'ai eu quelques surprises sur la coloration appliquée, et un communautaire, que j'ai conservé, qui propose plus de fonctionnalités.

La surprise est plutôt venue du côté de Jekyll, qui s'est retrouvé à interpréter tous les blocs de code que j'ai ajouté dans l'article, bloquant la génération de la page. Heureusement, la documentation donne quelques informations pour désactiver cette interprétation automatique. Il faut rajouter cette instruction dans la section Frontmatter, mais aussi rajouter une feuille de style pour le thème utilisé par ”Rouge”.

La suite

Le site est à nouveau sous GRAV, après avoir refait la configuration et désactivé le système d'optimisation automatique des images. Être passé à Jekyll m'a permis de revoir le fonctionnement de GRAV et améliorer mon système en y ajoutant des extensions. Je me retrouve donc avec deux systèmes qui me permettent d'avoir ce blog. Lequel garder finalement ? Au niveau de la rédaction des articles, les deux demandent à faire des modifications pour y rajouter les images, et aucune des deux solutions ne l'emporte sur l'autre. Le système plus complet de GRAV permet de voir un peu plus loin sur les possibilités. Mais sur un site comme celui-ci, les deux solutions font largement l'affaire. Mon choix final sera juste une question de préférence, et il n'est pas impossible que le site passe d'une solution à l'autre de temps en temps. Me voilà bien avancé…

Matias49