Me voilà enfin débarrassé de deux outils sur lesquels je (et d’autres) râle depuis quelques temps : Hugo, et Tailwind.
Maintenant, on va pouvoir s’amuser. Maintenant, je vais avoir un blog véritablement à mon image, épuré de technologies qui ne me conviennent pas, auxquelles je n’adhère pas, utilisées juste sous prétexte que c’est hype. Ce nouveau site, c’est 100% Richard Dern (ou presque).
Il va donc falloir vous habituer à ces inclusions d’articles dans l’article parce que j’en suis assez fan personnellement. Je ne m’en servirai jamais pour introduire un contenu qui n’a rien à faire là : ces inclusions seront toujours pertinentes, mais c’est là l’occasion pour moi d’en abuser à titre démonstratif. J’ai juste à faire :
<x-read-more :urls="/blog/2023/04/18/refonte-du-site/" />
Ou :
<x-read-more :urls="[
'/liens-interessants/2023/08/11/3ebdad4c8bd168e0d3f29a64341866a3/',
'/blog/2023/04/06/rant-mon-site-c-est-de-la-merde/',
'/blog/2022/02/12/rant-hugo-et-tailwind/'
]" />
Comme au début de cet article, pour que mes inclusions se fassent. De façon plus générale, je peux travailler avec Blade dans les articles, et donc en PHP. Évidemment, pour un développeur PHP, c’est beaucoup plus agréable…
J’ai développé mon propre générateur de site statique. Il n’est pas fait pour être redistribué : n’espérez pas télécharger les sources et lancer votre propre blog. Déjà parce que les sources de mes propre articles sont incluses1, et puis parce que je l’ai développé pour répondre à mes besoins et envies spécifiques. Par contre, j’encourage la lecture du code : vous y trouverez peut-être une bonne idée ou deux.
Le moteur repose sur Laravel dans sa dernière version. La partie UI/UX, c’est moi, vraiment à 100%. Non pas qu’il y ait de quoi se vanter, je ne cherche pas à faire du beau, je cherche à faire du “moi” : c’est pas très beau parce que je n’ai aucun goût (pas pour rien que je suis développeur back-end), mais je suis content du résultat.
Pas de framework pour le CSS, j’ai tout fait à la main, et ça correspond enfin à une esthétique de laquelle je me sens proche. Les sources du CSS sont moches et pas franchement optimisées, j’ai du refactoring à faire, mais c’est ce qui arrive quand on ne sait pas trop où l’on va. Mais au final, je suis assez satisfait de mes choix esthétiques. Encore une fois, je sais que ça ne plaira pas à tout le monde, mais pour mon blog, c’est terminé : je n’utiliserai plus rien de prémâché. Je me considère comme un artisan, je fais de l’artisanat, et puis zut.
Promis, je ne ferai pas trop d’inclusions sauvages dans les futurs articles, mais j’aime bien mon nouveau super-pouvoir…
En ce qui concerne les fonctionnalités, on va dire que je considère disposer d’un Minimum Viable Product : dans mon cas, cela signifie que je dispose à peu près des mêmes fonctionnalités que celles que j’utilisais avec Hugo.
En fait, mes sources markdown sont récupérées pratiquement telles quelles, seuls les shortcodes ont été un peu chiants à réécrire parce que cela impliquait changer tous les appels bizarres à {{< shortcode [...] />}}
, là où maintenant j’ai des balises, beaucoup mieux question interopérabilité.
Puisque j’ai là un MVP, et qu’en plus de dégager Hugo et Tailwind je poursuivais d’autres objectifs, je vais désormais pouvoir faire des choses vraiment intéressantes que je veux faire depuis longtemps sans savoir comment m’y prendre avec Hugo.
J’ai déjà un système partiellement fonctionnel pour les liens externes et internes mais je veux aller plus loin. À titre informatif, voilà ce que je dois faire sous Hugo :
{{- /*
Putain mais que c'est dégueulasse...
Les séquences d'échappement sont pour éviter les espaces indésirables dans les
liens.
Le but ici est de changer l'aspect des liens s'ils pointent vers une page
externe ou interne, et dans ce dernier cas, en fonction de la section concernée.
*/ -}}{{ $ext := strings.HasPrefix .Destination "http" }}{{- /* Check si l'URL est absolue ou relative
*/ -}}{{ $url := .Destination | safeURL }}{{- /* Hugo déconne plus loin sans ça...
*/ -}}{{ $content := .Text | safeHTML }}{{- /*
*/ -}}{{ if $ext }}{{- /* Lien externe
*/ -}}<a rel="noopener noreferrer" class="link external inline-flex items-center space-x-1"
title="Lien externe : {{ $url }}" href="{{ $url }}">{{- /*
*/ -}}<span>{{ $content }}</span>{{- /*
*/ -}}<span class="icon-[ri--external-link-line] h-4 w-4 text-slate-400 ml-1"></span>{{- /*
*/ -}}</a>{{- /*
*/ -}}{{ else }}{{- /* Lien interne
🤮 */ -}}{{ with (index (where .Page.Site.AllPages "RelPermalink" .Destination) 0) }}{{- /* Référence à la page liée
🤮 */ -}}{{ $date := .PublishDate | time.Format ":date_long" }}{{- /*
🤮 */ -}}{{ $section := .Parent }}{{- /* .Section ne fonctionne pas...
🤮 */ -}}{{ $title := "" }}{{- /* Le titre du lien est la concaténation de plusieurs éléments ci-dessous...
🤮 */ -}}{{ $parentTitle := delimit (slice "[" .Parent.Title "] ") "" }}{{- /*
🤮 */ -}}{{ if eq .Section "tags" }}{{- /* On ne met pas de date de publication si c'est un lien vers un mot-clé
🤮 */ -}}{{ $title = delimit (slice $parentTitle .Title) "" }}{{- /*
🤮 */ -}}{{ else }}{{- /*
🤮 */ -}}{{ $formatedDate := delimit (slice ", publié le" $date) " " }}{{- /*
🤮 */ -}}{{ $title = delimit (slice $parentTitle .Title $formatedDate) "" }}{{- /*
🤮 */ -}}{{ end }}{{- /*
*/ -}}<a rel="noopener noreferrer" class="link internal inline-flex items-center space-x-1" title="{{ $title }}"
href="{{ $url }}">{{- /*
*/ -}}{{ if eq .Section "tags" }}{{- /*
*/ -}}<span class="icon-[bi--tags-fill] text-emerald-600 h-4 w-4"></span>{{- /*
*/ -}}{{ else }}{{- /*
*/ -}}<span class="{{ $section.Params.icon }} {{ $section.Params.color }} h-4 w-4"></span>{{- /*
*/ -}}{{ end }}{{- /*
*/ -}}<span>{{ $content }}</span>{{- /*
*/ -}}</a>{{- /*
*/ -}}{{ end }}{{- /*
*/ -}}{{ end }}{{- /*
*/ -}}
C’est juste a-bo-mi-na-ble. À gerber. Avec PHP/Laravel/mon moteur, toute la logique se fait là où elle doit être faite : dans un fichier PHP, et l’affichage du résultat se fait là où il doit être fait : dans un fichier HTML. Plus exactement, c’est le parseur CommonMark de PHPLeague qui m’offre la possibilité de détecter un lien et le modifier pour affichage. C’est propre, lisible, maintenable. On pourra toujours m’objecter que c’est parce que je ne maîtrise pas Go.
Spoiler
Non, c'est juste que Go c'est de la merde. En particulier son templating mais je ne vais pas radoter.
En fait, le développement de mon moteur s’est fait en plusieurs phases. La première phase, j’ai tenté de mimer le fonctionnement d’Hugo. Les performances étaient excellentes, parfois meilleures (alors qu’Hugo se prend pour le plus rapide du monde). Mais très vite je suis arrivé aux mêmes limitations : toute modification dans ce que je veux faire prend des proportions dantesques et rend très vite le code dégueulasse. Si Go, c’est de la merde, en fait, ça va un peu plus loin que ça (évidemment).
Je critique donc un paradigme de programmation qui s’est très vite répandu (et qui d’ailleurs, infeste les 3/4 du web), c’est le principe du Just-in-Time. En gros, on repousse l’exécution d’une instruction jusqu’au tout dernier instant. Au lieu de lancer quelques instructions préliminaires pendant lesquels l’utilisateur a l’impression que rien ne se passe ou que sa machine rame, toute instruction est divisée en plein de micro-instructions qui ne seront exécutées que lorsque l’utilisateur veut que quelque chose se passe.
C’est assez difficile à expliquer concrètement mais ce qui rend Hugo si rapide, c’est qu’il ne génère le rendu d’une page que lorsqu’il n’a plus le choix. Il diffère le plus possible le plus d’instructions possibles, de sorte à économiser les ressources.
Ma deuxième tentative aurait pu me faire croire que c’était effectivement la meilleure chose à faire. J’ai décidé d’opter pour une approche qui anticipe beaucoup plus les choses, avec une étape de collecte d’informations, une étape de transformation, et une étape d’enregistrement, le tout en utilisant massivement toutes sortes de caches, au risque de dupliquer des caches que Laravel aurait déjà fait pour moi “sans que je sois au courant”. Il en a résulté une application vraiment lente (plus de 10 minutes pour une génération du site à froid).
Et puis, je me suis dit que ce n’est pas parce que je développe un site statique que mon moteur doit être minimaliste : je veux que mon moteur génère un site statique de la façon la plus personnalisée possible, c’est-à-dire qui respecte mes besoins et envies. J’ai ainsi abandonné l’idée de faire une application sans base de données, et je suis reparti d’une feuille blanche pour la dernière fois.
Il faut environ 1m30 à Hugo pour générer à froid (caches vides) l’ensemble de mon site, dans un container docker avec la surcouche Rosetta d’Apple. Il ne lui faut ensuite plus que 8 secondes pour “rafraîchir” le site après une petite modification.
Mon générateur est exactement aussi rapide. À la seconde près, en fait.
Mais la vraie force de mon générateur par rapport à Hugo est sa capacité à identifier et m’aider à corriger les problèmes.
Actuellement, il est capable de me dire quels fichiers sources devraient être renommés (par exemple, /test-du-bidule-machin.md
vers /test-du-bidule-machin/index.md
), quels attributs front matter sont problématiques (inutilisés, obsolètes, mal-formés, orphelins, etc.), lister les liens morts, etc.
Et il peut corriger tout seul certains de ces problèmes.
Ce qui traduit un deuxième paradigme moderne sur lequel je peste aussi tout le temps : il faut cacher les problèmes aux utilisateurs. Ils ne doivent pas savoir qu’il se passe un truc pas normal, on doit poursuivre l’exécution coûte que coûte. Du coup, j’ai des anomalies dans mes articles, et Hugo s’en fout, mon site est généré.
Mon moteur va me dire (prochainement) s’il existe des mots-clés orphelins, sous-utilisés, ou qui se rapprochent sémantiquement d’autres mots-clés pré-existants, voire corriger automatiquement des erreurs de syntaxe à la con (j’avais un mot-clé appelé “Intelligence Atificielle” : vas demander à Hugo de rajouter le “r” manquant et mettre à jour tout seul les pages qui référencent ce mot-clé erroné).
Autre fonctionnalité qu’il me tarde de mettre en place : les dossiers. Une façon de regrouper des articles éparpillés dans le temps ou dans les sections du site, d’une façon ordonnée, triée et navigable. Je pense évidemment à mon livre L’humain, cette espèce primitive dont la lecture est actuellement hasardeuse et décousue. Mais ça serait pertinent pour d’autres séries d’articles, telles que :
Enfin, j’aimerais des liens entre taxonomies. Par exemple, pour les films et séries, j’ai une taxonomie pour les acteurs et une taxonomie pour les personnages fictifs, mais rien pour dire que Bryce Dallas Howard interprète Claire Dearing dans la saga Jurassic World. Je veux, dans un avenir proche, que si vous êtes sur la page consacrée à Bryce Dallas Howard, vous voyez le personnage qu’elle a interprété et dans quel(s) film(s) et séries. Auparavant, sous Hugo, je n’avais pas la moindre idée de la faisabilité du truc. Maintenant, j’ai beaucoup plus de flexibilité à disposition.
J’ai évidemment un peu de polissage à faire, et une tonne de choses dans ma TODO pour ce nouveau moteur, mais en l’état, j’en suis très content. Il y a de la marge pour l’améliorer, comme le veut l’expression consacrée, mais je peux déjà en faire plus qu’après deux ans d’expérience avec Hugo.
Maintenant, je me sens un peu plus chez moi.
Ce n’est plus le cas depuis le 6 septembre : sources des articles ↩