Weniger ist mehr

Wenn man im eigenen Quellcode Funktionen nicht mehr findet und lange suchen muss, könnte ein Grund dafür over engineering sein. Ich habe meine Seite massiv aufgeräumt und bin regelrecht erleichtert.

Schon während wir an der Webseite meines Kollegen Mark gearbeitet haben, juckte es mir in den Fingern. Bei Mark haben wir immer wieder Plugins, Snippets und Teile von meinen Templates benutzt – wir müssen das Rad ja nicht immer neu erfinden. Und im Zuge der Arbeiten fielen mir zwei Sachen auf:

  1. Ich musste viel zu häufig überlegen, wo ich welchen Teil des Quellcodes finde
  2. Ich habe zu viele kleine Ausnahmen, die keinen großen Unterschied machen

Irgendwann hatte ich mal die grandiose Idee, meine Seite so modular wie möglich aufzubauen. Ich habe alles ausgereizt, was sich ausreizen ließ und das endete schließlich in einem viel zu komplexen Konstrukt, für eine eigentlich recht simple Webseite. Zeit, den Ballast loszuwerden!

Bestandsaufnahme

Mein Vorhaben, alles möglichst modular und wiederverwendbar aufzubauen, resultierte letztendlich in etlichen Dateien, die alle ein bisschen was machen und bei denen ich zum Schluss immer wieder saß und von Datei zu Datei gesprungen bin, um Abläufe zu verstehen. Gar nicht gut.

Besonders bei den Snippets habe ich mich ausgetobt, hier gab es zwei Kategorien:

  1. Organismen - Snippets, die etwas komplexer waren. Zum Beispiel eine Card, die wiederum aus mehreren anderen Snippets bestand, wie dem card-footer, card-body, card-header … die sogenannten
  2. Partials - Snippets, die nur eine einzige Aufgabe erfüllen und in sich recht simpel sind.

Schließlich führte Kirby das Prinzip der Slots ein, weshalb ich dann auch noch damit anfing und einige Snippets hatte, die ich für Layouts benutze und die eben diese Slots nutzten. Damit hatte ich also eine dritte Art Snippets. Nein, eigentlich vier, denn es gab auch noch Snippets, mit denen ich Snippets aus Plugins überschrieben habe …

Ich habe zsh mal zählen lassen, alt vs. neu:

  • alt: 82 Snippets 🤯
  • neu: 34 Snippets
  • alt: 25 Templates
  • neu: 16 Templates

34 Snippets sind immer noch nicht wenig, aber ich habe hier nun nichts, worauf ich verzichten will und mag auch keine überfrachteten Dateien, dann lieber in logische Blöcke aufteilen.

Für die meisten der Templates gab es auch noch einen Controller und für ein paar wenige auch ein Model. Ergänzend dazu auch noch einige page-methods …

Ich hatte außerdem den Entschluss gefasst, vom bisher benutzten UI-Framework UIKit zu Tailwindcss zu wechseln. Davon war ich anfangs ziemlich begeistert, dann fing das aber an mich massiv zu nerven. Mein schöner HTML-Code wurde immer unübersichtlicher, ich wiederholte mich ständig, weil eben nicht für jedes kleine UI-Element eine Komponente bauen wollte. Ich fing also an, den Tailwind-Kram in eigenes SCSS zu überführen. Wurde damit aber natürlich nie fertig, weil es immer Wichtigeres gab. Also auch hier alles neu.

Beim Wechsel weg von Tailwind habe ich mich seit längerer Zeit mal wieder intensiv mit CSS und all den neuen Möglichkeiten beschäftigt und wieder meinen Spaß daran gefunden. Schnell wurde mir dann klar, dass ich auch kein SCSS mehr benötige, inzwischen kann ich all das viel besser direkt mit CSS umsetzen.

Gesagt, getan. Mein Build-Prozess beschränkt sich jetzt ausschließlich aufs Zusammenpacken von einzelnen CSS-Dateien und einem Hot-Reload, wenn ich lokal entwickle. Love it.

Reduzieren

Von Anfang an war klar, dass ich weniger Snippets usw. benutzen möchte. Das ließ sich schon alleine reduzieren, indem ich es mit den Komponenten nicht übertrieb. Ich hatte mir zwei Methoden geschrieben $site->partial() und $site->organism() mit denen ich meine Snippets laden konnte und die meine Ordnerstruktur abstrahierten. Die wollte ich auf jeden Fall loswerden.

One Snippet to rule them all

Das erste Snippet, was ich schrieb, war mein Layout Snippet. Ich fing recht simpel an, Header, Footer, Main-Content. Die ursprüngliche Idee war, ein Haupt-Layout-Snippet zu haben und das dann später in den verschiedenen Seitentypen als Grundlage zu benutzen. Ein Listing und die Artikelansicht würden dann auf das Snippet zurückgreifen und vielleicht noch selber Layout-Snippets verwenden.

Schnell kam ich allerdings zu zwei Erkenntnissen:

  1. Ich brauche keine 5 unterschiedlichen Listingtypen, die sich alle nur in wenigen Details unterscheiden.
  2. Wenn ich sehr streng vorgehe, können alle Templates dasselbe Layout-Snippet nutzen.

So ist es dann auch gekommen. Ich habe nur dieses eine Layout-Snippet, welches etliche Slots enthält. Die meisten davon sind standardmäßig einfach leer. Ich kann in den jeweiligen Templates dann entscheiden, ob und wie ich sie nutzen möchte:

<main>
<?php snippet('page/breadcrumb') ?>

<?php if ($intro = $slots->intro()): ?>
    <?= $intro ?>
<?php endif; ?>

<?php if ($body = $slots->body()): ?>
    <?= $body ?>
<?php endif; ?>

<?php if ($actions = $slots->actions()): ?>
    <?= $actions ?>
<?php endif; ?>

<?php if ($related = $slots->related()): ?>
    <?= $related ?>
<?php endif; ?>

<?php if ($comments = $slots->comments()): ?>
    <?= $comments ?>
<?php endif; ?>
</main>

In den meisten Fällen werden diese Slots dann in den Templates einfach mit einem Snippet aufgefüllt, hier zum Beispiel die Kommentare im default Template:

<?php slot('comments'); ?>
<?php snippet('post/comments'); ?>
<?php endslot(); ?>

Meine Standard-Templates werden dadurch sehr schlank. Weil ich mich außerdem dazu entschieden habe, dass ich nicht so viele leicht unterschiedliche Listings- und Seiten-Templates brauche, hat sich mein Code dadurch enorm verschlankt und ist trotzdem nicht überfrachtet, weil ich womöglich mit etlichen if-Abfragen versuche fehlende Templates zu kompensieren.

Auch meine ganzen Komponenten konnte ich so enorm verkleinern. Alleine die Card, die ich auf der alten Seite benutzt hatte, existierte in zwei verschiedenen Varianten, die sich jeweils 3-4 Partial-Snippets teilten. Die Zeiten sind vorbei. Meine Cards sind nun nur noch ein Snippet mit Slots. Dank Container-Queries in CSS sind sie trotzdem noch weitaus dynamischer als sie vorher jemals waren. So cool!

<?php snippet(
    'card',
    [
        'link' => $article->url(),
        'backgroundImage' => $article->getHeroImage(),
        'classNames' => [$className],
    ],
    slots: true
); ?>
<?php slot('preview'); ?>
<h3><?= $article->title() ?></h3>
<?php endslot(); ?>
<?php slot('details'); ?>
<p>
    <?= strip_tags($article->intro()->kirbytext()) ?>
</p>
<?php endslot(); ?>

<?php endsnippet(); ?>

Anstatt also hier ein Snippet zu laden, welches wiederum aus vier anderen Snippets zusammengesetzt ist und alle reichen sie Daten zueinander weiter und blah… nur noch ein Card-Snippet mit Slots ❤️

Container-Queries in CSS ermöglichen es mir außerdem Snippets wie meine Embed-Box flexibler zu gestalten. Bisher musste ich CSS-Klassen reingeben, die festgelegt haben, ob das Vorschaubild links, oben oder den gesamten Platz einnimmt. Dank Container-Query braucht es das alles nicht mehr. Je nachdem, wie viel Platz die Box hat, entscheidet sie selber, wo das Bild am besten positioniert wird:

Jetzt könnte man natürlich fragen, was das soll, das ging doch mit media-queries schon immer. Der Clou ist allerdings, dass das eben nicht auf die breite des Screens fußt, sondern auf den Platz, die die Box innerhalb der Seite hat. Füge ich die Box also in einen Artikel ein, wo sie viel Platz hat, ist das Bild links, füge ich sie in eine (nicht vorhandene) Sidebar ein, wo sie nur wenig Platz hat, wird das Bild oben positioniert.

One controller to rule them all

Auch bei den Controllern habe ich angesetzt. Davon habe ich sowieso viel weniger, weil ich viel weniger Templates habe. Ich habe zudem einen Site-Controller eingeführt, der die Grunddaten liefert, die jede Seite braucht. Jeder andere Controller, bindet zuerst diesen Site-Controller ein. Hat ein Template gar keinen Controller, greift Kirby automatisch auf den Site-Controller zurück. So kann ich sicherstellen, dass jedes Template auf jeden Fall immer bestimmte Daten griffbereit hat.

Der Hauptcontroller enthält dann z.b. Daten für die Meta-Tags, ein Hero-Image oder zu welchem Team die Seite gehört (dazu irgendwann mal mehr 😎). Der Listing-Controller reichert diese Daten dann nur noch an:

return function ($site, $kirby, $pages, $page) {
    $shared = $kirby->controller('site', compact('page', 'pages', 'site', 'kirby'));

    // […]

    $articleList = $page->articleList($sourceBy, $sortingBy, $sortingDirection, $limit);

    $exports = [
        'articleList' => $articleList ?? [],
    ];

    return A::merge($shared, $exports);
};

One listing to … Okay, ich lass' es

Eine weitere Entscheidung war, dass ich weniger Listings haben will. Die meisten meiner Listings haben sich leicht in der Darstellungsweise unterschieden. Nichts, was so viele Ausnahmen und Extra-Templates rechtfertigen würde; zudem glaube ich nicht, dass Besucher die Unterschiede wirklich bemerken und wenn ja wohl eher als inkonsistent wahrnehmen. Also Templates reduziert, alle nutzen denselben Controller, selbst Ausnahmen greifen auf den Site-Controller zurück:

Vielleicht ist die oben im Quellcode schon diese Zeile aufgefallen:

$page->articleList($sourceBy, $sortingBy, $sortingDirection, $limit);

Hier passiert ziemlich viel Magie. Auf diese page-method konnte ich nicht verzichten, ich hatte sie in ähnlicher Form schon auf der alten Seite im Einsatz. Der Code dahinter sorgt für ein dynamisches Listing, obwohl immer dasselbe Template mit demselben Controller verwendet wird.

Das eigentlich Tolle daran: Ich kann die Listings über das Panel steuern, indem ich dort eine Quellenart und dann die entsprechenden Optionen auswähle:

Wie man hier sieht, kann ich die Art der Quelle wählen, kann sagen wie viele Einträge pro Seite angezeigt werden und ab wann paginiert wird. Obendrein kann ich noch festlegen, wie die Einträge sortiert werden sollen.

Wähle ich Children oder Index als Quellart aus, habe ich unter Seiten die Möglichkeit Unterseiten anzulegen. Wähle ich eine andere Art Quelle aus, ist das nicht mehr der Fall. Ich nutze dann Metadaten, die bei den Seiten hinterlegt werden. Hier sind alle Optionen:

Wähle ich den Typ Seiten aus, kann ich rechts eine Auswahl an Seiten treffen, die als Quelle dienen:

Wähle ich etwa Kategorien aus, so bekomme ich eine Auswahl aller vorhandenen Kategorien angezeigt. Im Listing werden dann alle Seiten mit der jeweiligen Kategorie aufgelistet.

Das ermöglicht es mir viele verschiedene Listings anzulegen, die dennoch alle auf demselben Template basieren.

Ein paar Ausnahmen gibt es, die Leseliste zum Beispiel oder so Sonderseiten, wie die Homepage, About oder Now. Die haben eigene Templates, basieren aber wiederum trotzdem auf dem Layout.

Plugins

Bei den Plugins konnte ich auch einiges rausschmeißen, die Liste ist dennoch ziemlich lang, wobei die Mehrzahl der Plugins einfach kleine Helfer sind, die ich ausgelagert habe. Ein paar größere sind natürlich auch dabei:

Besonders wichtig sind mir hier natürlich der IndieConnector und das Komments-Plugin, was hier in einer noch unveröffentlichten, neuen Major-Version läuft, die bald allen zur Verfügung stehen wird. Das Podcaster-Plugin brauche ich für meine Podcasts.

Das LinkPreview-Plugin ermöglicht mir diese kleinen Embed-Boxen. Es geht alle Links in einer Seite durch und holt und speichert sich deren Metadaten. Ist noch etwas buggy, aber sobald das besser läuft, werde ich es veröffentlichen.

Ganz wichtig ist noch das Staticache-Plugin, welches mir statische HTML-Dateien anlegt und damit meine Seite beschleunigt. Das Highlighter-Plugin sorgt für eine gute Darstellung von Quellcodes auf meinen Seiten und das Language-Selector-Plugin ändert das Layout des Sprachwechslers im Panel, was auf diese Weise etwas praktischer zu nutzen ist, finde ich:

Jetzt gilt es, diese Struktur beizubehalten und sich nicht zu Ausnahmen hinreißen zu lassen. Ich habe noch ein paar Ideen für meine Seite und gerade Hub-Bereich soll später nochmal anders funktionieren, aber ich werde versuchen, möglichst wenig von der jetzigen Struktur abzuweichen.

Beim Hub hatte ich ein kurioses Verhalten, denn egal welche Unterseite vom Hub ich aufgerufen habe, ich bekam immer einen 404-Fehler. Sobald ich die Route geändert habe, sie also nicht mehr /hub, sondern hub-test genannt habe, ging alles wieder. Ich habe .htaccess, routes und Plugins durchsucht und habe einfach keinen Grund für das Verhalten gefunden. Insbesondere, weil das gleiche Konstrukt auf der alten Seite ja funktionierte. Ich bin ratlos und konnte mir jetzt nur durch Umbenennen der Route und erforderliche Redirects helfen …

Weg damit

Ein paar Seiten sind auch weggefallen, wie die Übersichtsseite aller meine Artikel hier und woanders. Dank Umami konnte ich recht schnell identifizieren, für welche Seite ich Besonderheiten gebaut habe, die aber so gut wie nie aufgerufen wurden. Die Entscheidung war dann: Weg damit.

Es könnte also zu ein paar 404-Fehlern kommen, die gewollt sind.

Generell will ich nicht ausschließen, dass hier und da noch zu Fehlern kommt, weil ich was übersehen oder vergessen habe. Das könnte sicherlich in den kommenden Tagen und Wochen passieren. Ich behalte das im Auge.