3 Punkte von GN⁺ 2023-09-11 | 1 Kommentare | Auf WhatsApp teilen
  • 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 source beizubehalten, HTML-Dateien header.html voranzustellen und alle übrigen Dateien unverändert nach build zu kopieren
  • make build ordnet mit find source -type f und patsubst Eingabe- 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.html gesetzt
    • Dateien, die kein HTML sind, werden unverändert in das Verzeichnis build kopiert
  • Makefile-Regeln

    • Das Target build hat Abhängigkeiten zu allen Dateien unter source, jeweils zugeordnet zu Ausgabedateien unter build
    • Es führt selbst keine Arbeit aus, dient aber als Einstiegspunkt, damit die passende Regel für jede Datei angewendet wird
    • Die Regel build/%.html hängt von source/%.html, header.html und Makefile ab
    • Mit mkdir -p $(dir $@) wird das Ausgabeverzeichnis erstellt, und mit cat header.html $< > $@ werden Header und Eingabe-HTML zur Ausgabe zusammengeführt
    • Die Regel build/% kopiert Dateien, die kein HTML sind, mit cp $< $@ unverändert
    • Mit diesem header.html und diesen Regeln erzeugt make build ein Verzeichnis build, das lokal geöffnet oder auf einen Webserver hochgeladen werden kann

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 current hinzugefü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.html aus source/foo.html erzeugt wird
    • Entweder muss auch für Markdown-Dateien das Suffix .html beibehalten 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 entr lässt sich bei Änderungen an source, header.html oder Makefile automatisch make build ausführen
    • Target watch: find source header.html Makefile | entr make build
    • Wer Abhängigkeiten vermeiden möchte, kann auch inotifywait verwenden
  • 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 worktree lösen
    • Der Ansatz basiert auf Sangsoo Nams Artikel
    • Der Branch gh-pages wird als Worktree public_html hinzugefügt
    • build/* wird nach public_html kopiert
    • git add --all, git commit -m "Deploy to github pages" und git push origin gh-pages werden ausgeführt
    • Am Ende wird der Worktree mit git worktree remove public_html entfernt
  • 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

 
GN⁺ 2023-09-11
Hacker-News-Kommentare
  • Meine persönliche Website (https://pablo.rauzy.name/) habe ich früher mit einem einfachen Makefile erzeugt.
    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 grep und sed zum Einsatz.
    Dazu habe ich ein paar git hooks ergänzt, die bei Bedarf automatisch make aufrufen; 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.
  • Das Problem bei diesem Ansatz ist, dass Dateien, die man unter source/ löscht, unter build/ 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 make bauen.
    Falls es für dieses Muster in Makefiles einen gängigen Workaround gibt, würde ich ihn gern kennen.
    • Ich weiß nicht, ob das ein gängiges Muster ist, aber meine Lösung war, bei jedem Lauf einen Befehl auszuführen, der „unerwartete“ Dateien löscht.
      Mit der shell-Funktion von GNU Make liste ich Dateien auf und filtere mit der Funktion filter-out die „erwarteten“ Ausgaben heraus. Das ist ein hässlicher Hack, aber indem der Befehl als Teil einer Variablenerweiterung über die shell-Funktion ausgeführt wird, ist sichergestellt, dass er jedes Mal läuft.
      Makefile-Link: https://github.com/jaredkrinke/make-blog/blob/main/Makefile
    • Dateilöschungen und Umbenennungen sind in vielen Versionsverwaltungs-/Build-Systemen ein häufiges Problem.
      Neben der Nuklearoption make clean kann man auch spezielle Make-Targets für Löschen und Umbenennen definieren. Zum Beispiel so, dass make rm sourcefile oder make mv sourcefile newsourcefile sowohl 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.
    • Reicht nicht make clean?
    • So etwas müsste funktionieren:
      rm/%.html:
      @rm -f source/%.html build/%.html
      Ausführen würde man es mit $ make rm/page.html.
    • Das universelle Build-System Shake hat genau für diesen Zweck eine prune-Funktion.
      http://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
      Die beste Lösung, die meiner Meinung nach auch mit make funktioniert, ist aber ein make 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.gz wird von Grund auf erzeugt.
  • Ich wurde direkt von Karls Arbeit an dem im Artikel erwähnten Shell-Skript blog.sh inspiriert.
    Ich habe es übernommen und angepasst und daraus meinen minimalistischen Static-Site-Generator barf gemacht. 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
    • Da hat jemand einen ähnlichen Geschmack. Meines nenne ich 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
  • Wenn man ein wenig m4 beimischt, 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)

  • So etwas wie „ein bisschen m4“ gibt es nicht.
    Man startet ein sauberes Projekt und schwört sich, diesmal m4 nicht anzufassen. Dann fügt man, um Wiederholungen zu reduzieren, einen kleinen m4-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.
  • Statt sich auf allgemeine Textersetzungen wie m4 oder perl zu 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
  • Statt m4 oder sed-Suchen-und-Ersetzen würde ich envsubst ausprobieren.
    Das ist ein Programm, das Variablenreferenzen im Bash-Stil, zum Beispiel $TITLE, durch Werte aus Umgebungsvariablen ersetzt.
    export CURRENT="..."
    cat page.html | envsubt
  • „Ein bisschen m4“ – bitte bloß nicht.
    m4 taugt nicht einmal besonders gut als esoterische Programmiersprache.
  • Einmal gemacht, nie wieder.
    Dass etwas in sendmail lief, reicht nicht als Rechtfertigung für irgendetwas.
  • Mir gefällt, dass fast jeder Entwicklerblog, auf den man bei HN stößt, einen RSS-Feed hat.
    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 Sites von 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.
    • Ich versuche zu verstehen, ob ich als Entwickler seltsam bin, weil ich keinen eigenen Blog haben möchte.
      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.
    • Es gibt auch https://prose.sh, ähnlich wie Bear Blog.
  • Ein Freund hat einmal erklärt, wie er wissenschaftliche Arbeiten mit make generiert.
    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.
  • Eine hübsche Idee, aber wenn man ohnehin schon zu GitHub pusht, kann GitHub auch einfach den Quelltext nehmen und Markdown als gehostete Seiten veröffentlichen.
    https://pages.github.com/
    • Dann ist man über reines Hosting hinaus von GitHub abhängig. Besser ist es, von Anfang an dafür zu sorgen, dass die Site-Generierung lokal laufen kann.
  • Der Code gefällt mir.
    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ützlichen sed-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...
    • Sein GEMINI-Ansatz war ziemlich witzig. Er entfernt den Großteil der Formatierung per Regex.

Allerdings gibt es ein paar Einschränkungen. Ich organisiere Beiträge nach Namespaces und nehme das Datum in die URL auf, aber make kann das nicht direkt gut handhaben.

  • Wenn man sich solche Skripte ansieht, versteht man ziemlich genau, warum wir nicht alles selbst bauen, sondern Tools wie esbuild oder vite verwenden.
  • Der Vorteil von make besteht 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 cat zusammengefügt werden, gibt es keinen Vorteil, make statt eines Skripts zu verwenden. Es entsteht nur das Risiko unvollständiger Builds durch Dependency-Bugs.
    • Wenn Dateiabhängigkeiten eigentlich keine Rolle spielen, kann man die Build-Ziele als .phony markieren.
      Trotzdem kann man Unterscheidungen wie make build und make push beibehalten.
  • Wow, das ist fast genau das, was ich für meine Website vorhatte.
    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.