3 points par GN⁺ 2023-09-11 | 1 commentaires | Partager sur WhatsApp
  • Les sites statiques sont faciles à héberger, rapides et demandent peu de maintenance ; pour un site simple, quelques lignes de Makefile peuvent suffire sans avoir à apprendre un générateur dédié
  • La structure de base consiste à conserver l’arborescence des fichiers de source, à préfixer les fichiers HTML avec header.html, et à copier tels quels les autres fichiers dans build
  • make build associe les fichiers d’entrée et de sortie avec find source -type f et patsubst, puis applique des règles make différentes pour les fichiers .html et les autres
  • La mise en évidence de la page courante, la conversion Markdown, le serveur local, la reconstruction automatique à chaque changement et le déploiement sur GitHub Pages peuvent être ajoutés plus tard comme cibles supplémentaires selon les besoins
  • Avec peu de dépendances et des points de modification très clairs, pour les sites aux besoins modestes, un flux de build fait maison peut être plus rapide qu’un générateur générique

Un site statique de base avec un simple Makefile

  • Un générateur de site statique produit un résultat facile à héberger, rapide et avec une charge de maintenance très faible
  • Pour un site simple, écrire son propre script peut être plus rapide et plus satisfaisant que d’apprendre et d’adapter un générateur existant
  • Pour un site ordinaire sans horodatage mis à jour automatiquement ni flux RSS, une configuration encore plus simple qu’un script de blog suffit
  • Exigences de base

    • Tous les fichiers d’entrée sont placés dans le répertoire source, et la même arborescence est conservée en sortie
    • header.html est ajouté au début de tous les fichiers HTML
    • Les fichiers non HTML sont copiés tels quels dans le répertoire build
  • Règles du Makefile

    • La cible build dépend de tous les fichiers sous source, chacun étant associé à un fichier de sortie sous build
    • Elle n’effectue aucune opération par elle-même, mais sert de point d’entrée pour que la règle adaptée soit appliquée à chaque fichier
    • La règle build/%.html dépend de source/%.html, de header.html et du Makefile
    • mkdir -p $(dir $@) crée le répertoire de sortie, puis cat header.html $< > $@ fusionne l’en-tête et le HTML d’entrée dans le fichier généré
    • La règle build/% copie les fichiers non HTML tels quels avec cp $< $@
    • Avec ce seul header.html et ces règles, exécuter make build suffit à produire un répertoire build qu’on peut ouvrir en local ou mettre sur un serveur web

Exemples d’extension et cibles auxiliaires

  • Afficher la page courante

    • Mettre en évidence la page actuelle dans la navigation permet au visiteur de voir immédiatement où il se trouve sur le site
    • Dans l’exemple, on recherche le lien de navigation et on lui ajoute la classe current
    • sed -E 's|(href="$(subst source,,$<))|class="current" \1|' header.html | cat - $< > $@
    • La méthode de substitution exacte doit être adaptée à la structure du balisage utilisée
  • Générer du HTML à partir de Markdown

    • Si l’on ne veut pas écrire directement du HTML, ou si le contenu existant est en Markdown, on peut chaîner un convertisseur Markdown-vers-HTML dans le pipeline
    • L’exemple utilise smu
    • smu $< | cat header.html - > $@
    • La règle de base continue de supposer que build/foo.html est généré à partir de source/foo.html
    • Pour les fichiers Markdown, il faut soit conserver le suffixe .html, soit modifier la règle pour prendre des fichiers .md en entrée
  • Prévisualisation locale

    • Certains sites sont difficiles à prévisualiser correctement en ouvrant directement les fichiers locaux dans le navigateur ; une raison fréquente est l’usage de liens absolus plutôt que relatifs
    • Python est déjà installé sur beaucoup de systèmes et inclut un serveur web adapté à cet usage
    • Cible serve : python -m http.server -d build
  • Reconstruction automatique lors des changements

    • Pendant qu’on travaille sur le site, relancer manuellement la build à chaque fois est fastidieux
    • Avec entr, on peut exécuter automatiquement make build à chaque modification de source, header.html ou du Makefile
    • Cible watch : find source header.html Makefile | entr make build
    • Si l’on veut éviter cette dépendance, on peut aussi utiliser inotifywait
  • Déploiement sur GitHub Pages

    • Si le dépôt est hébergé sur GitHub, héberger le HTML généré sur GitHub Pages est un choix naturel
    • Ajuster les commandes de déploiement pour ne pas avoir à se soucier des détails de git est un peu délicat, mais on peut s’en sortir avec git worktree
    • La méthode s’appuie sur l’article de Sangsoo Nam
    • Ajouter la branche gh-pages comme worktree public_html
    • Copier build/* dans public_html
    • Exécuter git add --all, git commit -m "Deploy to github pages", puis git push origin gh-pages
    • Enfin, supprimer le worktree avec git worktree remove public_html
  • Exemple réel

    • Une page réalisée avec cette approche est visible dans karlb/astridbartel.de
    • Un générateur de site statique complet peut commencer avec six lignes de Makefile et évoluer rapidement selon les besoins, sans dépendances exotiques ni lourde charge de maintenance

1 commentaires

 
GN⁺ 2023-09-11
Avis sur Hacker News
  • Mon site personnel (https://pablo.rauzy.name/) était autrefois généré avec un simple Makefile
    J’y ai ensuite ajouté des fonctionnalités comme des actualités et un flux RSS, des listes automatiques de publications de recherche et de supports de cours, ainsi qu’une liste de livres filtrable par tags. C’est toujours un Makefile, mais le Makefile lui-même est même devenu un peu plus simple ; à la place, il appelle des scripts Bash qui utilisent les excellents utilitaires xml2 et 2xml pour manipuler le HTML ligne par ligne. Ils s’appuient surtout sur des utilitaires de base comme grep et sed
    J’y ai aussi ajouté quelques hooks git qui appellent automatiquement make quand c’est nécessaire ; en particulier, sur le serveur distant où le site est hébergé, pousser vers le dépôt reconstruit la version publique
    Cela fonctionne très bien depuis des années, et l’historique git remonte jusqu’en 2009. En regardant les premiers commits, on voit beccad7 (FIRST_VERSION) Initial commit, d1cc6d7 adding link to Google Reader shared items, 6ccfd0c fix typo, d337959 adding link to Identi.ca account, et cela fait vraiment 15 ans
  • Le problème avec cette approche, c’est que lorsqu’on supprime un fichier dans source/, il n’est pas supprimé dans build/
    Dans mes projets, reconstruire tout le site est suffisamment rapide, donc j’ai choisi de supprimer tout le dossier build avant la reconstruction
    https://github.com/jez/jez.github.io/blob/source/Makefile#L1...
    Cela neutralise en grande partie l’un des grands intérêts d’un système de build, à savoir les builds incrémentaux, mais si l’on sait quelle page on veut régénérer, on peut tout de même lancer directement make sur ce fichier
    J’aimerais savoir s’il existe un contournement courant pour ce type de pattern dans les Makefile
    • Je ne sais pas si c’est un pattern courant, mais ma solution consistait à exécuter à chaque fois une commande qui supprime les fichiers « inattendus »
      J’énumère les fichiers avec la fonction shell de GNU Make, puis je filtre les sorties « attendues » avec la fonction filter-out. C’est un hack assez laid, mais le fait d’exécuter la commande dans le cadre de l’expansion d’une variable via la fonction shell garantit qu’elle est exécutée à chaque fois
      Lien vers le Makefile : https://github.com/jaredkrinke/make-blog/blob/main/Makefile
    • La suppression et le renommage de fichiers sont des problèmes courants dans de nombreux systèmes de gestion de versions/build
      Outre l’option nucléaire comme make clean, on peut définir des cibles Make spécifiques pour la suppression et le renommage. Par exemple, faire en sorte que make rm sourcefile ou make mv sourcefile newsourcefile gère à la fois la suppression ou le renommage de la source et de la cible générée
      En pratique, même pour des blogs ou projets en ligne assez importants, le cycle make clean / make all est généralement suffisamment rapide, et il est souvent nécessaire lorsqu’on modifie des modèles ou des éléments de design du site. Si le projet est assez gros pour que le temps de reconstruction devienne préoccupant, il peut être plus pertinent d’utiliser un vrai CMS qui stocke les sources dans une base de données et génère dynamiquement le contenu lors des accès client
    • make clean ne suffit pas ?
    • Quelque chose comme ceci devrait fonctionner
      rm/%.html:
      @rm -f source/%.html build/%.html
      Et on l’exécute avec $ make rm/page.html
    • Le système de build généraliste Shake dispose d’une fonctionnalité prune précisément pour cet usage
      http://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
      Mais je pense que la meilleure solution qui fonctionne aussi avec make est d’avoir une cible make dist qui produit l’archive finale .tar.gz des artefacts. Si les règles sont bien écrites, les vieux fichiers ne s’y retrouveront pas. Cela peut être lent sur de gros projets, mais en développement on n’en a de toute façon pas besoin, seulement au moment des releases. Les builds incrémentaux restent possibles, et seule la dernière archive .tar.gz doit être reconstruite depuis zéro
  • J’ai été directement inspiré par le travail mentionné dans cet article autour du script shell blog.sh de Karl
    Je l’ai repris et adapté pour créer mon générateur de site statique minimaliste, barf. Il n’existerait pas si Karl n’avait pas publié son excellent travail
    [0] : https://github.com/karlb/karl.berlin/blob/master/blog.sh
    [1] : https://barf.bt.ht
    • On a des goûts similaires. Le mien s’appelle shite, et je l’utilise pour construire mon site. Le nom donne une indication sur la qualité du logiciel :)
      Ce que je préfère, c’est que jusqu’ici je n’ai jamais eu besoin de mettre quoi que ce soit à niveau, et je pense que je n’en aurai jamais besoin. Le deuxième meilleur point, c’est le hot reload sans JavaScript
      [1] https://github.com/adityaathalye/shite
      [2] https://evalapply.org
  • En y ajoutant un peu de m4, on peut conserver la même approche squelettique tout en gagnant un peu de flexibilité

J’ai maintenu il y a une vingtaine d’années un petit site web créé de cette façon. Mais, en dehors des sites web personnels, je ne sais pas si ce modèle convient encore aujourd’hui. Cette approche impose fondamentalement des rôles façon Web 1.0. Tous les utilisateurs contributeurs doivent soit être à l’aise avec HTML, soit quelqu’un doit se charger de la corvée de « webmaster »
[1] https://en.wikipedia.org/wiki/M4_(computer_language)

  • « Un peu de m4 », ça n’existe pas
    On démarre un projet propre en se jurant que, cette fois, on ne touchera pas à m4. Puis, pour réduire le code répétitif, on finit par ajouter un petit appel à m4
    Un an plus tard, on se retrouve à fouiller cinq couches d’expansion de macros pour comprendre pourquoi le mot « cat » disparaît silencieusement du site, avant de découvrir qu’un développeur junior a implémenté lui-même une boucle for au lieu de la copier depuis le manuel, et a raté les guillemets
    Une fois le problème immédiat réglé, on décide que déboguer le DSL est trop difficile, alors on récupère le fichier de macros M4 qu’on recopie de projet en projet. Ensuite, on passe une journée à remplacer toutes les utilisations de define par une macro génératrice de macros qui ajoute des commentaires à la sortie pour que le script de génération de traces de pile fonctionne
    Au prochain projet, on posera une règle absolue. Pas de m4 ! Bon, sauf peut-être à cet endroit-là
  • Plutôt que de s’appuyer sur de la substitution de texte générique comme m4 ou perl, je conseillerais d’utiliser SGML, qui est la base et le sur-ensemble commun de HTML et XML
    SGML fournit par défaut des macros textuelles faciles à vérifier typologiquement, c’est-à-dire l’expansion d’entités, et permet aussi l’expansion de macros paramétrées conscientes des types. Ici, le type désigne le type général de contenu d’un élément de balisage, c’est-à-dire les éléments enfants autorisés et leur ordre, tout en tenant compte de l’expansion ou de l’échappement selon des contextes comme les attributs, CDATA ou RCDATA
    Pour développer et échapper correctement des commentaires utilisateurs potentiellement malveillants, tout en appliquant par exemple des règles utilisateur qui autorisent le balisage de niveau inline mais interdisent l’élément script, seul SGML peut vraiment le faire correctement. On peut aussi étendre Markdown ou une syntaxe wiki en HTML, importer du contenu HTML externe ou syndiqué, générer du RSS et des outlines pour la navigation, etc. C’est bien adapté à une préparation de site statique assez complexe en ligne de commande
    [1]: https://sgmljs.net/docs/producing-html-tutorial/producing-ht...
    [2]: https://sgmljs.net/docs/sgmlproc-manual.html
  • Au lieu de m4 ou de chercher-remplacer avec sed, vous pourriez essayer envsubst
    C’est un programme qui remplace les références de variables de style Bash, par exemple $TITLE, par les valeurs des variables d’environnement
    export CURRENT="..."
    cat page.html | envsubt
  • « Un peu de m4 », pitié, surtout pas
    m4 est déjà mauvais même comme langage de programmation ésotérique
  • J’ai essayé une fois, plus jamais
    Le simple fait que ça ait tourné dans sendmail ne suffit pas à justifier quoi que ce soit
  • J’aime le fait que presque tous les blogs de développeurs que je croise sur HN aient un flux RSS
    Chaque fois que je lis ici un article intéressant, je m’abonne au flux. Que ce soit un site Wordpress, Bear Blog, Micro.blog, un blog Havenweb ou le flux d’un site fait maison, je l’ajoute au module Really Social Sites de Hey Homepage
    À terme, j’aimerais rendre cette liste de blogs publique, comme Kagi le fait avec son initiative Small Web. Mais si l’on veut ajouter de la qualité, la curation est essentielle, et quand on commence à réfléchir à la curation, lancer une sorte de magazine en ligne semble naturel
    • J’essaie de comprendre si c’est moi qui suis bizarre, en tant que développeur, de ne pas vouloir avoir mon propre blog
      Où les gens trouvent-ils cette « légitimité », au bon sens du terme, pour se dire qu’ils peuvent partager avec d’autres ? Je me demande pourquoi ils supposent que d’autres personnes s’intéresseront à ce sur quoi ils travaillent. Parfois, ça ressemble à une compétition. Du genre : « Pour obtenir des likes ou de la visibilité sur un blog, il faut fabriquer le truc le plus cool possible »
      La collaboration est évidemment une bonne chose, et elle n’est possible que si tout est public. Mais je ne vois pas bien la frontière entre « je fais ça parce que ça a l’air cool » et « je fais l’effort de le partager avec d’autres pour obtenir une réaction »
    • Il y a aussi https://prose.sh, qui ressemble à Bear Blog
  • Un ami m’a un jour expliqué sa façon de générer des articles scientifiques avec make
    Il disait qu’en ne modifiant qu’un fichier de test, une seule commande pouvait régénérer tout l’article, exécution des tests et génération des graphiques incluses
  • L’idée est élégante, mais si vous poussez déjà vers GitHub, vous pouvez vous contenter d’y mettre les sources et GitHub peut publier le Markdown sous forme de pages hébergées
    https://pages.github.com/
    • Dans ce cas, vous dépendez de GitHub pour davantage que du simple hébergement. Il vaut mieux faire en sorte dès le départ que la génération du site puisse être exécutée localement
  • J’aime bien le code
    Le mien est un peu trop conçu parce que je voulais du hot reload sans JavaScript, et c’était un agréable rasage de yak
    L’idée de base est la même. J’utilise des heredocs pour les templates, et un compilateur qui transforme du texte brut en HTML ; dans mon cas, c’est pandoc. J’utilise un CSV intermédiaire pour générer les index, et il y a aussi une technique sed utile pour extraire le front matter. Classique et efficace
    [1] https://github.com/karlb/karl.berlin/blob/master/blog.sh
    [2] https://github.com/adityaathalye/shite
    [3] Ma façon de faire : https://github.com/adityaathalye/shite/blob/master/bin/templ...
    • Son approche GEMINI m’a pas mal fait rire. Il enlève l’essentiel du formatage à coups de regex

Mais il y a tout de même quelques limites. J’organise mes articles par espace de noms et j’inclus la date dans l’URL, et make ne gère pas vraiment ça directement.

  • Quand on voit ce genre de scripts, on comprend exactement pourquoi on utilise des outils comme esbuild ou vite au lieu de tout faire soi-même.
  • L’avantage de make, c’est que, sur de gros programmes construits avec des compilateurs lents, il rend les recompilations incrémentales beaucoup plus rapides lorsqu’il y a de petites modifications.
    Une reconstruction complète qui prendrait 40 minutes peut se terminer en environ 3 secondes.
    Si un site statique se génère depuis zéro en moins d’une seconde en se contentant de cat-er un en-tête commun et quelques centaines de fichiers HTML, il n’y a aucun intérêt à utiliser make plutôt qu’un script. On ne fait qu’ajouter le risque d’effectuer un build incomplet à cause de bugs de dépendances.
    • Si les dépendances de fichiers ne sont pas réellement importantes, il suffit de marquer la cible de build comme .phony.
      On peut tout de même conserver une distinction comme make build et make push.
  • Waouh, c’est presque exactement ce que je voulais faire pour mon site.
    Sur d’autres petits projets, j’ai utilisé un tout petit script shell comme bundler improvisé. Il insère le CSS et le JS dans le HTML, et l’objectif était aussi de pouvoir servir localement les fichiers non buildés.