Statischer Site-Generator mit „Make“ (2022)
(karl.berlin)- Statische Websites sind einfach zu hosten, schnell und wartungsarm; für einfache Sites reichen oft ein paar Zeilen Makefile, ohne einen separaten Builder lernen zu müssen
- Die Kernstruktur besteht darin, die Dateianordnung in
sourcebeizubehalten, HTML-Dateienheader.htmlvoranzustellen und alle übrigen Dateien unverändert nachbuildzu kopieren make buildordnet mitfind source -type fundpatsubstEingabe- und Ausgabedateien einander zu und wendet unterschiedliche Make-Regeln auf.html- und andere Dateien an- Hervorhebung der aktuellen Seite, Markdown-Konvertierung, lokaler Server, Rebuilds bei Änderungen und Deployment auf GitHub Pages können bei Bedarf als zusätzliche Targets ergänzt werden
- Wegen weniger Abhängigkeiten und klarer Änderungsstellen kann bei Sites mit kleinen Anforderungen ein selbst gebauter Build-Flow schneller sein als ein universeller Generator
Eine einfache statische Website nur mit einem Makefile
- Statische Site-Generatoren liefern Ergebnisse, die einfach zu hosten und schnell sind und nur sehr wenig Wartungsaufwand verursachen
- Für einfache Sites kann es schneller und befriedigender sein, selbst ein Skript zu schreiben, als einen bestehenden Site-Builder zu lernen und anzupassen
- Für eine normale Website ohne automatisch aktualisierte Zeitstempel oder RSS-Feed reicht eine noch einfachere Struktur als bei Blog-Skripten
-
Grundanforderungen
- Alle Eingabedateien liegen im Verzeichnis
source, und in der Ausgabe wird dieselbe Verzeichnisstruktur beibehalten - Vor alle HTML-Dateien wird
header.htmlgesetzt - Dateien, die kein HTML sind, werden unverändert in das Verzeichnis
buildkopiert
- Alle Eingabedateien liegen im Verzeichnis
-
Makefile-Regeln
- Das Target
buildhat Abhängigkeiten zu allen Dateien untersource, jeweils zugeordnet zu Ausgabedateien unterbuild - Es führt selbst keine Arbeit aus, dient aber als Einstiegspunkt, damit die passende Regel für jede Datei angewendet wird
- Die Regel
build/%.htmlhängt vonsource/%.html,header.htmlundMakefileab - Mit
mkdir -p $(dir $@)wird das Ausgabeverzeichnis erstellt, und mitcat header.html $< > $@werden Header und Eingabe-HTML zur Ausgabe zusammengeführt - Die Regel
build/%kopiert Dateien, die kein HTML sind, mitcp $< $@unverändert - Mit diesem
header.htmlund diesen Regeln erzeugtmake buildein Verzeichnisbuild, das lokal geöffnet oder auf einen Webserver hochgeladen werden kann
- Das Target
Erweiterungsbeispiele und Hilfs-Targets
-
Aktuelle Seite markieren
- Wenn die aktuelle Seite in der Navigation hervorgehoben wird, sehen Besucher auf einen Blick, wo sie sich auf der Website befinden
- Im Beispiel wird der Navigationslink gefunden und die Klasse
currenthinzugefügt sed -E 's|(href="$(subst source,,$<))|class="current" \1|' header.html | cat - $< > $@- Die konkrete Ersetzungslogik sollte an die verwendete Markup-Struktur angepasst werden
-
HTML aus Markdown erzeugen
- Wer HTML nicht direkt schreiben möchte oder bestehende Inhalte im Markdown-Format hat, kann einen Markdown-zu-HTML-Konverter per Pipe einbinden
- Das Beispiel verwendet smu
smu $< | cat header.html - > $@- Die Grundregel geht weiterhin davon aus, dass
build/foo.htmlaussource/foo.htmlerzeugt wird - Entweder muss auch für Markdown-Dateien das Suffix
.htmlbeibehalten werden, oder die Regeln müssen so angepasst werden, dass sie.md-Dateien als Eingabe finden
-
Lokale Vorschau
- Manche Sites lassen sich nur schwer korrekt voranzeigen, wenn man lokale Dateien direkt im Browser öffnet; ein häufiger Grund ist die Verwendung von absoluten Links statt relativer Links
- Python ist auf vielen Systemen bereits installiert und enthält einen für diesen Zweck passenden Webserver
- Target
serve:python -m http.server -d build
-
Automatischer Rebuild bei Änderungen
- Beim Arbeiten an der Site ist es lästig, jedes Mal manuell neu zu bauen
- Mit
entrlässt sich bei Änderungen ansource,header.htmloderMakefileautomatischmake buildausführen - Target
watch:find source header.html Makefile | entr make build - Wer Abhängigkeiten vermeiden möchte, kann auch
inotifywaitverwenden
-
Deployment auf GitHub Pages
- Wenn das Repository auf GitHub liegt, ist es naheliegend, das erzeugte HTML über GitHub Pages zu hosten
- Die Deployment-Befehle so abzustimmen, dass man sich nicht um Git-Details kümmern muss, ist etwas knifflig, lässt sich aber mit
git worktreelösen - Der Ansatz basiert auf Sangsoo Nams Artikel
- Der Branch
gh-pageswird als Worktreepublic_htmlhinzugefügt build/*wird nachpublic_htmlkopiertgit add --all,git commit -m "Deploy to github pages"undgit push origin gh-pageswerden ausgeführt- Am Ende wird der Worktree mit
git worktree remove public_htmlentfernt
-
Praktisches Beispiel
- Eine mit diesem Ansatz erstellte Seite ist unter karlb/astridbartel.de zu sehen
- Ein vollständiger statischer Site-Generator kann mit sechs Zeilen im Makefile beginnen und lässt sich ohne ungewöhnliche Abhängigkeiten oder Wartungslast schnell an die eigenen Bedürfnisse anpassen
1 Kommentare
Hacker-News-Kommentare
Später kamen Funktionen hinzu wie News und RSS-Feeds, automatische Listen von Forschungsarbeiten und Vorlesungsmaterialien sowie eine nach Tags filterbare Bücherliste. Es ist immer noch ein Makefile, aber das Makefile selbst ist eher etwas einfacher geworden; stattdessen ruft es Bash-Skripte auf, die die hervorragenden Utilities xml2 und 2xml verwenden, um HTML zeilenweise zu verarbeiten. Hauptsächlich kommen Kern-Utilities wie
grepundsedzum Einsatz.Dazu habe ich ein paar git hooks ergänzt, die bei Bedarf automatisch
makeaufrufen; insbesondere auf dem Remote-Server, auf dem die Website gehostet wird, wird die öffentliche Version neu gebaut, wenn man ins Repository pusht.Das läuft seit Jahren sehr gut, und die git-Historie reicht bis 2009 zurück. Wenn ich mir die ersten Commits ansehe, waren das
beccad7 (FIRST_VERSION) Initial commit,d1cc6d7 adding link to Google Reader shared items,6ccfd0c fix typo,d337959 adding link to Identi.ca account, und es sind wirklich 15 Jahre vergangen.xml2und2xmlwirken ein wenig wie ein vernachlässigtes Projekt.https://github.com/cryptorick/xml2
https://manpages.debian.org/unstable/xml2/2xml.1.en.html
source/löscht, unterbuild/nicht gelöscht werden.Bei meinen Projekten ist ein kompletter Neubau der Website schnell genug, deshalb habe ich mich dafür entschieden, vor dem Rebuild den gesamten build-Ordner zu löschen.
https://github.com/jez/jez.github.io/blob/source/Makefile#L1...
Damit wird einer der großen Gründe für ein Build-System, nämlich inkrementelle Builds, zu einem guten Teil ausgehebelt; wenn man aber weiß, welche Seite man neu erzeugen möchte, kann man weiterhin nur diese Datei direkt mit
makebauen.Falls es für dieses Muster in Makefiles einen gängigen Workaround gibt, würde ich ihn gern kennen.
Mit der
shell-Funktion von GNU Make liste ich Dateien auf und filtere mit der Funktionfilter-outdie „erwarteten“ Ausgaben heraus. Das ist ein hässlicher Hack, aber indem der Befehl als Teil einer Variablenerweiterung über dieshell-Funktion ausgeführt wird, ist sichergestellt, dass er jedes Mal läuft.Makefile-Link: https://github.com/jaredkrinke/make-blog/blob/main/Makefile
Neben der Nuklearoption
make cleankann man auch spezielle Make-Targets für Löschen und Umbenennen definieren. Zum Beispiel so, dassmake rm sourcefileodermake mv sourcefile newsourcefilesowohl die Quelle als auch die erzeugten Ziele löscht bzw. umbenennt.In der Praxis ist selbst bei ziemlich großen Blogs oder Online-Projekten ein
make clean-/make all-Zyklus normalerweise schnell genug und wird oft ohnehin gebraucht, wenn man Templates oder Elemente des Website-Designs ändert. Wenn die Größe so weit geht, dass die Rebuild-Zeit ein Problem wird, ist es möglicherweise sinnvoller, die Quellen in einer Datenbank zu verwalten und ein echtes CMS zu verwenden, das beim Client-Zugriff dynamisch generiert.make clean?rm/%.html:@rm -f source/%.html build/%.htmlAusführen würde man es mit
$ make rm/page.html.prune-Funktion.http://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
Die beste Lösung, die meiner Meinung nach auch mit
makefunktioniert, ist aber einmake dist-Target, das das endgültige.tar.gz-Archiv der Ergebnisse erstellt. Wenn die Regeln korrekt geschrieben sind, landen keine veralteten Dateien darin. Bei großen Projekten kann das langsam sein, aber während der Entwicklung braucht man es ohnehin nicht, sondern nur beim Release. Inkrementelle Builds bleiben weiterhin möglich; nur das abschließende.tar.gzwird von Grund auf erzeugt.blog.shinspiriert.Ich habe es übernommen und angepasst und daraus meinen minimalistischen Static-Site-Generator
barfgemacht. Ohne Karls Veröffentlichung dieser großartigen Arbeit gäbe es ihn nicht.[0]: https://github.com/karlb/karl.berlin/blob/master/blog.sh
[1]: https://barf.bt.ht
shite, und damit baue ich meine Website. Der Name deutet auf die Softwarequalität hin :)Am besten gefällt mir daran, dass ich bisher nichts upgraden musste und es wohl auch künftig nicht müssen werde. Das Nächstbeste ist Hot Reload ohne JavaScript.
[1] https://github.com/adityaathalye/shite
[2] https://evalapply.org
m4beimischt, kann man denselben skelettartigen Ansatz beibehalten und trotzdem etwas mehr Flexibilität gewinnen.Habe vor etwa 20 Jahren mal eine kleine Website gepflegt, die auf diese Weise gebaut war. Aber abgesehen von persönlichen Websites bin ich mir nicht sicher, ob dieses Modell heute noch gut passt. Denn im Kern erzwingt dieser Ansatz eine Web-1.0-Rollenverteilung. Alle beitragenden Nutzer müssen entweder HTML beherrschen, oder jemand muss die lästige Aufgabe des „Webmasters“ übernehmen.
[1] https://en.wikipedia.org/wiki/M4_(computer_language)
m4“ gibt es nicht.Man startet ein sauberes Projekt und schwört sich, diesmal
m4nicht anzufassen. Dann fügt man, um Wiederholungen zu reduzieren, einen kleinenm4-Aufruf ein.Ein Jahr später wühlt man sich durch fünf Ebenen Makro-Expansion, um herauszufinden, warum das Wort „cat“ stillschweigend von der Website verschwindet, und entdeckt am Ende, dass ein Junior-Entwickler, statt es aus dem Handbuch zu kopieren, selbst eine for-Schleife implementiert und dabei die Anführungszeichen vermasselt hat.
Nachdem das akute Problem gelöst ist, kommt man zu dem Schluss, dass das Debuggen der DSL zu schwierig ist, und holt die M4-Makrodatei hervor, die man von Projekt zu Projekt mitkopiert hat. Danach verbringt man einen Tag damit, alle
define-Verwendungen durch Makro-erzeugende Makros zu ersetzen, die der Ausgabe Kommentare hinzufügen, damit ein Skript zur Erzeugung von Stacktraces funktioniert.Beim nächsten Projekt stellt man eine absolute Regel auf: kein
m4! Natürlich könnte diese eine Stelle eine Ausnahme sein.m4oderperlzu verlassen, würde ich empfehlen, SGML zu verwenden, die Grundlage und gemeinsame Obermenge von HTML und XML.SGML bringt einfache typgeprüfte Textmakros, also Entity Expansion, von Haus aus mit und ermöglicht auch parametrisierte Makro-Expansion mit Typkenntnis. Mit Typ ist hier der allgemeine Inhaltstyp von Markup-Elementen gemeint, also die erlaubten Kindelemente und deren Reihenfolge; außerdem werden Kontexte wie Attribute, CDATA und RCDATA für Expansion oder Escaping berücksichtigt.
Potenziell bösartige Nutzerkommentare korrekt zu expandieren und zu escapen und dabei zum Beispiel benutzerdefinierte Regeln anzuwenden, die Inline-Markup erlauben, aber
script-Elemente verbieten, lässt sich eigentlich nur mit SGML sauber erweitern. Auch Markdown oder Wiki-Syntax nach HTML zu expandieren, externe/syndizierte HTML-Inhalte einzubinden sowie RSS und Outlines für die Navigation zu erzeugen, ist möglich. Für recht komplexe Vorbereitungsarbeiten an statischen Sites auf der Kommandozeile passt das gut.[1]: https://sgmljs.net/docs/producing-html-tutorial/producing-ht...
[2]: https://sgmljs.net/docs/sgmlproc-manual.html
m4odersed-Suchen-und-Ersetzen würde ichenvsubstausprobieren.Das ist ein Programm, das Variablenreferenzen im Bash-Stil, zum Beispiel
$TITLE, durch Werte aus Umgebungsvariablen ersetzt.export CURRENT="..."cat page.html | envsubtm4“ – bitte bloß nicht.m4taugt nicht einmal besonders gut als esoterische Programmiersprache.Dass etwas in sendmail lief, reicht nicht als Rechtfertigung für irgendetwas.
Immer wenn ich hier einen interessanten Beitrag lese, abonniere ich den Feed. Ob Wordpress-Site, Bear Blog, Micro.blog, Havenweb-Blog oder der Feed einer selbstgebauten Site: Ich füge ihn dem Modul
Really Social Sitesvon Hey Homepage hinzu.Langfristig würde ich diese Blogliste gern öffentlich machen, so wie Kagi es mit der Small-Web-Initiative tut. Wenn man allerdings Qualität hinzufügen will, ist Kuration der Kernpunkt, und wenn man über Kuration nachdenkt, wirkt es ziemlich naheliegend, eine Art Online-Magazin zu starten.
Woher nehmen Leute dieses im positiven Sinne verstandene „Anspruchsgefühl“, dass sie etwas mit anderen teilen dürfen? Ich frage mich, warum sie annehmen, dass andere sich für das interessieren, woran ich arbeite. Manchmal fühlt es sich wie ein Wettbewerb an. Nach dem Motto: „Um Likes oder Reichweite im Blog zu bekommen, muss ich etwas möglichst Cooles bauen.“
Zusammenarbeit ist natürlich gut, und sie ist nur möglich, wenn alles öffentlich ist. Aber die Grenze zwischen „ich mache das, weil es cool wirkt“ und „ich bemühe mich, es mit anderen zu teilen, um eine Reaktion zu bekommen“ ist mir nicht ganz klar.
Er meinte, selbst wenn man nur eine einzige Testdatei ändert, könne man mit einem einzigen Befehl die gesamte Arbeit neu erzeugen, inklusive Testausführung und Diagrammerstellung.
https://pages.github.com/
Meiner ist ein wenig überkonstruiert, weil ich Hot Reload ohne JavaScript wollte, aber es war schönes Yak Shaving.
Die Grundidee ist dieselbe. Für Templates nutze ich Heredocs, dazu einen Compiler, der Plain Text in HTML umwandelt; in meinem Fall ist das
pandoc. Für die Indexerzeugung verwende ich eine Zwischen-CSV, und es gibt auch einen nützlichensed-Trick, um Front Matter herauszuziehen. Klassisch und gut.[1] https://github.com/karlb/karl.berlin/blob/master/blog.sh
[2] https://github.com/adityaathalye/shite
[3] Meine Vorgehensweise: https://github.com/adityaathalye/shite/blob/master/bin/templ...
Allerdings gibt es ein paar Einschränkungen. Ich organisiere Beiträge nach Namespaces und nehme das Datum in die URL auf, aber
makekann das nicht direkt gut handhaben.makebesteht darin, dass es bei großen Programmen, die mit langsamen Compilern gebaut werden, kleine Änderungen durch inkrementelle Rebuilds deutlich schneller macht.Ein kompletter Rebuild, der 40 Minuten dauern würde, kann in etwa 3 Sekunden fertig sein.
Wenn eine statische Website von Grund auf in unter einer Sekunde erzeugt wird, indem nur ein gemeinsamer Header und ein paar Hundert HTML-Dateien per
catzusammengefügt werden, gibt es keinen Vorteil,makestatt eines Skripts zu verwenden. Es entsteht nur das Risiko unvollständiger Builds durch Dependency-Bugs..phonymarkieren.Trotzdem kann man Unterscheidungen wie
make buildundmake pushbeibehalten.In anderen kleinen Projekten habe ich als provisorischen Bundler ein sehr kleines Shell-Skript verwendet. Es fügt CSS und JS in HTML ein; Ziel war, auch nicht gebaute Dateien lokal ausliefern zu können.