Kontext und Variablen im Hugo Static Site Generator

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ In diesem Artikel werfen wir einen Blick auf das Thema Kontext und Variablen in Hugo, einem beliebten Static-Site-Generator. Sie werden Konzepte wie den globalen Kontext, die Flusssteuerung und Variablen in Hugo-Vorlagen sowie den Datenfluss von Inhaltsdateien durch Vorlagen zu Partials und Basisvorlagen verstehen.

In diesem Artikel werfen wir einen genauen Blick darauf, wie der Kontext im Hugo-Static-Site-Generator funktioniert. Wir werden untersuchen, wie Daten von Inhalten zu Vorlagen fließen, wie bestimmte Konstrukte die verfügbaren Daten verändern und wie wir diese Daten an Partials und Basisvorlagen weitergeben können.

Dieser Artikel ist keine Einführung in Hugo . Sie werden wahrscheinlich am meisten davon profitieren, wenn Sie etwas Erfahrung mit Hugo haben, da wir nicht jedes Konzept von Grund auf neu durchgehen, sondern uns auf das Hauptthema Kontext und Variablen konzentrieren. Wenn Sie sich jedoch durchgehend auf die Hugo-Dokumentation beziehen, können Sie möglicherweise auch ohne Vorkenntnisse nachvollziehen!

Wir werden verschiedene Konzepte untersuchen, indem wir eine Beispielseite erstellen. Nicht jede einzelne Datei, die für die Beispielseite benötigt wird, wird detailliert behandelt, aber das vollständige Projekt ist auf GitHub verfügbar. Wenn Sie verstehen möchten, wie die Teile zusammenpassen, ist das ein guter Ausgangspunkt. Bitte beachten Sie auch, dass wir nicht behandeln, wie Sie eine Hugo-Site einrichten oder den Entwicklungsserver ausführen – Anweisungen zum Ausführen des Beispiels finden Sie im Repository.

Was ist ein Static-Site-Generator?

Wenn das Konzept statischer Site-Generatoren neu für Sie ist, finden Sie hier eine kurze Einführung! Statische Site-Generatoren lassen sich vielleicht am besten beschreiben, indem man sie mit dynamischen Sites vergleicht. Eine dynamische Website wie ein CMS stellt im Allgemeinen für jeden Besuch eine Seite von Grund auf neu zusammen, ruft möglicherweise Daten aus einer Datenbank ab und kombiniert dazu verschiedene Vorlagen. In der Praxis führt die Verwendung von Caching dazu, dass die Seite nicht so oft neu generiert wird, aber für den Zweck dieses Vergleichs können wir uns das so vorstellen. Eine dynamische Website eignet sich gut für dynamische Inhalte : Inhalte, die sich häufig ändern, Inhalte, die je nach Eingabe in vielen verschiedenen Konfigurationen angezeigt werden, und Inhalte, die vom Website-Besucher manipuliert werden können.

Im Gegensatz dazu ändern sich viele Websites selten und akzeptieren wenig Eingaben von Besuchern. Ein „Hilfe“-Abschnitt für eine Anwendung, eine Artikelliste oder ein eBook könnten Beispiele für solche Seiten sein. In diesem Fall ist es sinnvoller, bei inhaltlichen Änderungen die finalen Seiten einmalig zusammenzustellen und danach jedem Besucher die gleichen Seiten zu liefern, bis sich die Inhalte wieder ändern.

Dynamische Sites bieten mehr Flexibilität, stellen jedoch höhere Anforderungen an den Server, auf dem sie ausgeführt werden. Es kann auch schwierig sein, sie geografisch zu verteilen, insbesondere wenn Datenbanken beteiligt sind. Statische Site-Generatoren können auf jedem Server gehostet werden, der statische Dateien bereitstellen kann, und sind einfach zu verteilen.

Eine heute gängige Lösung, die diese Ansätze vermischt, ist der JAMstack. „JAM“ steht für JavaScript, APIs und Markup und beschreibt die Bausteine ​​einer JAMstack-Anwendung: Ein Static-Site-Generator generiert statische Dateien zur Auslieferung an den Client, aber der Stack hat eine dynamische Komponente in Form von JavaScript, die auf dem Client läuft — diese Client-Komponente kann dann APIs verwenden, um dem Benutzer dynamische Funktionalität bereitzustellen.

Hugo

Hugo ist ein beliebter Static-Site-Generator. Es ist in Go geschrieben, und die Tatsache, dass Go eine kompilierte Programmiersprache ist, weist auf einige von Hugos Vor- und Nachteilen hin. Zum einen ist Hugo sehr schnell , was bedeutet, dass es sehr schnell statische Seiten generiert. Das hat natürlich keinen Einfluss darauf, wie schnell oder langsam die mit Hugo erstellten Seiten für den Endnutzer sind, aber für den Entwickler ist es durchaus wertvoll, dass Hugo auch große Seiten im Handumdrehen kompiliert.

Da Hugo jedoch in einer kompilierten Sprache geschrieben ist, ist eine Erweiterung schwierig . Bei einigen anderen Website-Generatoren können Sie Ihren eigenen Code – in Sprachen wie Ruby, Python oder JavaScript – in den Kompilierungsprozess einfügen. Um Hugo zu erweitern, müssten Sie Ihren Code zu Hugo selbst hinzufügen und neu kompilieren – andernfalls bleiben Sie bei den Vorlagenfunktionen hängen, die Hugo standardmäßig enthält.

Obwohl es eine große Vielfalt an Funktionen bietet, kann diese Tatsache einschränkend werden, wenn die Generierung Ihrer Seiten eine komplizierte Logik beinhaltet. Wie wir festgestellt haben, häufen sich bei einer Website, die ursprünglich auf einer dynamischen Plattform ausgeführt wurde, die Fälle, in denen Sie die Möglichkeit, Ihren benutzerdefinierten Code einzufügen, für selbstverständlich hielten, tendenziell zu häufen.

Unser Team unterhält eine Vielzahl von Websites, die sich auf unser Hauptprodukt, den Tower-Git-Client, beziehen, und wir haben kürzlich versucht, einige davon auf einen statischen Site-Generator zu verschieben. Eine unserer Sites, die „Learn“-Site, schien besonders gut für ein Pilotprojekt geeignet zu sein. Diese Seite enthält eine Vielzahl von kostenlosem Lernmaterial, darunter Videos, eBooks und FAQs zu Git, aber auch zu anderen technischen Themen.

Sein Inhalt ist größtenteils statischer Natur, und alle interaktiven Funktionen (wie Newsletter-Anmeldungen) wurden bereits von JavaScript unterstützt. Ende 2020 haben wir diese Seite von unserem bisherigen CMS auf Hugo umgestellt und läuft heute als statische Seite. Natürlich haben wir während dieses Prozesses viel über Hugo gelernt. Dieser Artikel ist eine Möglichkeit, einige der Dinge zu teilen, die wir gelernt haben.

Unser Beispiel

Da dieser Artikel aus unserer Arbeit an der Umstellung unserer Seiten auf Hugo entstanden ist, liegt es nahe, eine (sehr!) vereinfachte hypothetische Zielseite als Beispiel zusammenzustellen. Unser Hauptaugenmerk wird auf einer wiederverwendbaren sogenannten „Listen“-Vorlage liegen.

Kurz gesagt, Hugo verwendet eine Listenvorlage für jede Seite, die Unterseiten enthält. Zu Hugos Template-Hierarchie gehört noch mehr, aber Sie müssen nicht jedes mögliche Template implementieren. Eine einzelne Listenvorlage reicht weit. Es wird in jeder Situation verwendet, die eine Listenvorlage erfordert, in der keine spezialisiertere Vorlage verfügbar ist.

Mögliche Anwendungsfälle sind eine Homepage, ein Blog-Index oder eine Liste mit FAQs. Unsere wiederverwendbare Listenvorlage befindet sich in unserem Projekt unter layouts/_default/list.html . Auch hier sind die restlichen Dateien, die zum Kompilieren unseres Beispiels benötigt werden, auf GitHub verfügbar, wo Sie auch besser sehen können, wie die Teile zusammenpassen. Das GitHub-Repository enthält auch eine single.html Vorlage – wie der Name schon sagt, wird diese Vorlage für Seiten verwendet, die keine Unterseiten haben, sondern als eigenständige einzelne Inhalte fungieren.

Jetzt, wo wir die Voraussetzungen geschaffen und erklärt haben, was wir tun werden, fangen wir an!

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Der Kontext oder „Der Punkt“

Alles beginnt mit dem Punkt. In einer Hugo-Vorlage ist das Objekt . – „der Punkt“ – bezieht sich auf den aktuellen Kontext. Was bedeutet das? Jede in Hugo gerenderte Vorlage hat Zugriff auf eine Reihe von Daten – ihren Kontext . Dies wird zunächst auf ein Objekt gesetzt, das die gerade wiedergegebene Seite darstellt, einschließlich ihres Inhalts und einiger Metadaten. Der Kontext umfasst auch Site-weite Variablen wie Konfigurationsoptionen und Informationen über die aktuelle Umgebung. Sie greifen auf ein Feld wie den Titel der aktuellen Seite mit .Title und auf die verwendete Version von Hugo über .Hugo.Version – mit anderen Worten, Sie greifen auf Felder der . Struktur.

Wichtig ist, dass sich dieser Kontext ändern kann, wodurch ein Verweis wie `.Title` oben auf etwas anderes verweist oder sogar ungültig wird. Dies passiert zum Beispiel, wenn Sie mit range eine Art Sammlung durchlaufen oder wenn Sie Templates in Teil- und Basis-Templates aufteilen . Darauf gehen wir später im Detail ein!

Hugo verwendet das „Templates“-Paket von Go, wenn wir uns also in diesem Artikel auf Hugo-Templates beziehen, sprechen wir wirklich über Go-Templates. Hugo fügt viele Vorlagenfunktionen hinzu, die in Standard-Go-Vorlagen nicht verfügbar sind.

Meiner Meinung nach ist der Kontext und die Möglichkeit, es neu zu binden, eine der besten Eigenschaften von Hugo. Für mich macht es sehr viel Sinn, dass „der Punkt“ immer das Objekt darstellt, das an einem bestimmten Punkt der Hauptfokus meiner Vorlage ist, und es bei Bedarf neu binden, wenn ich weitermache. Natürlich ist es auch möglich, sich in ein Wirrwarr zu verwickeln, aber ich war bisher damit zufrieden, in dem Maße, dass ich schnell anfing, es in jedem anderen Static-Site-Generator zu vermissen, den ich mir angesehen habe.

Damit sind wir bereit, uns den bescheidenen Ausgangspunkt unseres Beispiels anzusehen – die Vorlage unten, die sich in unserem Projekt im Speicherort layouts/_default/list.html :

 <html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>

Der größte Teil der Vorlage besteht aus einer einfachen HTML-Struktur mit einem Stylesheet-Link, einem Menü für die Navigation und einigen zusätzlichen Elementen und Klassen, die für das Styling verwendet werden. Das Interessante ist zwischen den geschweiften Klammern , die Hugo signalisieren, einzugreifen und zu zaubern, indem sie alles zwischen den Klammern ersetzen, um einen Ausdruck zu bewerten und möglicherweise auch den Kontext zu manipulieren.

Wie Sie vielleicht erraten können, bezieht sich {{ .Title }} im Titel-Tag auf den Titel der aktuellen Seite, während sich {{ .Site.Title }} auf den Titel für die gesamte Site bezieht, der in der Hugo-Konfiguration festgelegt ist . Ein Tag wie {{ .Title }} weist Hugo einfach an, dieses Tag durch den Inhalt des Felds Title im aktuellen Kontext zu ersetzen.

Wir haben also auf einige Daten zugegriffen, die zu der Seite in einer Vorlage gehören. Woher kommen diese Daten? Das ist das Thema des folgenden Abschnitts.

Inhalt und Titelbild

Einige der im Kontext verfügbaren Variablen werden automatisch von Hugo bereitgestellt. Andere werden von uns definiert, hauptsächlich in Inhaltsdateien . Es gibt auch andere Datenquellen wie Konfigurationsdateien, Umgebungsvariablen, Datendateien und sogar APIs. In diesem Artikel konzentrieren wir uns auf Inhaltsdateien als Datenquelle.

Im Allgemeinen stellt eine einzelne Inhaltsdatei eine einzelne Seite dar. Eine typische Inhaltsdatei enthält den Hauptinhalt dieser Seite, aber auch Metadaten über die Seite, wie ihren Titel oder das Datum, an dem sie erstellt wurde. Hugo unterstützt mehrere Formate sowohl für den Hauptinhalt als auch für die Metadaten. In diesem Artikel gehen wir von der vielleicht häufigsten Kombination aus: Der Inhalt wird als Markdown in einer Datei bereitgestellt, die die Metadaten als YAML-Vordergrund enthält.

In der Praxis bedeutet dies, dass die Inhaltsdatei mit einem Abschnitt beginnt, der durch eine Linie mit drei Bindestrichen an jedem Ende begrenzt ist. Dieser Abschnitt stellt die Titelseite dar, und hier werden Metadaten mithilfe einer key: value -Syntax definiert (wie wir gleich sehen werden, unterstützt YAML auch ausgefeiltere Datenstrukturen). Auf die Titelei folgt der eigentliche Inhalt, spezifiziert mit der Auszeichnungssprache Markdown.

Lassen Sie uns die Dinge konkreter machen, indem wir uns ein Beispiel ansehen. Hier ist eine sehr einfache Inhaltsdatei mit einem Titelfeld und einem Inhaltsabsatz:

 --- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!

(Diese Datei befindet sich in unserem Projekt unter content/_index.md , wobei _index.md die Inhaltsdatei für eine Seite mit Unterseiten bezeichnet. Auch hier macht das GitHub-Repository deutlich, wo welche Datei abgelegt werden soll.)

Mit der Vorlage von früher gerendert, zusammen mit einigen Stilen und peripheren Dateien (alle auf GitHub gefunden), sieht das Ergebnis so aus:

Homepage des Tower-Git-Clients
(Große Vorschau)

Sie fragen sich vielleicht, ob die Feldnamen im Titel unserer Inhaltsdatei vorbestimmt sind oder ob wir beliebige Felder hinzufügen können. Die Antwort ist „beides“. Es gibt eine Liste mit vordefinierten Feldern, aber wir können auch jedes andere Feld hinzufügen, das uns einfällt. Auf diese Felder wird in der Vorlage jedoch etwas anders zugegriffen. Während auf ein vordefiniertes Feld wie title einfach als .Title , wird auf ein benutzerdefiniertes Feld wie author mit .Params.author .

(Eine schnelle Referenz zu den vordefinierten Feldern, zusammen mit Dingen wie Funktionen, Funktionsparametern und Seitenvariablen, finden Sie in unserem eigenen Hugo-Spickzettel!)

Die .Content Variable, die für den Zugriff auf den Hauptinhalt aus der Inhaltsdatei in Ihrer Vorlage verwendet wird, ist etwas Besonderes. Hugo hat eine „Shortcode“-Funktion , mit der Sie einige zusätzliche Tags in Ihren Markdown-Inhalten verwenden können. Sie können auch Ihre eigenen definieren. Leider funktionieren diese Shortcodes nur über die .Content Variable – während Sie alle anderen Daten durch einen Markdown-Filter laufen lassen können, werden die Shortcodes im Inhalt nicht verarbeitet.

Ein Hinweis hier zu undefinierten Variablen: Der Zugriff auf ein vordefiniertes Feld wie .Date funktioniert immer, auch wenn Sie es nicht gesetzt haben – in diesem Fall wird ein leerer Wert zurückgegeben. Der Zugriff auf ein undefiniertes benutzerdefiniertes Feld wie .Params.thisHasNotBeenSet funktioniert ebenfalls und gibt einen leeren Wert zurück. Der Zugriff auf ein nicht vordefiniertes Feld der obersten Ebene wie .thisDoesNotExist verhindert jedoch, dass die Site kompiliert wird.

Wie zuvor durch .Params.author sowie .Hugo.version und .Site.title , können verkettete Aufrufe verwendet werden, um auf ein Feld zuzugreifen, das in einer anderen Datenstruktur verschachtelt ist. Solche Strukturen können wir in unserer Titelei definieren. Sehen wir uns ein Beispiel an, in dem wir eine Karte oder ein Wörterbuch definieren und einige Eigenschaften für ein Banner auf der Seite in unserer Inhaltsdatei angeben. Hier ist der aktualisierte content/_index.md :

 --- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!

Lassen Sie uns nun ein Banner zu unserer Vorlage hinzufügen und auf die Bannerdaten mit .Params wie oben beschrieben verweisen:

 <html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>

So sieht unsere Seite jetzt aus:

Startseite des Tower-Git-Clients mit einem Banner mit der Aufschrift „Try Tower For Free“.
(Große Vorschau)

In Ordung! Im Moment greifen wir problemlos auf Felder des Standardkontexts zu. Wie bereits erwähnt, ist dieser Kontext jedoch nicht festgelegt, sondern kann sich ändern.

Werfen wir einen Blick darauf, wie das passieren könnte.

Ablaufsteuerung

Flusssteuerungsanweisungen sind ein wichtiger Bestandteil einer Vorlagensprache, die es Ihnen ermöglicht, je nach Wert von Variablen unterschiedliche Dinge zu tun, Daten zu durchlaufen und vieles mehr. Hugo-Vorlagen bieten den erwarteten Satz von Konstrukten, einschließlich if/else für bedingte Logik und range für Schleifen. Hier werden wir die Flusskontrolle in Hugo im Allgemeinen nicht behandeln (mehr dazu in der Dokumentation), sondern uns darauf konzentrieren, wie sich diese Anweisungen auf den Kontext auswirken. In diesem Fall sind die interessantesten Anweisungen with und range .

Beginnen wir mit with . Diese Anweisung prüft, ob ein Ausdruck einen „nicht leeren“ Wert hat, und wenn ja, bindet sie den Kontext neu, um auf den Wert dieses Ausdrucks zu verweisen . Ein end -Tag gibt den Punkt an, an dem der Einfluss der with -Anweisung aufhört und der Kontext wieder auf seinen vorherigen Zustand zurückgeführt wird. Die Hugo-Dokumentation definiert einen nicht leeren Wert als falsch, 0 und jedes beliebige Array, Slice, Map oder Zeichenfolge der Länge Null.

Derzeit listet unsere Listenvorlage überhaupt nicht viel auf. Es kann sinnvoll sein, dass eine Listenvorlage tatsächlich einige ihrer Unterseiten auf irgendeine Weise enthält. Dies gibt uns eine perfekte Gelegenheit für Beispiele unserer Flusssteuerungsanweisungen.

Vielleicht möchten wir einige hervorgehobene Inhalte oben auf unserer Seite anzeigen. Dies kann ein beliebiger Inhalt sein – zum Beispiel ein Blogbeitrag, ein Hilfeartikel oder ein Rezept. Nehmen wir an, unsere Tower-Beispielseite hat einige Seiten, die ihre Funktionen, Anwendungsfälle, eine Hilfeseite, eine Blogseite und eine „Lernplattform“-Seite hervorheben. Diese befinden sich alle im Verzeichnis content/ . Wir konfigurieren, welcher Inhalt enthalten sein soll, indem wir ein Feld in der Inhaltsdatei für unsere Homepage hinzufügen, content/_index.md . Auf die Seite wird über ihren Pfad verwiesen, wobei das Inhaltsverzeichnis als Stamm angenommen wird, etwa so:

 --- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...

Als nächstes muss unsere Listenvorlage geändert werden, um diesen Inhalt anzuzeigen. Hugo hat eine Vorlagenfunktion, .GetPage , die es uns ermöglicht, auf andere Seitenobjekte als das, das wir gerade rendern, zu verweisen. Erinnern Sie sich, wie der Kontext, . , war ursprünglich an ein Objekt gebunden, das die gerenderte Seite darstellt? Mit .GetPage und with können wir den Kontext vorübergehend an eine andere Seite binden und auf die Felder dieser Seite verweisen, wenn wir unsere vorgestellten Inhalte anzeigen:

 <nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>

Hier beziehen sich {{ .Title }} , {{ .Summary }} und {{ .Permalink }} zwischen den with und den end -Tags auf diese Felder in der vorgestellten Seite und nicht auf das Hauptfeld, das gerendert wird.

Lassen Sie uns zusätzlich zu einem vorgestellten Inhalt einige weitere Inhalte weiter unten auflisten. Genau wie die vorgestellten Inhalte werden die aufgelisteten Inhalte in content/_index.md definiert, der Inhaltsdatei für unsere Homepage. Wir fügen unserer Titelseite eine Liste von Inhaltspfaden wie folgt hinzu (in diesem Fall geben wir auch die Abschnittsüberschrift an):

 --- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---

Der Grund dafür, dass die Blog-Seite ein eigenes Verzeichnis und eine _index.md -Datei hat, ist, dass der Blog eigene Unterseiten haben wird – Blog-Einträge.

Um diese Liste in unserer Vorlage anzuzeigen, verwenden wir range . Es überrascht nicht, dass diese Anweisung eine Liste durchläuft, aber auch den Kontext erneut an jedes Element der Liste bindet. Dies ist sehr praktisch für unsere Inhaltsliste.

Beachten Sie, dass „listing“ aus der Sicht von Hugo nur einige Zeichenfolgen enthält. Bei jeder Iteration der „range“-Schleife wird der Kontext an einen dieser Strings gebunden . Um Zugriff auf das eigentliche Seitenobjekt zu erhalten, liefern wir seine Pfadzeichenfolge (jetzt den Wert von . ) als Argument an .GetPage . Dann verwenden wir die with -Anweisung erneut, um den Kontext erneut an das aufgelistete Seitenobjekt und nicht an seine Pfadzeichenfolge zu binden. Jetzt ist es einfach, den Inhalt jeder aufgelisteten Seite der Reihe nach anzuzeigen:

 <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>

So sieht die Seite zu diesem Zeitpunkt aus:

Startseite des Tower-Git-Clients mit vier vorgestellten Seiten und Bannern
(Große Vorschau)

Aber warten Sie, das obige Template enthält etwas Seltsames – anstatt .GetPage , rufen wir $.GetPage . Können Sie erraten, warum .GetPage nicht funktioniert?

Die Notation .GetPage gibt an, dass die GetPage Funktion eine Methode des aktuellen Kontexts ist. Tatsächlich gibt es im Standardkontext eine solche Methode, aber wir sind einfach weitergegangen und haben den Kontext geändert ! Wenn wir .GetPage , wird der Kontext an eine Zeichenfolge gebunden, die diese Methode nicht hat. Wie wir damit umgehen, ist das Thema des nächsten Abschnitts.

Der globale Kontext

Wie oben zu sehen ist, gibt es Situationen, in denen der Kontext geändert wurde, aber wir möchten immer noch auf den ursprünglichen Kontext zugreifen. Hier liegt es daran, dass wir eine im ursprünglichen Kontext vorhandene Methode aufrufen möchten – eine andere häufige Situation ist, wenn wir auf eine Eigenschaft der gerenderten Hauptseite zugreifen möchten. Kein Problem, es gibt eine einfache Möglichkeit, dies zu tun.

In einer Hugo-Vorlage bezieht sich $ , bekannt als globaler Kontext , auf den ursprünglichen Wert des Kontexts – den Kontext, wie er zu Beginn der Vorlagenverarbeitung war. Im vorherigen Abschnitt wurde es verwendet, um die .GetPage Methode aufzurufen, obwohl wir den Kontext an einen String zurückgebunden hatten. Jetzt werden wir es auch verwenden, um auf ein Feld der Seite zuzugreifen, die gerendert wird.

Am Anfang dieses Artikels habe ich erwähnt, dass unsere Listenvorlage wiederverwendbar ist. Bisher haben wir es nur für die Homepage verwendet und eine Inhaltsdatei gerendert, die sich unter content/_index.md . Im Beispiel-Repository gibt es eine weitere Inhaltsdatei, die mit dieser Vorlage gerendert wird: content/blog/_index.md . Dies ist eine Indexseite für den Blog, und genau wie die Homepage zeigt sie einen hervorgehobenen Inhalt und listet ein paar weitere auf – in diesem Fall Blogbeiträge.

Nehmen wir nun an, wir möchten aufgelistete Inhalte auf der Startseite etwas anders anzeigen – nicht genug, um eine separate Vorlage zu rechtfertigen, aber etwas, das wir mit einer bedingten Anweisung in der Vorlage selbst tun können. Als Beispiel zeigen wir den aufgelisteten Inhalt in einem zweispaltigen Raster an, im Gegensatz zu einer einspaltigen Liste, wenn wir feststellen, dass wir die Homepage rendern.

Hugo enthält eine Seitenmethode, .IsHome , die genau die Funktionalität bereitstellt, die wir benötigen. Wir werden die eigentliche Änderung in der Darstellung handhaben, indem wir den einzelnen Inhalten eine Klasse hinzufügen, wenn wir feststellen, dass wir uns auf der Homepage befinden, und unsere CSS-Datei den Rest erledigen lässt.

Natürlich könnten wir die Klasse stattdessen auch zum body-Element oder zu einem anderen enthaltenden Element hinzufügen, aber das würde den globalen Kontext nicht so gut demonstrieren. Bis wir den HTML-Code für den aufgelisteten Inhalt geschrieben haben, . bezieht sich auf die aufgelistete Seite , aber IsHome muss auf der Hauptseite aufgerufen werden, die gerendert wird. Der globale Kontext kommt uns zu Hilfe:

 <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>

Der Blog-Index sieht genauso aus wie unsere Homepage, wenn auch mit anderem Inhalt:

Startseite des Tower-Git-Clients mit vier vorgestellten Seiten und Bannern mit unterschiedlichen Inhalten
(Große Vorschau)

…aber unsere Homepage zeigt jetzt ihre vorgestellten Inhalte in einem Raster an:

Startseite des Tower-Git-Clients mit vier vorgestellten Seiten in zwei Zeilen mal zwei Spalten und nicht wie zuvor alle vier übereinander
(Große Vorschau)

Partielle Vorlagen

Beim Aufbau einer echten Website wird es schnell nützlich, Ihre Vorlagen in Teile aufzuteilen. Vielleicht möchten Sie einen bestimmten Teil einer Vorlage wiederverwenden oder einfach eine riesige, unhandliche Vorlage in zusammenhängende Teile aufteilen. Hierfür bieten sich die partiellen Vorlagen von Hugo an.

Aus Kontextperspektive ist es hier wichtig, dass wir beim Einbinden einer partiellen Vorlage explizit den Kontext übergeben, den wir ihr zur Verfügung stellen möchten. Eine gängige Praxis besteht darin, den Kontext so zu übergeben, wie er ist, wenn der Partial enthalten ist, wie hier: {{ partial "my/partial.html" . }} {{ partial "my/partial.html" . }} . Wenn sich der Punkt hier auf die Seite bezieht, die gerendert wird, wird dies an den Teil weitergegeben; Wenn der Kontext auf etwas anderes zurückgeführt wurde, wird das weitergegeben.

Natürlich können Sie den Kontext in partiellen Templates genauso neu binden wie in normalen. In diesem Fall bezieht sich der globale Kontext, $ , auf den ursprünglichen Kontext, der an das Teil übergeben wird, nicht auf die Hauptseite, die gerendert wird (es sei denn, dies wurde übergeben).

Wenn wir möchten, dass eine partielle Vorlage Zugriff auf bestimmte Daten hat, könnten wir auf Probleme stoßen, wenn wir nur diese an die partielle übergeben. Erinnern Sie sich an unser früheres Problem mit dem Zugriff auf Seitenmethoden nach dem erneuten Binden des Kontexts? Dasselbe gilt für partials , aber in diesem Fall kann uns der globale Kontext nicht helfen – wenn wir beispielsweise einen String an ein partielles Template übergeben haben, verweist der globale Kontext im Partial auf diesen String, und wir haben gewonnen Methoden, die im Seitenkontext definiert sind, können nicht aufgerufen werden.

Die Lösung für dieses Problem liegt darin , mehr als ein Datenelement zu übergeben, wenn das Partial eingeschlossen wird. Wir dürfen jedoch nur ein Argument für den Teilaufruf angeben. Wir können dieses Argument jedoch zu einem zusammengesetzten Datentyp machen, üblicherweise einer Karte (in anderen Programmiersprachen als Wörterbuch oder Hash bekannt).

In dieser Map können wir beispielsweise einen Seitenschlüssel auf das aktuelle Page setzen, zusammen mit anderen Schlüsseln für benutzerdefinierte Daten, die übergeben werden sollen. Das Seitenobjekt ist dann als .Page im Partial und im anderen verfügbar auf Werte der Karte wird ähnlich zugegriffen. Eine Map wird mithilfe der dict -Vorlagenfunktion erstellt, die eine gerade Anzahl von Argumenten akzeptiert, die abwechselnd als Schlüssel, als Wert, als Schlüssel, als Wert usw. interpretiert werden.

Lassen Sie uns in unserer Beispielvorlage den Code für unsere vorgestellten und aufgelisteten Inhalte in Teile verschieben. Für den vorgestellten Inhalt reicht es aus, das Objekt der vorgestellten Seite zu übergeben. Der aufgelistete Inhalt benötigt jedoch zusätzlich zu dem bestimmten aufgelisteten Inhalt, der gerendert wird, Zugriff auf die .IsHome Methode. Wie bereits erwähnt, ist .IsHome zwar auch im Seitenobjekt für die aufgelistete Seite verfügbar, aber das gibt uns nicht die richtige Antwort – wir wollen wissen, ob die gerenderte Hauptseite die Startseite ist.

Wir könnten stattdessen einen booleschen Satz an das Ergebnis des Aufrufs von .IsHome , aber vielleicht benötigt der Partial in Zukunft Zugriff auf andere Seitenmethoden – lassen Sie uns mit der Übergabe des Hauptseitenobjekts sowie des aufgelisteten Seitenobjekts fortfahren. In unserem Beispiel befindet sich die Hauptseite in $ und die aufgelistete Seite in . . In der Map, die an das listed Partial übergeben wird, erhält der Schlüssel Page den Wert $ , während der Schlüssel „Listed“ den Wert erhält . . Dies ist die aktualisierte Hauptvorlage:

 <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>

Der Inhalt unseres „vorgestellten“ Teils ändert sich nicht im Vergleich zu der Zeit, als er Teil der Listenvorlage war:

 <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>

Unser Teil für aufgelisteten Inhalt spiegelt jedoch die Tatsache wider, dass das ursprüngliche Seitenobjekt jetzt in .Page gefunden wird, während der aufgelistete Inhalt in .Listed gefunden wird:

 <article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>

Hugo bietet auch Basisvorlagenfunktionen, mit denen Sie eine gemeinsame Basisvorlage erweitern können, anstatt Untervorlagen einzufügen. In diesem Fall funktioniert der Kontext ähnlich: Wenn Sie eine Basisvorlage erweitern, stellen Sie die Daten bereit, die den ursprünglichen Kontext in dieser Vorlage darstellen.

Benutzerdefinierte Variablen

Es ist auch möglich, Ihre eigenen benutzerdefinierten Variablen in einer Hugo-Vorlage zuzuweisen und neu zuzuweisen. Diese sind in der Vorlage verfügbar, in der sie deklariert sind, werden aber nicht in Partials oder Basisvorlagen aufgenommen, es sei denn, wir geben sie ausdrücklich weiter. Eine benutzerdefinierte Variable , die innerhalb eines „Blocks“ deklariert wird, wie diejenige, die durch eine if -Anweisung angegeben wird, ist nur innerhalb dieses Blocks verfügbar – wenn wir außerhalb des Blocks darauf verweisen möchten, müssen wir sie außerhalb des Blocks deklarieren und dann innerhalb des ändern nach Bedarf sperren.

Benutzerdefinierte Variablen haben Namen, denen ein Dollarzeichen ( $ ) vorangestellt ist. Um eine Variable zu deklarieren und ihr gleichzeitig einen Wert zuzuweisen, verwenden Sie den Operator := . Nachfolgende Zuweisungen an die Variable verwenden den Operator = (ohne Doppelpunkt). Einer Variablen kann nicht zugewiesen werden, bevor sie deklariert ist, und sie kann nicht deklariert werden, ohne ihr einen Wert zu geben.

Ein Anwendungsfall für benutzerdefinierte Variablen ist das Vereinfachen langer Funktionsaufrufe durch Zuweisen eines Zwischenergebnisses zu einer entsprechend benannten Variablen. Beispielsweise könnten wir das Featured-Page-Objekt einer Variablen namens $featured zuweisen und diese Variable dann an die with -Anweisung übergeben. Wir könnten die Daten, die dem „aufgelisteten“ Partial zugeführt werden sollen, auch in eine Variable packen und diese dem Partial-Aufruf übergeben.

So würde unsere Vorlage mit diesen Änderungen aussehen:

 <section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>

Basierend auf meiner Erfahrung mit Hugo würde ich empfehlen , benutzerdefinierte Variablen großzügig zu verwenden , sobald Sie versuchen, eine kompliziertere Logik in einer Vorlage zu implementieren. Während es natürlich ist, zu versuchen, Ihren Code kurz zu halten, kann dies die Dinge leicht weniger klar machen, als sie sein könnten, und Sie und andere verwirren.

Verwenden Sie stattdessen aussagekräftig benannte Variablen für jeden Schritt und machen Sie sich keine Gedanken darüber, zwei Zeilen (oder drei oder vier usw.) zu verwenden, wo eine ausreichen würde.

.Kratzen

Lassen Sie uns abschließend den .Scratch -Mechanismus behandeln. In früheren Versionen von Hugo konnten benutzerdefinierte Variablen nur einmal zugewiesen werden; Es war nicht möglich, eine benutzerdefinierte Variable neu zu definieren. Heutzutage können benutzerdefinierte Variablen neu definiert werden, was .Scratch weniger wichtig macht, obwohl es immer noch seinen Nutzen hat.

Kurz gesagt, .Scratch ist ein Scratch-Bereich, in dem Sie Ihre eigenen Variablen wie benutzerdefinierte Variablen festlegen und ändern können . Im Gegensatz zu benutzerdefinierten Variablen gehört .Scratch zum Seitenkontext. Wenn Sie diesen Kontext also beispielsweise an einen Partial übergeben, werden die Scratch-Variablen automatisch mitgebracht.

Sie können Variablen auf .Scratch setzen und abrufen, indem Sie die Methoden Set und Get aufrufen. Es gibt noch mehr Methoden als diese, zum Beispiel zum Setzen und Aktualisieren zusammengesetzter Datentypen, aber diese beiden reichen für unsere Zwecke hier aus. Set benötigt zwei Parameter : den Schlüssel und den Wert für die Daten, die Sie einstellen möchten. Get benötigt nur einen: den Schlüssel für die Daten, die Sie abrufen möchten.

Früher haben wir dict verwendet, um eine Kartendatenstruktur zu erstellen, um mehrere Datenteile an einen Partial zu übergeben. Dies wurde gemacht, damit der Partial für eine aufgelistete Seite Zugriff sowohl auf den ursprünglichen Seitenkontext als auch auf das bestimmte aufgelistete Seitenobjekt haben würde. Die Verwendung .Scratch ist nicht unbedingt ein besserer oder schlechterer Weg, dies zu tun – was besser ist, kann von der Situation abhängen.

Mal sehen, wie unsere Listenvorlage aussehen würde, .Scratch anstelle von dict verwendet wird, um Daten an das Teil zu übergeben. Wir rufen $.Scratch.Get (wieder unter Verwendung des globalen Kontexts), um die Scratch-Variable „listed“ auf zu setzen . – in diesem Fall das aufgelistete Seitenobjekt. Dann übergeben wir nur das Seitenobjekt $ an das Partial. Die Scratch-Variablen folgen automatisch.

 <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>

Dies würde auch einige Änderungen am Teil der Datei „ listed.html “ erfordern – der ursprüngliche Seitenkontext ist jetzt als „der Punkt“ verfügbar, während die aufgelistete Seite aus dem .Scratch Objekt abgerufen wird. Wir verwenden eine benutzerdefinierte Variable, um den Zugriff auf die aufgelistete Seite zu vereinfachen:

 <article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>

Ein Argument dafür, Dinge auf diese Weise zu tun, ist Konsistenz. Mit .Scratch können Sie es sich zur Gewohnheit machen, das aktuelle Seitenobjekt immer an Partials zu übergeben und zusätzliche Daten als Scratch-Variablen hinzuzufügen. Dann weißt du, wann immer du deine Partials schreibst oder bearbeitest, dass . ist ein Seitenobjekt. Natürlich kann man sich auch mit einer übergebenen Map eine Konvention aufstellen: zB immer das Page-Objekt als .Page .

Fazit

Wenn es um Kontext und Daten geht, bringt ein statischer Site-Generator sowohl Vorteile als auch Einschränkungen mit sich. Einerseits kann eine Operation, die zu ineffizient ist, wenn sie bei jedem Seitenbesuch ausgeführt wird, vollkommen in Ordnung sein, wenn sie nur einmal ausgeführt wird, während die Seite kompiliert wird. Andererseits mag es Sie überraschen, wie oft es nützlich wäre, Zugriff auf einen Teil der Netzwerkanfrage zu haben, sogar auf einer überwiegend statischen Site.

Um beispielsweise Abfragezeichenfolgenparameter auf einer statischen Website zu verarbeiten, müssten Sie auf JavaScript oder eine proprietäre Lösung wie die Weiterleitungen von Netlify zurückgreifen. Der Punkt hier ist, dass der Sprung von einer dynamischen zu einer statischen Seite zwar theoretisch einfach ist, aber eine Änderung der Denkweise erfordert. Am Anfang ist es leicht, in alte Gewohnheiten zurückzufallen, aber Übung macht den Meister.

Damit schließen wir unseren Blick auf das Datenmanagement im Hugo Static Site Generator. Even though we focused only on a narrow sector of its functionality, there are certainly things we didn't cover that could have been included. Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.

Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.

Weiterführende Lektüre

If you're looking for more information on Hugo, here are some nice resources:

  • The official Hugo documentation is always a good place to start!
  • A great series of in-depth posts on Hugo on Regis Philibert's blog.