Générateur de site statique avec « Make » (2022)
(karl.berlin)- 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 avecheader.html, et à copier tels quels les autres fichiers dansbuild make buildassocie les fichiers d’entrée et de sortie avecfind source -type fetpatsubst, puis applique des règles make différentes pour les fichiers.htmlet 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.htmlest ajouté au début de tous les fichiers HTML- Les fichiers non HTML sont copiés tels quels dans le répertoire
build
- Tous les fichiers d’entrée sont placés dans le répertoire
-
Règles du Makefile
- La cible
builddépend de tous les fichiers soussource, chacun étant associé à un fichier de sortie sousbuild - 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/%.htmldépend desource/%.html, deheader.htmlet duMakefile mkdir -p $(dir $@)crée le répertoire de sortie, puiscat 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 aveccp $< $@ - Avec ce seul
header.htmlet ces règles, exécutermake buildsuffit à produire un répertoirebuildqu’on peut ouvrir en local ou mettre sur un serveur web
- La cible
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.htmlest généré à partir desource/foo.html - Pour les fichiers Markdown, il faut soit conserver le suffixe
.html, soit modifier la règle pour prendre des fichiers.mden 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 automatiquementmake buildà chaque modification desource,header.htmlou duMakefile - 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-pagescomme worktreepublic_html - Copier
build/*danspublic_html - Exécuter
git add --all,git commit -m "Deploy to github pages", puisgit 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
Avis sur Hacker News
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
grepetsedJ’y ai aussi ajouté quelques hooks git qui appellent automatiquement
makequand 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 publiqueCela 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 ansxml2et2xmlsemblent être un projet un peu abandonnéhttps://github.com/cryptorick/xml2
https://manpages.debian.org/unstable/xml2/2xml.1.en.html
source/, il n’est pas supprimé dansbuild/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
makesur ce fichierJ’aimerais savoir s’il existe un contournement courant pour ce type de pattern dans les Makefile
J’énumère les fichiers avec la fonction
shellde GNU Make, puis je filtre les sorties « attendues » avec la fonctionfilter-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 fonctionshellgarantit qu’elle est exécutée à chaque foisLien vers le Makefile : https://github.com/jaredkrinke/make-blog/blob/main/Makefile
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 quemake rm sourcefileoumake mv sourcefile newsourcefilegère à la fois la suppression ou le renommage de la source et de la cible généréeEn pratique, même pour des blogs ou projets en ligne assez importants, le cycle
make clean/make allest 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 clientmake cleanne suffit pas ?rm/%.html:@rm -f source/%.html build/%.htmlEt on l’exécute avec
$ make rm/page.htmlpruneprécisément pour cet usagehttp://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
Mais je pense que la meilleure solution qui fonctionne aussi avec
makeest d’avoir une ciblemake distqui produit l’archive finale.tar.gzdes 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.gzdoit être reconstruite depuis zéroblog.shde KarlJe 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
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
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)
m4», ça n’existe pasOn 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 àm4Un 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
definepar 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 fonctionneAu prochain projet, on posera une règle absolue. Pas de
m4! Bon, sauf peut-être à cet endroit-làm4ouperl, je conseillerais d’utiliser SGML, qui est la base et le sur-ensemble commun de HTML et XMLSGML 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
m4ou de chercher-remplacer avecsed, vous pourriez essayerenvsubstC’est un programme qui remplace les références de variables de style Bash, par exemple
$TITLE, par les valeurs des variables d’environnementexport CURRENT="..."cat page.html | envsubtm4», pitié, surtout pasm4est déjà mauvais même comme langage de programmation ésotériqueLe simple fait que ça ait tourné dans sendmail ne suffit pas à justifier quoi que ce soit
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 Sitesde 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
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 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
https://pages.github.com/
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 techniquesedutile 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...
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
makene gère pas vraiment ça directement.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 à utilisermakeplutôt qu’un script. On ne fait qu’ajouter le risque d’effectuer un build incomplet à cause de bugs de dépendances..phony.On peut tout de même conserver une distinction comme
make buildetmake push.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.