Menu

Web-Entwicklung mit F# und HTMX

Website-Programmierung mit F#, Strapi und HTMX

Die eigene Web-Seite ist für Freelancer und Software-Unternehmen wohl eines der wichtigsten Aushängeschilder.
Potenzielle Kunden können - über den Inhalt hinaus - einen ersten Eindruck über die Kompetenzen des Anbieters bekommen. Ein kurzer Blick in die Browser-Entwicklungstools reicht aus um zu sehen, ob die Seite performant und technisch Korrekt umgesetzt ist.

Bei der eigenen Anwendung hat man aber Freiheiten, die man bei einem Kundenprojekt oft nicht hat:
Es gibt keine Einschränkung bei der Gestaltung und dem Inhalt sowie bei der Wahl der Programmiersprache oder Frameworks.

In meinem Fall habe ich mich sofort für meine Lieblingssprache F# entschieden.

Bei einem Auftrag würde man die Wahl nicht so leichtfertig treffen. Man muss das vorhandene Know-How und auch die Präferenz der Kollegen beim Kunden einbeziehen, sowie die Systemlandschaft die meistens bereits Vorhanden ist. Auch die Wartung und Pflege des Projektes nach Abschluss sollte man im Auge behalten.
Findet der Kunde nach Projektende neue Entwickler die sich schnell und effektiv darum kümmern können?
Eine TypeScript-Applikation mit einem gängigen Framework wie Next.JS kann man leichter vermitteln als ein funktionales "Meisterwerk" in Haskell.
Auch der Stil in dem die Anwendung geschrieben wurde unterliegt solchen Beschränkungen. Ein JavaScript-Backend kann rein funktional mit Monaden übersäht entwickelt werden, und ja, das macht Spaß und man kann sich auf sein Genie etwas einbilden - nur blöd, wenn der Nachfolger ganz andere Paradigmen gewöhnt ist und die hohe Lernkurve nun zusätzlichen Stress für ihn bedeutet.

Rücksicht nehmen muss ich bei meiner Seite allerdings nur auf mich selbst. Und ich mag unkonventionelle Technologien mit technischen Vorteilen.

Anforderungen

Was will ich eigentlich bauen?
Wenn man nicht weiß, wie das Endprodukt aussehen soll, kann man auch nicht geradewegs darauf hinarbeiten.
Ich will nicht sagen, dass man einen detaillierten Anforderungskatalog und ein UX-Wireframe benötigt - ganz und gar nicht. Aber eine grobe Vorstellung sollte man haben.

Neben dem "was will ich?" gibt es auch das "was brauche ich?". Genau wie einem Kundenprojekt ist das was gewollt ist, nicht immer das was wirklich gebraucht wird, und das gilt es auszuloten. Ich brauche eine Art Visitenkarte, etwas um Projektgebern von meiner Fachkompetenz zu überzeugen. Gleichzeitig will ich eine "schöne" Seite gestalten, die schnell lädt, allen technischen Standards entspricht und - na klar - zu Auftragsangeboten führt. Und ich will einen Blog um mich einfach mitzuteilen und Kollegen und Kunden meine Sichtweisen zu zeigen.

Die Website wird also hauptsächlich aus statischen Teilen bestehen, und nur ein kleiner Teil - der Blog - muss dynamisch erzeugt werden.
Ein flexibles Frontend-Framework wie Next.JS wäre also nicht nur zu viel, sondern auch unpassend; und eine unpassende Auswahl an Technologien bilden kein gutes Fundament für ein Software-Projekt. Darum habe ich mich für einen klassischen Ansatz mit einfachen Templating entschieden.

HTMX

Ein bisschen Dynamik brauchte ich aber schon.
Ich habe ein Kontaktformular vorgesehen, und wollte nicht ausschließen später mehr Interaktionsmöglichkeiten einzubauen.

Da ich häufig mit Webanwendungen zu tun habe, war React mit MUI meine erste Wahl - ohne viel darüber nachzudenken. Aber um die Seiten später statisch ausliefern zu können musste eine Lösung für das Serverseitige Rendern (SSR) her.
Da gibt es praktisch nur Next.JS.
Es verspricht einen Isomorphen Ansatz - also das Erzeugen von statischem HTML und Integration mit der Client-seitigen Darstellung. Das klappt häufig nur mittelmäßig gut. Viele Features von MUI können ausschließlich im Client genutzt werden (custom theme) und führen mitunter zu suboptimaler Performance.

Am meisten störte mich aber der enorme Ressourcen-Bedarf, sowohl beim Erzeugen der statischen Daten als auch während der Laufzeit. Auch der Browser hat bei einer Next.JS-Anwendung mehr zu tun, als mir lieb ist.

Ich sattelte also auf Templating mit Handlebars um. Statisches HTML wird nun einmal erzeugt und dann ohne viel Speicher oder CPU-Bedarf ausgeliefert. Trotzdem sollten Interaktionen nicht zum Neuladen der Seiten führen. Also konnte ich entweder ein paar JavaScript-Funktionen schreiben, die sich dann um Formulare usw. kümmern, oder man benutzt klassische Bibliotheken wie jQuery.

Die Rettung: HTMX

Das ist eine kleine Bibliothek, die reguläres HTML durch Direktiven erweitert, welche dann z.B. automatisch bei Klicken auf einen Button einen AJAX-Request sendet, und das Element dann durch die Antwort in Form von HTML ersetzt. Es wird also kein JSON für den Informationsaustausch verwendet, sondern der Browser sendet einfache HTTP-Anforderungen und erhält Markup, vom Server generiert, zurück.

Für Anwendungen, die keinen oder nur wenig Zustandsdaten im Browser verarbeiten müssen, ist das ein kluger Ansatz.

Ich könnte mir gut Vorstellen, HTMX für Online-Shops oder Administrationsoberflächen einzusetzen, und das State-Management komplett im Server zu realisieren. Meine kleine Homepage ist nicht komplex genug um als Exempel zu dienen, aber in Zukunft werde ich sicher häufiger HTMX empfehlen und auch verwenden.

HTMX - high power tools for html

Frontend-Server mit F# und Giraffe

giraffe.png

Mit dem Giraffe-Framework auf Basis von ASP.NET habe ich den Server-Part umgesetzt.

Ich würde jetzt beschreiben wie innovativ oder aufregend das ist, aber eigentlich ist das Frontend-System eher langweilig:

Ein kleiner Router lädt Daten aus dem Strapi-CMS und stößt einen Handlebars-Renderer mit Diesen an. Alles sehr konkret ohne viel Abstraktion. Das Giraffe-Framework bzw. ASP kümmert sich um Caching und die HTTP-Kommunikation. Das .NET-Framework kommt bereits mit allen Werkzeugen um später die Konfiguration aus der Umgebung zu laden.

Mir sind allerdings ein paar Problemchen aufgefallen. Giraffe basiert auf ASP.NET, verwendet daher auch die entsprechenden Klassen für die Webserver-Funktionen. Als funktionales Framework geht das allerdings manchmal ein bisschen auseinander. Z.B. verwende ich die mitgelieferte Middleware für statische Dateien, kann in Diese aber nicht eingreifen. Ich würde gern das Caching und ETags der statischen Dateien anpassen, dafür gibt es aber keine Option innerhalb von Giraffe.

Das Strapi-CMS

Für den Blog und Medien-Dateien brauchte ich ein schlankes und einfach zu handhabendes CMS.
Es gibt mittlerweile unzählige Headless-Content-Management-Systeme - also CMS, die sich nur um Datenhaltung und Verwaltungsoberfläche kümmern, nicht aber um das Rendering.
Da ich seit mehr als 10 Jahren vor allem mit Node.JS arbeite, wollte ich ein entsprechendes CMS. Denn erstens kenne ich das Ökosystem rund um Node, und zweitens kann ich schnell Anpassungen vornehmen.

Mir ist wichtig, dass die Benutzeroberfläche intuitiv ist, aber auch, dass die Relationen zwischen verschiedenen Daten gut abgebildet werden.
Dazu soll das CMS eine gut dokumentierte Schnittstelle anbieten. Ob GraphQL oder REST spielt nicht so eine große Rolle. Strapi bietet mir all das, ist sehr leicht aufzusetzen, ist gut dokumentiert und hat bereits viele wichtige Plugins und Erweiterungsmöglichkeiten.

Strapi - Open source Node.js Headless CMS

Perfekt ist es natürlich nicht. Ich bin wenig angetan von der Form der Datenstrukturen, die über die Schnittstelle ausgeliefert werden - zu viel Schachtelung. Aber zumindest ist die alles konsistent.

CSS - der lange Weg zu TailwindCSS

Wie ich weiter oben bereits erwähnte, habe ich zu Beginn MUI - Material UI verwendet.
Aus aktuellen Projekten kannte ich MUI auswendig.
Leider gab es Probleme mit Next.JS, und ohne React-Rendering auf Server-Ebene, macht das komplette Framework keinen Sinn.

Die Suche nach Alternativen gestaltete sich als schwierig und mühsam. Für einen leichtgewichtigen Ansatz gibt es Bulma, Pico.css, Milligram oder Pure.
Sie bringen entweder einen fertigen Satz an CSS-Klassen mit, oder geben Styles für HTML-Elemente direkt vor.
Allesamt brachten mir aber herzlich wenig, da ich entweder selbst viele Styles schreiben musste, damit ich die gewollte Optik erreichen konnte, oder machten die Mobil-Optimierung schwierig, durch mangelhafte Flexbox - Konzepte und komische (veraltete) Grid - Vorgaben. Es fehlte bei allen immer etwas Wichtiges. Vielleicht habe ich mir auch nicht genug Mühe gegeben mich, bei dem ein oder anderen Framework, tief gehend genug einzuarbeiten?

Jedenfalls bin ich dazu übergegangen mein eigenes CSS von Grund auf zu entwickeln. Mir ist dann aufgefallen, dass ich überwiegend kleine Hilfsklassen erstelle, also hier ein Padding, da eine Textfarbe usw. Ob das der beste Ansatz ist, darüber scheiden sich die Geister. Für mich war es der natürlichste Weg CSS zu schreiben.

Ein Framework, dass dieses Konzept komplett umsetzt ist TailwindCSS . Und da mein eigenes CSS eh schon sehr ähnlich aussah, hab ich eben den Umschwung zu Tailwind gemacht.

Eigentlich mag ich Frameworks gar nicht, besonders nicht, wenn es überall als Allheilmittel angepriesen wird.
Nach 20 Jahren in der Software-Industrie hat man viele Hypes und Trends durchlebt, die sich später als großer Fehler oder Sackgasse erwiesen haben.

In meinem Fall, passte aber die Technologie zur Anwendung, und was soll ich sagen? Tailwind hat die Arbeit ordentlich beschleunigt, und ja, ich habe überall Klassen im DOM, und sehr viel Wiederholungen - aber im Gegenzug ist alles visuell so, wie ich es mir vorgestellt habe und auch noch responsiv. Das Tooling um Tailwind hatte keinerlei Probleme mit meinen Handlebars-Templates. Da war ich doch positiv überrascht. Führe ich irgendwo eine neue Klasse aus der Tailwind-Sammlung in einer winzigen Handlebars-Komponente auf, wird diese auch korrekt im finalen CSS erzeugt.

Einziges Manko: nested classes werden nicht (oder nicht richtig) unterstützt.

Zusammenfassung

F#-Webserver

Mit dem Giraffe-Framework lässt sich ein Webserver genauso einfach und schnell aufsetzen wie mit Node.JS. Der Vorteil ist, ganz klar, das mächtige .NET-Framework mit all seinen integrierten Lösungen. Ich erwähnte ja, dass der Server-Code langweilig ist - und das ist etwas sehr Gutes. Jeder mit ein wenig .NET-Erfahrung sollte den Code schnell verstehen können - vor allem ich selbst, wenn ich ihn monatelang nicht mehr angeschaut habe. Dennoch hatte ich stellenweise das Gefühl, dass Giraffe und ASP.NET nicht 100%-ig harmonieren. Funktionale Programmierung und Objektorientierte schließen sich nicht gegenseitig aus. Aber funktionale Komposition und klassenbasierte Objekte mit Methoden für innere Zusandsänderungen...naja das passt nicht immer gut zusammen. Giraffe ist gut genug für den professionellen Einsatz, aber stellenweise gibt es noch konzeptionelle Löcher zu stopfen.

HTMX

Dynamisches HTML ohne JavaScript-Programme schreiben zu müssen, die für Bugs und Probleme sorgen, sollte ich mal das DOM verändern wollen, ist schon praktisch. Aus Design-Sicht macht es Sinn das State-Management an einer Stelle zu haben, und nicht auf Server und Client verteilen zu müssen. Und die Server-Zentrierung dürfte für etwas mehr Sicherheit und Integrität sorgen. Ein abschließendes Fazit zu HTMX kann ich noch nicht geben, dafür setze ich es noch nicht lang genug ein. Bisher ist es sehr motivierend und spannend damit zu arbeiten.

Strapi

Ein gut durchdachtes Headless-CMS ohne viel Schnickschnack. Ich finde es eine gute, pragmatische Wahl für kleine Web-Seiten. Aus Erfahrung weiß ich, dass es recht gut skaliert, nicht unbedingt bis auf Plattform-Ebene, aber es würde für gängige Online-Shops und besonders Blogs und News-Sites hervorragend passen.

TailwindCSS

Im Augenblick freue ich zu sehr über das Ergebnis mit Tailwind, um objektiv bleiben zu können. Die Integration in meinen unüblichen Stack verlief absolut reibungslos. Auf den gängigen Browsern sieht alles korrekt aus und die mobile Ansicht funktionierte auf Anhieb richtig.

Alles zusammen

Zumindest für den Anfang passen alle Technologien zusammen.
Die Integration erfolgt über 3 CLI-Aufrufe, die Performance stimmt und der Ressourcen-Bedarf ist so niedrig, dass die kleinste virtuelle Instanz meines Hosters ausreicht um die Seite zu bauen und auszuliefern.