Nouveau blog -- partie 1 : HTML

Un panda roux observe, dans un atelier cosy inspiré de Ghibli, des feuilles Markdown se transformer en balises HTML dorées, entre magazines tech futuristes et écrans holographiques en bois sculpté.

J'avais évoqué sur le blog le changement de plateforme en 2024, donc ça va faire bientôt 3 ans que le sujet est en cours de mon côté (quand j'en ai parlé en 2024 j'avais déjà pas mal travaillé sur le sujet dans l'année…), et c'est enfin une réalité ! Enfin depuis quelques articles déjà, mais je n'ai pas fait d'annonce en grande pompe non pour me laisser l'option de revenir en arrière si je voyais que ça ne fonctionnait pas.

En tout cas, après plus de 9 ans avec Ghost, je tourne définitivement la page Ghost pour quelque chose de différent et je vous explique tout ça !

Si vous êtes habitués à mes articles hebdomadaires sans liens directs entre eux, je vais trancher avec mes habitudes et je vais créer la première série du blog !

Mais promis : quand j'aurai fini je reprendrais un rythme plus normal et surtout moins auto-centré ! 😅

Je pars de quoi vers quoi ?🔗

Si vous n'êtes pas intéressé par le cheminement qui m'a amené au résultat actuel, vous pouvez complètement sauter cette partie.

Sans reprendre toute mon histoire de blogging (un article existe déjà sur le sujet si vous êtes curieux), j'ai commencé ce blog en installant un Ghost sur un serveur dédié en version 0.x.y. À l'époque j'étais séduit par le fait de pouvoir rédiger en Markdown et par le stockage en Markdown aussi ! Ça me garantissait un minimum de capacité à sortir le contenu si le besoin s'en faisait sortir un jour. J'ai donc fait avec les quelques ratés de l'éditeur web à l'époque pour commencer à rédiger. Puis j'ai suivi les versions avant de me retrouver bloquer en version 3. La version 4 me provoquant une perte complète de toutes les images…

Avoir des versions majeures de retard n'étaient pas acceptables à mon sens. Le stockage en base de donnée qui avait largement évolué aussi pour ne plus être du Markdown aussi. Markdown ayant été relégué à une option de formatage dans un bloc de texte…

Note : je peux sembler extrêmement critique envers Ghost, c'est vrai mais surtout en ce qui concerne mon rapport à Ghost et à son éloignement entre l'évolution de Ghost et mon usage. Je pense que Ghost est un outil qui peut correspondre à pas mal de gens, modulo qu'on prenne bien le temps de comprendre les limitations de la plateforme (comme toutes plateformes, quand bien même c'est rarement fait) et que ça n'est pas un bloqueur.

À cette période un flottement s'est créé chez moi : quitter Ghost après tant de temps ? Mais créer mon outil prendrait trop de temps / d'effort…

Et puis j'ai découvert Astro, qui est un framework web orienté contenu, super performant, avec plein de facilités, supportant Markdown nativement, etc. Je me suis donc lancé dans un PoC en mars 2023 à travers la création de GoReadYourself, ma plateforme de "micro blogging" maison (mon instance est accessible ici) qui m'aide à préparer mes revues de presse. J'ai très très vite pu utiliser GoReadYourself au quotidien, ça rempli encore en grande partie le travail que j'en attends, et je me suis convaincu que c'est ce dont j'avais besoin pour mon blog !

J'ai donc commencé à travailler sur une re-création de mon blog en juillet 2023. Et j'ai avancé doucement, ajoutant des briques, retouchant des choses, ajustant des détails, me perdant parfois une journée sur certains détails, en ajoutant plein de petits trucs que je n'avais même pas sur Ghost pour me convaincre de la plus-value, sans jamais mettre en ligne de version test, ne sachant pas clairement comment je voulais déployer le blog, etc. En gros : j'ai fait à la fois les erreurs que je citais dans mon article "Presque 15 ans de presque blogging" et aussi de la plupart des projets qui n'arrivent jamais en production… Et j'ai choisi la solution technique par envie de l'utiliser plutôt que pour répondre au besoin… Le dernier commit date de novembre 2024, je ne voyais plus l'intérêt de poursuivre, car je voyais bien que je m'enlisais…

J'ai laissé quelques mois passer, puis j'ai tranché : j'ai fait page blanche, je suis reparti du besoin, j'ai défini un MVP avec 4 lots, j'ai créé une branche sur dépôt git de mon blog et j'ai commencé à coder en me concentrant uniquement sur les fonctionnalités nécessaires pour une mise en ligne, mettant de côté pour plus tard tout le reste. Je travaille pas mal dessus dans l'été, j'ai continué quand j'avais du temps sur la fin d'année jusqu'à me dire "c'est bon je pousse en ligne" à la mi-décembre.

Le Markdown au centre🔗

Ça fait très longtemps que je travaille avec du Markdown. Pour moi Aaron Swartz et John Gruber ont créé un langage extrêmement puissant sur pas mal d'aspect : il est pensé pour être converti facilement en HTML, il permet de se concentrer sur le contenu et pas la forme ou la structure. Il y a beaucoup à dire sur les langages de markup comme Markdown, AsciiDoc ou même TeX (j'ai passé une grosse partie de mes études à écrire du LaTeX et je pense que ça m'a vraiment aidé pour la suite !), mais je pense que ce n'est pas le sujet du jour !

Pour moi c'était logique d'écrire sur mon blog avec du Markdown. C'est pour ça que j'avais choisi Ghost. C'est pour ça que j'ai quitté Ghost. Ça fait presque 4 ans que je suis revenu à l'écriture en pur Markdown et un dépôt git. Je peux écrire partout, avec mon éditeur du quotidien (actuellement VSCode), mes habitudes côté raccourcis clavier, même sans Internet au besoin (j'écris beaucoup dans le train, avec un Internet intermittent ; parfois juste je pars avec mon laptop pour me poser dans un parc pour être au calme et me couper des notifications). Jusque-là je copiais à la main le contenu des articles vers Ghost pour les publier. Je perdais entre 20 et 45 minutes (parfois plus…) à faire ce report chaque semaine pour mon confort d'écriture.

Maintenant je convertis directement mes fichiers Markdown dans le HTML que vous voyez (enfin presque, je vous explique plus loin) de sorte à juste avoir à écrire et commit pour mettre en ligne. Tout est automatisé. Tout est fluide pour moi.

Deno et Marked comme seules dépendances🔗

J'en ai déjà parlé sur le blog, je suis partisan d'avoir le moins de dépendance possible. Chaque dépendance implique des mises à jours, des failles de sécurités, etc. Donc avoir le moins de dépendance possible était important pour moi.

J'ai choisi de dépendre vraiment que de deux outils : Deno et Marked.

J'ai choisi Deno parce que je pense que c'est le moteur qu'on devrait utiliser partout aujourd'hui. Je sais, certains diront que Bun aurait été un meilleur choix, bla bla bla. Deno n'a pas juste copié Node, il est pensé pour la sécurité à tous les niveaux : choix de Rust pour que le compilateur garantisse autant que possible une bonne gestion mémoire et choix d'un modèle de permission strict pour limiter au maximum les accès aux différents éléments de la machine hôte. Deno a aussi une DX (Developer eXperience) excellente : vous n'avez aucune configuration à faire pour faire du TypeScript (first-class citizen), vous n'avez pas à vous battre avec les dépendances, vous n'avez pas de node_modules.

Je sais que Deno n'est pas le moteur le plus performant selon les benchmarks. Et alors ? Déjà, je n'ai pas besoin de très haute performance. Ensuite les benchmarks valent toujours ce qu'ils valent : pas grand-chose par rapport à la réalité qui est que les gens utilisent généralement mal leurs outils et donc perdent beaucoup de performance par le code qu'ils écrivent ou les dépendances choisies. Et aussi le réalisme des benchmarks ; la plupart des cas que j'ai vu c'est un serveur web qui fait un "Hello, World!" en retour d'un appel HTTP, pas vraiment ce qu'on fait dans la vraie vie.

En tout et pour tout, j'ai zéro lignes de configuration Deno / TypeScript / autre pour pouvoir écrire du code qui fonctionne. J'ai un fichier deno.json qui contient des tasks (équivalent des scripts du package.json Node) et quelques lignes de dépendances, rien de plus.

Vient ensuite ma seconde dépendances : Marked. Il s'agit d'une librairie très extensible pour convertir du Markdown en HTML. J'aurai pu écrire la mienne et perdre des semaines à créer une librairie avec un aussi bon support de CommonMarks qui passent les 652 cas de tests, mais profiter de l'intelligence collective qui a mené à Marked était un meilleur choix. Il y a surement des outils plus rapides, mais Marked est facile d'usage, extensible et déjà bien rapide (convertir les plus de 200 articles de mon blog prend entre 500ms et 800ms).

J'ai par contre développé six plugins pour mes besoins spécifiques et pouvoir améliorer (à mon sens en tout cas) le HTML produit :

  • @anthonypena/marked-inline-ascii-punctuation : pour automatiquement convertir certaines ponctuations qui sont peu pratiques à taper au clavier (par exemple ... est transformé en ) ;
  • @anthonypena/marked-heading-links : pour ajouter un lien à chaque titre des articles de sorte à pouvoir pointer une partie directement ;
  • @anthonypena/marked-better-image : pour afficher les images de manière plus propre (pas une simple balise <img/> mais une <figure> avec une description) ;
  • @anthonypena/marked-image-gallery : pour gérer des galeries d'images quand j'ai plusieurs images qui se suivent ;
  • @anthonypena/marked-djot-div : pour ajouter le support de la syntaxe des <div> venants de Djot (un langage dérivé de CommonMark), j'avais écrit le plugin pour tester un truc, mais je ne l'utilise pas encore sur le blog, comme je ne pouvais pas avoir d'équivalent dans Ghost ;
  • @anthonypena/marked-toc-for-series : pour ajouter un sommaire de série avec des liens entre les articles au début de l'article (vous le verrez apparaitre au moment de la partie 2 de cette série) ;

Pour être honnête, j'utilise aussi Highlight.js mais je n'ai qu'une dizaine de lignes pour l'intégrer et je pourrais facilement changer de librairie si l'envie m'en prenait.

De morceau d'HTML à un site🔗

C'est là qu'arrive la difficulté à mon sens : produire du HTML à partir des fichiers Markdown ne suffit pas. Il faut lier tout ça. Créer une page d'accueil, mettre un peu de cadre, avoir la structure d'une vraie page HTML.

C'est là que j'ai dû créer mon propre moteur de templating.

Ne vous attendez pas à un truc de fou. J'ai fait un système simple de .replaceAll() mais qui marche parfaitement.

Schématiquement, tout se passe dans une fonction applyArticleTemplate() qui va prendre un template (une chaîne de caractère contenant du HTML mais avec quelques marqueurs spécifiques) et un objet article qui contient le contenu de l'article et toutes les métadonnées dont j'ai besoin.

Je vais constituer un dictionnaire de clé à remplacer puis je vais aller remplacer chaque occurrence de la clé par la valeur que j'ai définie. Rien de sorcier. J'ai quand même dû créer une syntaxe de condition qui n'est pas jolie mais qui fait le travail sans souci.

Tout tient dans ce bloc de code (j'ai juste coupé un peu le dictionnaire, car c'est un peu verbeux) :

export function applyArticleTemplate(
  template: string,
  article: Article
): string {
  let html = template;
  const dictionary = {
    "article.url": `/${article.path}`,
    "article.title": article.title,
    "article.contentHtml": article.contentHtml,
    // ...
    "article.readingTimeInMinutes": String(article.readingTimeInMinutes),
    ...Object.fromEntries(
      article.tags.map((tag, index) => [`article.tags[${index}].label`, tag])
    ),
  };
  const match = html.matchAll(/#if\((.*)\)\{/gm);
  for (const [ifPattern, ifTarget] of match) {
    if (ifTarget in dictionary) {
      html = html.replaceAll(ifPattern, "");
    } else {
      const start = html.indexOf(ifPattern);
      const end = html.indexOf("}#fi", start);
      html = html.replace(html.slice(start, end + 4), "");
    }
  }
  html = html.replaceAll("}#fi", "");
  for (const [key, value] of Object.entries(dictionary)) {
    html = html.replaceAll(`{{${key}}}`, value);
  }
  return html;
}
TypeScript

Côté template, on va retrouver quelque chose de très simple : on voit des "moustaches" (comme en Angular (et d'autres) parce que ça me parait bien comme syntaxe, et y'a rien qui ressemble en HTML de base) contenant la clé qui doit être utilisé.

<!DOCTYPE html>
<html lang="fr">
  <head>
    ...
    <title>{{article.title}} - Anthony PENA</title>
  </head>
  <body>
    ...
    <main>
      <article class="post">
        <header>
          <section class="title">
            <h1>{{article.title}}</h1>
          </section>
          <section class="metadata">
            ...
            <div class="tags">
              #if(article.tags[0].label){
              <span class="tag">#Web</span>}#fi
              #if(article.tags[1].label){
              <span class="tag">#Frontend</span>}#fi
              #if(article.tags[2].label){
              <span class="tag">#{{article.tags[2].label}}</span>}#fi
            </div>
          </section>
          ...
        <main>{{article.contentHtml}}</main>
      </article>
    </main>
  </body>
</html>
HTML

Là où la plupart des gens auraient sorti une librairie toute prête pour faire ça (avec l'obligation de maintenir la dépendance, se surcharger de fonctionnalités inutiles, devoir apprendre une syntaxe sophistiquée pour répondre à énormément de cas), coder mon propre système n'a pas du tout été difficile, me permet pas mal d'évolutions et à moindre effort !

Static html first🔗

Si j'ai un temps pensé à utiliser Astro, au moment de la remise à plat j'ai défini un point très simple : je n'ai pas besoin de faire du SSR (Server Side Rendering = rendu à chaque requête) mais j'ai besoin de SSG (Static Site Generation = génération statique à un instant T).

L'ensemble des fichiers Markdown de mon blog est converti en un site HTML static, en environ 2 secondes, à chaque commit et chaque matin vers 6h30 (pour gérer les articles qui serait commit en avance), formant un site complet qui ne demande aucun calcul côté serveur (enfin presque, mais j'y reviendrai plus tard). L'article que vous lisez en ce moment n'a donc demandé que peu d'énergie pour vous parvenir, et ne bougera pas avant demain matin au plus tôt sauf si je me rends compte d'une erreur quelque part et que je la corrige.

C'est un choix fort de ma part. Je voulais revenir à la base du web : le HTML et le CSS.

J'écris un article par semaine, je trouvais absurde de générer le contenu à la volée, je trouvais absurde d'avoir un site dynamique qui a forcément un coût écologique non nul.

Uniquement du HTML mais déjà 2 thèmes et un support mobile !🔗

Si vous coupez le chargement des feuilles de style, vous aurez déjà un thème light / dark sur mon blog.

Laisser aux lecteurs le choix de lire sur fond blanc ou noir était très important pour moi. Mais je ne voulais pas avoir de clignotement blanc/noir sur la page non plus. C'était aussi important pour moi que mon blog soit lisible sur mobile comme sur desktop.

J'ai donc utilisé au maximum les fonctionnalités des navigateurs avec deux balises magiques à mettre dans les balises <head> :

<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
HTML

La première va s'assurer qu'on gère correctement l'échelle d'affichage pour éviter des textes trop petits / trop grands, sans bloquer le zoom ! (je trouve insupportable les sites qui bloquent le zoom et du coup qui ne me permettent pas de regarder en gros un détail qui m'intéresse…)

La seconde va directement indiquer au navigateur que mon contenu peut être lu en thème light ou dark, et donc qu'il peut appliquer les styles qui vont bien : c'est géré automatiquement. La plupart des navigateurs vont reprendre le paramètre de votre OS pour le thème à afficher par défaut !

Conclusion🔗

Comme c'était logique pour moi de faire mon blog en suivant un process simple : faire du HTML, puis du CSS quand je bloque, puis du JavaScript quand je suis bloqué (et sans qu'il soit nécessaire) ; ce premier article s'arrête au HTML.

À mon sens, il n'y a rien de fou sur mon blog en termes de HTML, mais je trouvais ça important de partir de ça pour montrer qu'au fond c'est facile de produire du contenu HTML et qu'il n'y a besoin de beaucoup de HTML pour que tout fonctionne.

J'espère vous retrouver pour la seconde partie de cette série ! 🤓

Crédit photo : Générée via Mistral AI avec le prompt suivant :

Une scène chaleureuse et immersive, inspirée par l’esthétique poétique de Studio Ghibli, représentant un atelier d’écriture en bois clair et vieilli, baigné d’une lumière dorée et tamisée, comme un après-midi d’automne. Au centre, un panda roux (Firefox) anthropomorphe, vêtu d’une veste ample et confortable, est assis à un bureau en bois, en train d’écrire à la plume sur des feuilles de papier Markdown. Autour de lui, des feuilles de papier se transforment magiquement en parchemins HTML, avec des balises HTML qui apparaissent comme par enchantement en encre dorée.

Le bureau est en bois clair, avec des piles de carnets et de magazines techniques futuristes empilés de manière désordonnée mais harmonieuse. Certains carnets sont ouverts, révélant des extraits de code HTML et des schémas techniques dessinés à la main. Un écran transparent et flottant (style holographique, avec un cadre en bois sculpté) affiche des lignes de code HTML en temps réel, intégrées naturellement à la scène.

En arrière-plan, une grande fenêtre donne sur un paysage automnal : des arbres aux feuilles rouges, oranges et jaunes, et des lanternes suspendues qui projettent une lumière douce. Des feuilles d’érable flottent lentement dans l’air, ajoutant une touche magique à la scène.

À droite, en bas, le panda roux est légèrement tourné en 3/4 dos, lové sur un coussin, entouré d’un halo lumineux subtil. Il ne dépasse pas le tiers de la hauteur de l’image et observe la transformation des feuilles Markdown en HTML avec curiosité et satisfaction.

L’ambiance est cosy, onirique et high-tech : les couleurs sont douces (beiges, bleus pâles, oranges chauds), avec des jeux de lumière tamisée dorée qui créent des ombres chaleureuses. Les détails sont soignés, avec des textures visibles (grain du bois, tissu du coussin, pelage du panda roux), et une tasse de thé fumant posée sur le bureau. Le tout respire la sérénité et l’inspiration, comme un moment suspendu entre la magie et la technologie.""Une scène chaleureuse et immersive, inspirée par l’esthétique poétique de Studio Ghibli, représentant un atelier d’écriture en bois clair et vieilli, baigné d’une lumière dorée et tamisée, comme un après-midi d’automne. Au centre, un panda roux (Firefox) anthropomorphe, vêtu d’une veste ample et confortable, est assis à un bureau en bois, en train d’écrire à la plume sur des feuilles de papier Markdown. Autour de lui, des feuilles de papier se transforment magiquement en parchemins HTML, avec des balises HTML qui apparaissent comme par enchantement en encre dorée.

Le bureau est en bois clair, avec des piles de carnets et de magazines techniques futuristes empilés de manière désordonnée mais harmonieuse. Certains carnets sont ouverts, révélant des extraits de code HTML et des schémas techniques dessinés à la main. Un écran transparent et flottant (style holographique, avec un cadre en bois sculpté) affiche des lignes de code HTML en temps réel, intégrées naturellement à la scène.

En arrière-plan, une grande fenêtre donne sur un paysage automnal : des arbres aux feuilles rouges, oranges et jaunes, et des lanternes suspendues qui projettent une lumière douce. Des feuilles d’érable flottent lentement dans l’air, ajoutant une touche magique à la scène.

À droite, en bas, le panda roux est légèrement tourné en 3/4 dos, lové sur un coussin, entouré d’un halo lumineux subtil. Il ne dépasse pas le tiers de la hauteur de l’image et observe la transformation des feuilles Markdown en HTML avec curiosité et satisfaction.

L’ambiance est cosy, onirique et high-tech : les couleurs sont douces (beiges, bleus pâles, oranges chauds), avec des jeux de lumière tamisée dorée qui créent des ombres chaleureuses. Les détails sont soignés, avec des textures visibles (grain du bois, tissu du coussin, pelage du panda roux), et une tasse de thé fumant posée sur le bureau. Le tout respire la sérénité et l’inspiration, comme un moment suspendu entre la magie et la technologie.