Kontekst i zmienne w generatorze statycznych witryn Hugo
Opublikowany: 2022-03-10W tym artykule przyjrzymy się bliżej działaniu kontekstu w generatorze witryn statycznych Hugo. Zbadamy, w jaki sposób dane przepływają z treści do szablonów, jak określone konstrukcje zmieniają dostępne dane i jak możemy przekazać te dane do częściowych i szablonów podstawowych.
Ten artykuł nie jest wprowadzeniem do Hugo . Prawdopodobnie najwięcej z tego skorzystasz, jeśli masz jakieś doświadczenie z Hugo, ponieważ nie będziemy omawiać wszystkich koncepcji od zera, ale raczej skupimy się na głównym temacie, jakim jest kontekst i zmienne. Jeśli jednak będziesz odwoływać się do dokumentacji Hugo w całym tekście, możesz być w stanie podążać za nią nawet bez wcześniejszego doświadczenia!
Przestudiujemy różne koncepcje, tworząc przykładową stronę. Nie każdy plik wymagany dla przykładowej witryny zostanie szczegółowo omówiony, ale kompletny projekt jest dostępny w serwisie GitHub. Jeśli chcesz zrozumieć, jak poszczególne elementy do siebie pasują, to dobry punkt wyjścia. Należy również pamiętać, że nie będziemy omawiać, jak skonfigurować witrynę Hugo ani uruchomić serwer deweloperski — instrukcje dotyczące uruchomienia przykładu znajdują się w repozytorium.
Co to jest statyczny generator witryn?
Jeśli koncepcja generatorów statycznych witryn jest dla Ciebie nowa, oto krótkie wprowadzenie! Generatory witryn statycznych najlepiej opisać porównując je z witrynami dynamicznymi. Witryna dynamiczna, taka jak CMS, zazwyczaj tworzy stronę od podstaw przy każdej wizycie, być może pobierając dane z bazy danych i łącząc w tym celu różne szablony. W praktyce zastosowanie buforowania oznacza, że strona nie jest dość często regenerowana, ale na potrzeby tego porównania możemy tak o tym pomyśleć. Witryna dynamiczna doskonale nadaje się do zawartości dynamicznej : zawartości, która często się zmienia, zawartości prezentowanej w wielu różnych konfiguracjach w zależności od danych wejściowych oraz zawartości, którą odwiedzający witrynę może manipulować.
W przeciwieństwie do tego, wiele witryn rzadko się zmienia i przyjmuje niewielki wkład od odwiedzających. Przykładami takich witryn może być sekcja „pomoc” do aplikacji, lista artykułów lub e-book. W takim przypadku bardziej sensowne jest składanie ostatecznych stron raz , gdy zmienia się treść, a następnie udostępnianie tych samych stron wszystkim odwiedzającym, aż do ponownej zmiany treści.
Witryny dynamiczne mają większą elastyczność, ale stawiają większe wymagania na serwerze, na którym działają. Dystrybucja geograficzna może być również trudna, zwłaszcza jeśli zaangażowane są bazy danych. Generatory witryn statycznych mogą być hostowane na dowolnym serwerze zdolnym do dostarczania plików statycznych i są łatwe w dystrybucji.
Powszechnym obecnie rozwiązaniem, które łączy te podejścia, jest JAMstack. „JAM” oznacza JavaScript, interfejsy API i znaczniki i opisuje bloki konstrukcyjne aplikacji JAMstack: generator statycznych witryn generuje statyczne pliki do dostarczenia do klienta, ale stos zawiera dynamiczny komponent w postaci JavaScript działającego na kliencie — ten składnik klienta może następnie korzystać z interfejsów API, aby zapewnić użytkownikowi dynamiczną funkcjonalność.
Hugo
Hugo to popularny generator stron statycznych. Jest napisany w Go, a fakt, że Go jest skompilowanym językiem programowania, wskazuje na niektóre zalety i wady Hugos. Po pierwsze, Hugo jest bardzo szybki , co oznacza, że bardzo szybko generuje statyczne witryny. Oczywiście nie ma to wpływu na to, jak szybkie lub wolne są strony tworzone za pomocą Hugo dla użytkownika końcowego, ale dla dewelopera fakt, że Hugo kompiluje nawet duże strony w mgnieniu oka, jest całkiem cenny.
Ponieważ jednak Hugo jest napisany w języku kompilowanym, jego rozszerzenie jest trudne . Niektóre inne generatory witryn umożliwiają wstawienie własnego kodu — w językach takich jak Ruby, Python lub JavaScript — w procesie kompilacji. Aby rozszerzyć Hugo, musiałbyś dodać swój kod do samego Hugo i ponownie go skompilować — w przeciwnym razie utkniesz z funkcjami szablonów, które Hugo jest dostarczany po wyjęciu z pudełka.
Chociaż zapewnia szeroką gamę funkcji, fakt ten może stać się ograniczający, jeśli generowanie stron wymaga skomplikowanej logiki. Jak stwierdziliśmy, mając witrynę pierwotnie opracowaną działającą na dynamicznej platformie, przypadki, w których możliwość wpisania niestandardowego kodu zostały uznane za oczywiste, często się nawarstwiają.
Nasz zespół utrzymuje różne witryny internetowe związane z naszym głównym produktem, klientem Tower Git, a ostatnio przyjrzeliśmy się przeniesieniu niektórych z nich do statycznego generatora witryn. Jedna z naszych stron, strona „Ucz się”, wyglądała na szczególnie dobrze dopasowaną do projektu pilotażowego. Ta witryna zawiera różnorodne bezpłatne materiały szkoleniowe, w tym filmy, eBooki i często zadawane pytania dotyczące Git, ale także inne tematy techniczne.
Jego zawartość ma w dużej mierze charakter statyczny, a wszelkie dostępne funkcje interaktywne (takie jak subskrypcja biuletynu) były już obsługiwane przez JavaScript. Pod koniec 2020 r. przekonwertowaliśmy tę witrynę z naszego poprzedniego systemu CMS na Hugo , a dziś działa jako witryna statyczna. Oczywiście podczas tego procesu wiele się dowiedzieliśmy o Hugo. Ten artykuł jest sposobem na podzielenie się niektórymi rzeczami, których się nauczyliśmy.
Nasz przykład
Ponieważ ten artykuł wyrósł z naszej pracy nad konwersją naszych stron na Hugo, wydaje się naturalne, że zestawiamy (bardzo!) uproszczoną hipotetyczną stronę docelową jako przykład. Naszym głównym celem będzie szablon wielokrotnego użytku, tzw. „lista”.
Krótko mówiąc, Hugo użyje szablonu listy dla każdej strony zawierającej podstrony. Hierarchia szablonów Hugos to coś więcej, ale nie musisz wdrażać każdego możliwego szablonu. Pojedynczy szablon listy to długa droga. Będzie używany w każdej sytuacji wymagającej szablonu listy, gdy nie jest dostępny żaden bardziej wyspecjalizowany szablon.
Potencjalne przypadki użycia obejmują stronę główną, indeks blogów lub listę często zadawanych pytań. Nasz szablon listy wielokrotnego użytku będzie znajdował się w layouts/_default/list.html
w naszym projekcie. Ponownie, pozostałe pliki potrzebne do skompilowania naszego przykładu są dostępne w serwisie GitHub, gdzie można również lepiej przyjrzeć się, jak poszczególne elementy do siebie pasują. Repozytorium GitHub zawiera również szablon single.html
— jak sama nazwa wskazuje, szablon ten jest używany dla stron, które nie mają podstron, ale działają jako pojedyncze fragmenty treści.
Teraz, gdy już przygotowaliśmy scenę i wyjaśniliśmy, co będziemy robić, zacznijmy!
Kontekst lub „kropka”
Wszystko zaczyna się od kropki. W szablonie Hugo obiekt .
— „kropka” — odnosi się do aktualnego kontekstu. Co to znaczy? Każdy szablon renderowany w Hugo ma dostęp do zestawu danych — jego kontekstu . Jest to początkowo ustawione na obiekt reprezentujący aktualnie renderowaną stronę, w tym jej zawartość i niektóre metadane. Kontekst obejmuje również zmienne obejmujące całą witrynę, takie jak opcje konfiguracji i informacje o bieżącym środowisku. Uzyskasz dostęp do pola takiego jak tytuł bieżącej strony za pomocą .Title
i wersji Hugo używanej przez .Hugo.Version
— innymi słowy, uzyskujesz dostęp do pól .
Struktura.
Co ważne, ten kontekst może się zmienić, powodując, że odwołanie takie jak `.Title` powyżej wskazuje na coś innego, a nawet unieważnia. Dzieje się tak, na przykład, gdy zapętlisz się nad pewnego rodzaju kolekcją za pomocą range
, lub gdy dzielisz szablony na podszablony i szablony podstawowe . Przyjrzymy się temu szczegółowo później!
Hugo używa pakietu „szablonów” Go, więc kiedy w tym artykule odwołujemy się do szablonów Hugo, tak naprawdę mówimy o szablonach Go. Hugo dodaje wiele funkcji szablonów niedostępnych w standardowych szablonach Go.
Moim zdaniem kontekst i możliwość ponownego powiązania to jedna z najlepszych cech Hugosa. Dla mnie bardzo sensowne jest, aby zawsze „kropka” przedstawiała dowolny obiekt, na którym skupiam się w danym momencie w moim szablonie, zmieniając go w miarę potrzeb. Oczywiście można też wpaść w splątany bałagan, ale do tej pory byłem z tego zadowolony, do tego stopnia, że szybko zacząłem go brakować w jakimkolwiek innym generatorze stron statycznych, który oglądałem.
Dzięki temu jesteśmy gotowi, aby spojrzeć na skromny punkt wyjścia naszego przykładu — poniższy szablon, znajdujący się w lokalizacji layouts/_default/list.html
w naszym projekcie:
<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>
Większość szablonu składa się z podstawowej struktury HTML, z linkiem do arkusza stylów, menu do nawigacji oraz kilkoma dodatkowymi elementami i klasami używanymi do stylizacji. Interesujące rzeczy znajdują się między nawiasami klamrowymi , które sygnalizują Hugo, aby wkroczył i zrobił swoją magię, zastępując wszystko, co jest między nawiasami, wynikiem oceny jakiegoś wyrażenia i potencjalnie manipulacji kontekstem.
Jak można się domyślić, {{ .Title }}
w tagu title odnosi się do tytułu bieżącej strony, natomiast {{ .Site.Title }}
odnosi się do tytułu całej witryny, ustawionego w konfiguracji Hugo . Tag taki jak {{ .Title }}
po prostu mówi Hugo, aby zastąpił ten tag zawartością pola Title
w bieżącym kontekście.
Tak więc uzyskaliśmy dostęp do niektórych danych należących do strony w szablonie. Skąd pochodzą te dane? To jest temat następnej sekcji.
Treść i sprawa frontowa
Niektóre zmienne dostępne w kontekście są automatycznie dostarczane przez Hugo. Inne są przez nas definiowane, głównie w plikach treści . Istnieją również inne źródła danych, takie jak pliki konfiguracyjne, zmienne środowiskowe, pliki danych, a nawet interfejsy API. W tym artykule skupimy się na plikach treści jako źródle danych.
Ogólnie rzecz biorąc, jeden plik treści reprezentuje jedną stronę. Typowy plik treści zawiera główną treść tej strony, ale także metadane dotyczące strony, takie jak jej tytuł lub data utworzenia. Hugo obsługuje kilka formatów zarówno głównej treści, jak i metadanych. W tym artykule omówimy prawdopodobnie najczęstszą kombinację: zawartość jest dostarczana jako Markdown w pliku zawierającym metadane jako front YAML.
W praktyce oznacza to, że plik treści zaczyna się od sekcji oddzielonej linią zawierającą trzy myślniki na każdym końcu. Ta sekcja stanowi sprawę frontu , a tutaj metadane są definiowane za pomocą key: value
(jak zobaczymy wkrótce, YAML obsługuje również bardziej rozbudowane struktury danych). Po stronie frontowej następuje rzeczywista treść, określona przy użyciu języka znaczników Markdown.
Zróbmy coś bardziej konkretnego, patrząc na przykład. Oto bardzo prosty plik treści z jednym polem z przodu i jednym akapitem treści:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Ten plik znajduje się pod adresem content/_index.md
w naszym projekcie, gdzie _index.md
oznacza plik treści dla strony, która ma podstrony. Ponownie, repozytorium GitHub jasno pokazuje, dokąd ma trafić plik.)
Renderowany przy użyciu wcześniejszego szablonu, wraz z niektórymi stylami i plikami peryferyjnymi (wszystkie znalezione na GitHub), wynik wygląda tak:
Możesz się zastanawiać, czy nazwy pól w przedniej części naszego pliku treści są z góry określone, czy też możemy dodać dowolne pole, które nam się podoba. Odpowiedź brzmi „oba”. Istnieje lista predefiniowanych pól, ale możemy również dodać dowolne inne pole, jakie możemy wymyślić. Jednak dostęp do tych pól w szablonie jest nieco inny. Podczas gdy predefiniowane pole, takie jak title
, jest dostępne po prostu jako .Title
, niestandardowe pole, takie jak author
, jest dostępne za pomocą .Params.author
.
(Aby uzyskać szybki przegląd predefiniowanych pól, wraz z takimi rzeczami, jak funkcje, parametry funkcji i zmienne strony, zobacz naszą własną ściągawkę Hugo!)
Zmienna .Content
, używana do uzyskiwania dostępu do głównej zawartości z pliku zawartości w szablonie, jest wyjątkowa. Hugo ma funkcję „skróconego kodu”, dzięki której możesz użyć dodatkowych tagów w treści Markdown. Możesz także zdefiniować własne. Niestety, te skróty będą działać tylko przez zmienną .Content
— podczas gdy możesz uruchomić dowolny inny fragment danych przez filtr Markdown, nie obsłuży to skrótów w treści.
Uwaga na temat niezdefiniowanych zmiennych: dostęp do wstępnie zdefiniowanego pola, takiego jak .Date
, zawsze działa, nawet jeśli go nie ustawiłeś — w tym przypadku zostanie zwrócona pusta wartość. Dostęp do niezdefiniowanego pola niestandardowego, takiego jak .Params.thisHasNotBeenSet
, również działa, zwracając pustą wartość. Jednak uzyskanie dostępu do niezdefiniowanego wstępnie pola najwyższego poziomu, takiego jak .thisDoesNotExist
, uniemożliwi kompilację witryny.
Jak wskazano wcześniej przez .Params.author
, a także .Hugo.version
i .Site.title
, wywołania łańcuchowe mogą być używane do uzyskiwania dostępu do pola zagnieżdżonego w innej strukturze danych. Możemy zdefiniować takie struktury w naszej frontowej materii. Spójrzmy na przykład, w którym definiujemy mapę lub słownik, określając niektóre właściwości banera na stronie w naszym pliku treści. Oto zaktualizowana 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!
Teraz dodajmy baner do naszego szablonu, odwołując się do danych banera za pomocą .Params
w sposób opisany powyżej:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Oto jak teraz wygląda nasza strona:
W porządku! W tej chwili bez żadnych problemów uzyskujemy dostęp do pól kontekstu domyślnego. Jednak, jak wspomniano wcześniej, ten kontekst nie jest stały, ale może się zmienić.
Zobaczmy, jak to się może stać.
Kontrola przepływu
Instrukcje kontroli przepływu są ważną częścią języka szablonów, umożliwiając wykonywanie różnych czynności w zależności od wartości zmiennych, pętlę danych i nie tylko. Szablony Hugo zapewniają oczekiwany zestaw konstrukcji, w tym if/else
dla logiki warunkowej i range
dla pętli. Tutaj nie będziemy omawiać ogólnie kontroli przepływu w Hugo (więcej informacji na ten temat można znaleźć w dokumentacji), ale skoncentrujemy się na tym, jak te stwierdzenia wpływają na kontekst. W tym przypadku najciekawsze są zdania with
i range
.
Zacznijmy with
. Ta instrukcja sprawdza, czy jakieś wyrażenie ma wartość „niepustą”, a jeśli tak, ponownie wiąże kontekst, aby odwoływać się do wartości tego wyrażenia . Znacznik end
wskazuje punkt, w którym kończy się wpływ instrukcji with
, a kontekst powraca do tego, co było wcześniej. Dokumentacja Hugo definiuje niepustą wartość jako false, 0 oraz dowolną tablicę, wycinek, mapę lub ciąg znaków o zerowej długości.
Obecnie w naszym szablonie listy w ogóle niewiele się dzieje. Może mieć sens, aby szablon listy zawierał w jakiś sposób niektóre z jego podstron . Daje nam to doskonałą okazję do przedstawienia przykładów naszych instrukcji kontroli przepływu.
Być może chcemy wyświetlić niektóre polecane treści na górze naszej strony. Może to być dowolna treść — na przykład post na blogu, artykuł pomocy lub przepis. W tej chwili załóżmy, że nasza przykładowa witryna Tower zawiera strony przedstawiające jej funkcje, przypadki użycia, stronę pomocy, stronę bloga i stronę „platformy edukacyjnej”. Wszystkie znajdują się w katalogu content/
. Konfigurujemy, który fragment treści ma zostać wyróżniony, dodając pole w pliku treści dla naszej strony głównej, content/_index.md
. Strona jest określana ścieżką, zakładając, że katalog zawartości to root, na przykład:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
Następnie nasz szablon listy musi zostać zmodyfikowany, aby wyświetlić ten fragment treści. Hugo ma funkcję szablonu .GetPage
, która pozwoli nam odnosić się do obiektów strony innych niż ten, który aktualnie renderujemy. Przypomnij sobie kontekst, .
, był początkowo powiązany z obiektem reprezentującym renderowaną stronę? Używając .GetPage
with
, możemy tymczasowo ponownie powiązać kontekst z inną stroną, odwołując się do pól tej strony podczas wyświetlania naszych polecanych treści:
<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>
Tutaj {{ .Title }}
, {{ .Summary }}
i {{ .Permalink }}
między tagami with
i end
odnoszą się do tych pól na polecanej stronie , a nie do głównego, który jest renderowany.
Oprócz posiadania polecanego fragmentu treści, wymieńmy jeszcze kilka fragmentów treści w dalszej części. Podobnie jak polecana treść, wymienione fragmenty treści zostaną zdefiniowane w content/_index.md
, pliku treści dla naszej strony głównej. Dodamy listę ścieżek treści do naszej sprawy frontowej w ten sposób (w tym przypadku również określając nagłówek sekcji):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
Powodem, dla którego strona bloga ma swój własny katalog i plik _index.md
, jest to, że blog będzie miał własne podstrony — posty na blogu.
Aby wyświetlić tę listę w naszym szablonie, użyjemy range
. Nic dziwnego, że to stwierdzenie zapętli się na liście, ale spowoduje również ponowne powiązanie kontekstu z każdym elementem listy po kolei. Jest to bardzo wygodne dla naszej listy treści.
Zauważ, że z perspektywy Hugo „lista” zawiera tylko niektóre ciągi. Dla każdej iteracji pętli „zakres” kontekst zostanie powiązany z jednym z tych ciągów . Aby uzyskać dostęp do rzeczywistego obiektu strony, dostarczamy jego ciąg ścieżki (teraz wartość .
) jako argument do .GetPage
. Następnie ponownie użyjemy instrukcji with
, aby ponownie powiązać kontekst z wymienionym obiektem strony, a nie jego ciągiem ścieżki. Teraz można łatwo wyświetlić kolejno zawartość każdej wymienionej strony:
<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>
Oto jak strona wygląda w tym momencie:
Ale poczekaj, w powyższym szablonie jest coś dziwnego — zamiast wywoływania .GetPage
, wywołujemy $.GetPage
. Czy wiesz, dlaczego .GetPage
nie działa?
Notacja .GetPage
wskazuje, że funkcja GetPage
jest metodą bieżącego kontekstu. Rzeczywiście, w domyślnym kontekście istnieje taka metoda, ale po prostu poszliśmy dalej i zmieniliśmy kontekst ! Kiedy wywołujemy .GetPage
, kontekst jest powiązany z ciągiem, który nie ma tej metody. Sposób, w jaki to obejdziemy, jest tematem następnej sekcji.
Kontekst globalny
Jak widać powyżej, są sytuacje, w których kontekst został zmieniony, ale nadal chcielibyśmy uzyskać dostęp do oryginalnego kontekstu. Tutaj dzieje się tak, ponieważ chcemy wywołać metodę istniejącą w oryginalnym kontekście — inną powszechną sytuacją jest, gdy chcemy uzyskać dostęp do jakiejś właściwości renderowanej strony głównej. Nie ma problemu, jest na to prosty sposób.
W szablonie Hugo $
, znany jako kontekst globalny , odnosi się do oryginalnej wartości kontekstu — kontekstu, jaki był w momencie rozpoczęcia przetwarzania szablonu. W poprzedniej sekcji został użyty do wywołania metody .GetPage
, mimo że odwróciliśmy kontekst do ciągu. Teraz użyjemy go również do uzyskania dostępu do pola renderowanej strony.
Na początku tego artykułu wspomniałem, że nasz szablon listy jest wielokrotnego użytku. Do tej pory używaliśmy go tylko na stronie głównej, renderując plik treści znajdujący się pod adresem content/_index.md
. W przykładowym repozytorium znajduje się inny plik treści, który zostanie wyrenderowany przy użyciu tego szablonu: content/blog/_index.md
. Jest to strona indeksu bloga i podobnie jak strona główna, zawiera polecaną treść i zawiera listę kilku innych — w tym przypadku postów na blogu.
Załóżmy teraz, że chcemy nieco inaczej wyświetlić listę treści na stronie głównej — nie wystarczy, aby uzasadnić osobny szablon, ale coś, co możemy zrobić za pomocą instrukcji warunkowej w samym szablonie. Na przykład, jeśli wykryjemy, że renderujemy stronę główną, wyświetlimy wymienioną zawartość w siatce dwukolumnowej, w przeciwieństwie do listy jednokolumnowej.
Hugo jest wyposażony w metodę stronicowania .IsHome
, która zapewnia dokładnie taką funkcjonalność, jakiej potrzebujemy. Zajmiemy się faktyczną zmianą prezentacji, dodając klasę do poszczególnych fragmentów treści, gdy znajdziemy się na stronie głównej, pozwalając naszemu plikowi CSS zrobić resztę.
Moglibyśmy oczywiście dodać klasę do elementu body lub do elementu zawierającego zamiast tego, ale to nie umożliwiłoby tak dobrej demonstracji kontekstu globalnego. Zanim napiszemy kod HTML wymienionego fragmentu treści, .
odnosi się do wymienionej strony , ale IsHome
należy wywołać na renderowanej stronie głównej. Na ratunek przychodzi nam kontekst globalny:
<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>
Indeks bloga wygląda tak samo, jak nasza strona główna, aczkolwiek z inną zawartością:
…ale nasza strona główna wyświetla teraz polecaną zawartość w siatce:
Szablony częściowe
Podczas tworzenia prawdziwej witryny internetowej szybko przydaje się dzielenie szablonów na części. Być może chcesz ponownie wykorzystać konkretną część szablonu, a może po prostu chcesz podzielić ogromny, nieporęczny szablon na spójne części. W tym celu najlepszym rozwiązaniem są częściowe szablony Hugo.
Z perspektywy kontekstu ważne jest to, że kiedy dołączamy częściowy szablon, wyraźnie przekazujemy mu kontekst , który chcemy mu udostępnić. Powszechną praktyką jest przekazywanie w kontekście, tak jak w przypadku dołączenia częściowej, na przykład: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Jeśli kropka w tym miejscu odnosi się do renderowanej strony, to właśnie to zostanie przekazane do części; jeśli kontekst został odwrócony do czegoś innego, to właśnie to jest przekazywane.
Możesz oczywiście ponownie powiązać kontekst w szablonach częściowych, tak jak w normalnych. W tym przypadku kontekst globalny $
, odnosi się do oryginalnego kontekstu przekazanego do części, a nie do renderowanej strony głównej (chyba że to właśnie zostało przekazane).
Jeśli chcemy, aby szablon częściowy miał dostęp do określonej części danych, możemy napotkać problemy, jeśli przekażemy tylko to do części. Przypominasz sobie wcześniejszy problem z dostępem do metod strony po ponownym powiązaniu kontekstu? To samo dotyczy części , ale w tym przypadku kontekst globalny nie może nam pomóc — jeśli przekazaliśmy, powiedzmy, ciąg do szablonu częściowego, kontekst globalny w częściowym będzie odnosić się do tego ciągu i wygraliśmy nie można wywoływać metod zdefiniowanych w kontekście strony.
Rozwiązanie tego problemu polega na przekazywaniu więcej niż jednego fragmentu danych podczas włączania części. Możemy jednak podać tylko jeden argument do wywołania częściowego. Możemy jednak uczynić ten argument złożonym typem danych, powszechnie mapą (znaną jako słownik lub hash w innych językach programowania).
Na tej mapie możemy na przykład ustawić klucz Page
na bieżący obiekt strony, wraz z innymi kluczami do przekazywania dowolnych danych niestandardowych. Obiekt strony będzie wtedy dostępny jako .Page
w części, a drugi w podobny sposób uzyskuje się dostęp do wartości mapy. Mapa jest tworzona za pomocą funkcji szablonu dict
, która pobiera parzystą liczbę argumentów, interpretowanych naprzemiennie jako klucz, jego wartość, klucz, jego wartość i tak dalej.
W naszym przykładowym szablonie przenieśmy kod dla naszej polecanej i wymienionej zawartości do części. W przypadku polecanej treści wystarczy przekazać polecany obiekt strony. Jednak wymieniona zawartość wymaga dostępu do metody .IsHome
oprócz renderowania określonej wymienionej zawartości. Jak wspomniano wcześniej, chociaż .IsHome
jest również dostępny w obiekcie strony dla wymienionej strony, nie da nam to prawidłowej odpowiedzi — chcemy wiedzieć, czy renderowana strona główna jest stroną główną.
Zamiast tego moglibyśmy przekazać zestaw logiczny do wyniku wywołania .IsHome
, ale być może w przyszłości częściowy będzie potrzebował dostępu do innych metod strony — przejdźmy do przekazywania w głównym obiekcie strony, jak również w wymienionym obiekcie strony. W naszym przykładzie strona główna znajduje się w $
, a wymieniona strona w .
. Tak więc w mapie przekazanej do listed
fragmentu klucz Page
otrzymuje wartość $
, podczas gdy klucz „Listed” otrzymuje wartość .
. To jest zaktualizowany główny szablon:
<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>
Zawartość naszej „polecanej” części nie zmienia się w porównaniu do tego, kiedy była częścią szablonu listy:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Jednak nasza część dla wymienionej zawartości odzwierciedla fakt, że oryginalny obiekt strony znajduje się teraz w .Page
, podczas gdy wymieniony fragment zawartości znajduje się w .Listed
:
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
Hugo zapewnia również funkcję szablonu podstawowego, która pozwala rozszerzyć wspólny szablon podstawowy , w przeciwieństwie do dołączania szablonów podrzędnych. W tym przypadku kontekst działa podobnie: rozszerzając szablon bazowy, podajesz dane, które będą stanowić oryginalny kontekst w tym szablonie.
Zmienne niestandardowe
Możliwe jest również przypisanie i ponowne przypisanie własnych zmiennych niestandardowych w szablonie Hugo. Będą one dostępne w szablonie, w którym są zadeklarowane, ale nie trafią do żadnych podszablonów ani szablonów podstawowych, chyba że wyraźnie je przekażemy. Zmienna niestandardowa zadeklarowana wewnątrz „bloku” , taka jak ta określona przez instrukcję if
, będzie dostępna tylko w tym bloku — jeśli chcemy się do niej odwoływać poza blokiem, musimy ją zadeklarować poza blokiem, a następnie zmodyfikować wewnątrz bloku zablokować zgodnie z wymaganiami.
Zmienne niestandardowe mają nazwy poprzedzone znakiem dolara ( $
). Aby zadeklarować zmienną i jednocześnie nadać jej wartość, użyj operatora :=
. Kolejne przypisania do zmiennej używają operatora =
(bez dwukropka). Nie można przypisać zmiennej przed jej zadeklarowaniem i nie można jej zadeklarować bez podania jej wartości.
Jednym z przypadków użycia zmiennych niestandardowych jest uproszczenie długich wywołań funkcji poprzez przypisanie jakiegoś wyniku pośredniego do odpowiednio nazwanej zmiennej. Na przykład, możemy przypisać polecany obiekt strony do zmiennej o nazwie $featured
, a następnie podać tę zmienną do instrukcji with
. Moglibyśmy również umieścić dane do dostarczenia do „wymienionej” części w zmiennej i przekazać je do wywołania częściowego.
Oto jak wyglądałby nasz szablon po tych zmianach:
<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>
Opierając się na moim doświadczeniu z Hugo, polecam swobodne używanie zmiennych niestandardowych, gdy tylko próbujesz zaimplementować bardziej zaangażowaną logikę w szablonie. Chociaż naturalne jest, aby starać się zachować zwięzły kod, może to łatwo sprawić, że sprawy będą mniej jasne, niż mogłyby być, wprowadzając w błąd Ciebie i innych.
Zamiast tego użyj opisowo nazwanych zmiennych dla każdego kroku i nie martw się, że użyjesz dwóch wierszy (lub trzech, czterech itd.) tam, gdzie można by to zrobić.
.Zadrapanie
Na koniec omówimy mechanizm .Scratch
. We wcześniejszych wersjach Hugo zmienne niestandardowe można było przypisać tylko raz; nie było możliwe przedefiniowanie zmiennej niestandardowej. Obecnie zmienne niestandardowe można przedefiniować, co sprawia, że .Scratch
jest mniej ważny, chociaż nadal ma swoje zastosowania.
Krótko mówiąc, .Scratch
to obszar zdrapek umożliwiający ustawianie i modyfikowanie własnych zmiennych , takich jak zmienne niestandardowe. W przeciwieństwie do zmiennych niestandardowych, .Scratch
należy do kontekstu strony, więc przekazanie tego kontekstu na przykład do częściowego spowoduje automatyczne przeniesienie ze sobą zmiennych scratch.
Możesz ustawiać i pobierać zmienne w .Scratch
, wywołując jego metody Set
i Get
. Metod jest więcej niż te, na przykład do ustawiania i aktualizowania złożonych typów danych, ale te dwie wystarczą tutaj dla naszych potrzeb. Set
przyjmuje dwa parametry : klucz i wartość danych, które chcesz ustawić. Get
zajmuje tylko jedno: klucz do danych, które chcesz odzyskać.
Wcześniej używaliśmy dict
do tworzenia struktury danych mapy, aby przekazywać wiele fragmentów danych do części. Zostało to zrobione, aby częściowy dla wymienionej strony miał dostęp zarówno do oryginalnego kontekstu strony, jak i konkretnego wymienionego obiektu strony. Używanie .Scratch
niekoniecznie jest lepszym lub gorszym sposobem na zrobienie tego — cokolwiek jest preferowane, może zależeć od sytuacji.
Zobaczmy, jak wyglądałby nasz szablon listy, używając .Scratch
zamiast dict
do przekazywania danych do części. $.Scratch.Get
(ponownie używając kontekstu globalnego), aby ustawić zmienną scratch „listed” na .
— w tym przypadku wymieniony obiekt strony. Następnie przekazujemy tylko obiekt strony, $
, do części. Zmienne skreczowania pojawią się automatycznie.
<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>
Wymagałoby to również pewnych modyfikacji części listed.html
— oryginalny kontekst strony jest teraz dostępny jako „kropka”, podczas gdy wymieniona strona jest pobierana z obiektu .Scratch
. Użyjemy zmiennej niestandardowej, aby uprościć dostęp do wymienionej strony:
<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>
Jednym z argumentów za robieniem rzeczy w ten sposób jest konsekwencja. Używając .Scratch
, możesz wyrobić sobie nawyk przekazywania w bieżącym obiekcie strony dowolnej części, dodając dodatkowe dane jako zmienne scratch. Wtedy, za każdym razem, gdy piszesz lub edytujesz swoje podszablony, wiesz, że .
jest obiektem strony. Oczywiście możesz również ustalić dla siebie konwencję, używając przekazanej mapy: na przykład zawsze wysyłając obiekt strony jako .Page
.
Wniosek
Jeśli chodzi o kontekst i dane, statyczny generator witryn niesie ze sobą zarówno korzyści, jak i ograniczenia. Z jednej strony operacja, która jest zbyt nieefektywna, gdy jest uruchamiana przy każdej wizycie na stronie, może być doskonale dobra, gdy jest uruchamiana tylko raz, gdy strona jest kompilowana. Z drugiej strony może Cię zaskoczyć, jak często przydałby się dostęp do jakiejś części żądania sieciowego nawet w witrynie w większości statycznej.
Aby obsłużyć parametry ciągu zapytania , na przykład w witrynie statycznej, musisz skorzystać z JavaScript lub jakiegoś zastrzeżonego rozwiązania, takiego jak przekierowania Netlify. Chodzi o to, że chociaż przeskok ze strony dynamicznej do statycznej jest teoretycznie prosty, wymaga zmiany sposobu myślenia. Na początku łatwo jest wrócić do starych nawyków, ale praktyka uczyni mistrza.
Na tym kończymy nasze spojrzenie na zarządzanie danymi w generatorze stron statycznych Hugo. 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.
Dalsza lektura
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.