Ich versuche diesmal etwas anderes. Normalerweise baue ich Webanwendungen mit Ruby um Sinatra herum, zum Beispiel die Blogsoftware ursprung. Auch der PC-Hardwareempfehler pc-kombo ist im Grunde so eine Ruby/Sinatra-Anwendung. Aber man will ja nicht immer das gleiche machen, sondern dazulernen und neue Lösungen ausprobieren. Bei Pipes z.B. war das der grafische Editor. Gerade versuche ich mich daher an einem statischen Seitengenerator als technischen Kern eines neuen Projekts.
Worum geht es dabei? Performance und Architektur. Zum einen verspreche ich mir eine schnellere Seite, wenn ihr Kern statisches HTML ist und nur von nginx ausgeliefert wird, ohne Datenbankabfrage oder Kontakt mit Ruby. Zum anderen will ich auch sehen, wie anders die Daten strukturiert werden können und wie viel kleiner der Code wird, wenn der Fokus mehr auf das generierte HTML und CSS und eventuell Javascript liegt und beim Seitenaufruf nicht direkt auf die Datenbank zugegriffen werden muss.
Bisher setzte ich sehr auf Konfiguration. So gibt es einen Ordner pages/ mit JSON-Dateien, die bestimmen welche HTML-Seiten erstellt werden sollen. Als Beispiel die gpu.json:
{ "view": "gpus", "target": "gpus.html", "collection": "gpus" }
Die Felder hier bestimmen das Verhalten des Generators: view
welches Template verwendet wird, target
welche Datei erstellt werden soll, collection
welche Datenbankdaten dem Template mitgegeben werden sollen.
Dazu gehören noch zwei Templates: Zuerst die implizit aufgerufene layout.erb:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>generatortest</title> </head> <body> <%= yield %> </body> </html>
Und die gpus.erb:
<h2>Gpus</h2> All the gpus we know: <ol> <% collection.each do |item| %> <li><%= item[:name] %></li> <% end %> </ol>
All das beachtet die generator.rb:
require 'erb' require 'tilt' require 'json' require 'moneta' store = Moneta.new(:Memory) store[:gpus] = [{id: 1, ean: '123', name: 'WindForce RTX 2080'}, {id: 2, ean: '456', name: 'Sapphire RX 590'} ] # foreach page definition in pages, get the specified data collection, give it to the template and save the html at the target destination Dir.glob(File.expand_path('../pages/*.json', __FILE__)).each do |file| control = JSON.parse(File.new(file).read, symbolize_names: true) collection = store[control[:collection].to_sym] if control[:collection] view = File.join('../views/', control[:view] + '.erb') view = File.expand_path(view, __FILE__) if File.exists?(view) layoutpath = File.expand_path('../views/layout.erb', __FILE__) layout = Tilt::ERBTemplate.new(layoutpath) output = layout.render { # this construction ensures the layout is used template = Tilt::ERBTemplate.new(view) # the final render has nothing to evaluate but needs the # the local collection, thus the empty Object as param template.render(Object.new, collection: collection) } targetpath = File.join('../public/', control[:target]) targetpath = File.expand_path(targetpath, __FILE__) File.write(targetpath, output) end end
Man sieht: Das ist absolutes Anfangsstadium. Aber es funktioniert schonmal! Nach Aufruf des Generators landet fertiges HTML im Ordner public/. Was dann der wäre, den der Webserver ausliefern könnte, inklusive dieser gpus.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>generatortest</title> </head> <body> <h2>Gpus</h2> All the gpus we know: <ol> <li>WindForce RTX 2080</li> <li>Sapphire RX 590</li> </ol> </body> </html>
Überlegungen
Um zwei Entscheidungen habe ich mich gedrückt bzw sie ungewöhnlich flexibel gelassen. Mit Moneta vermeide ich die Entscheidung für eine Datenbank. Normalerweise wäre das sqlite, aber ich bin noch nicht sicher ob das hier wirklich passt oder ob es mehrere Prozesse geben wird. Deswegen landen die Datenbanktestdaten bisher auch nur im Arbeitsspeicher. Und tilt würde es später einfach machen, statt erb eine andere Templatesprache zu benutzen.
Bedenken habe ich noch wegen der Filter. Ich weiß, dass ich später Nutzer Listen – wie die im Beispiel erstellte – filtern lassen will. Soll das komplett per Javascript geschehen? Oder baue ich dafür Seiten ein, die mit Ajax auf die angepassten Datenbanksets zugreifen? Dass es einen dynamischen Teil der Anwendung geben wird ist immerhin bereits vorgesehen, als Backend für den Administrator.
Ob der Seitengenerator nicht ein Monster werden wird? Ich werde versuchen müssen ihn schlank zu halten. Andererseits sehe ich gerade nicht, dass ihm noch viel fehlen würde, um die Seite umzusetzen die ich bauen will.
Erstes Fazit
Das könnte funktionieren. Wie schlüssig sich das Konzept anfühlt und wie schnell dieser Prototyp zu bauen war erklärt mir etwas, warum die letzten Jahre diese static site generators so beliebt wurden. Es ist eben nicht nur etwas für Blogs oder eine Homepage. Die HTML-Erstellung nicht erst beim Seitenaufruf zu machen ist auch generell ein gutes Prinzip.
Aber es wird sich zeigen, ob mit diesem Weg dann auch wirklich eine performante und wartbare Webanwendung entsteht.
onli blogging am : Zukunftspläne für pc-kombo: 4 Aufgaben, 3 Seiten, mehr FOSS
Vorschau anzeigen
onli blogging am : Xapian/Omega in Ruby/Sinatra integrieren
Vorschau anzeigen