Context și variabile în generatorul de site static Hugo
Publicat: 2022-03-10În acest articol, vom arunca o privire atentă asupra modului în care funcționează contextul în generatorul de site-uri static Hugo. Vom examina modul în care datele circulă de la conținut la șabloane, modul în care anumite constructe schimbă datele disponibile și cum putem transmite aceste date către șabloane parțiale și de bază.
Acest articol nu este o introducere în Hugo . Probabil că vei profita la maximum dacă ai ceva experiență cu Hugo, deoarece nu vom trece peste fiecare concept de la zero, ci ne vom concentra mai degrabă pe subiectul principal al contextului și al variabilelor. Cu toate acestea, dacă vă referiți la documentația Hugo pe tot parcursul, este posibil să vă puteți urmări chiar și fără experiență anterioară!
Vom studia diferite concepte prin construirea unei pagini de exemplu. Nu fiecare fișier necesar pentru site-ul exemplu va fi tratat în detaliu, dar proiectul complet este disponibil pe GitHub. Dacă doriți să înțelegeți cum se potrivesc piesele, acesta este un bun punct de plecare. De asemenea, rețineți că nu vom acoperi cum să configurați un site Hugo sau să rulați serverul de dezvoltare - instrucțiunile pentru rularea exemplului sunt în depozit.
Ce este un generator de site static?
Dacă conceptul de generatoare statice de site este nou pentru dvs., iată o introducere rapidă! Generatoarele de site-uri statice sunt poate cel mai bine descrise comparându-le cu site-urile dinamice. Un site dinamic precum un CMS asamblează, în general, o pagină de la zero pentru fiecare vizită, poate preluând date dintr-o bază de date și combinând diferite șabloane pentru a face acest lucru. În practică, utilizarea memoriei cache înseamnă că pagina nu este regenerată atât de des, dar în scopul acestei comparații, putem gândi la asta. Un site dinamic este potrivit pentru conținut dinamic : conținut care se schimbă des, conținut care este prezentat într-o mulțime de configurații diferite în funcție de intrare și conținut care poate fi manipulat de către vizitatorul site-ului.
În schimb, multe site-uri se schimbă rareori și acceptă puține contribuții din partea vizitatorilor. O secțiune „ajutor” pentru o aplicație, o listă de articole sau o carte electronică ar putea fi exemple de astfel de site-uri. În acest caz, este mai logic să asamblați paginile finale o dată când conținutul se schimbă, apoi difuzând aceleași pagini fiecărui vizitator până când conținutul se schimbă din nou.
Site-urile dinamice au mai multă flexibilitate, dar solicită mai mult serverului pe care rulează. De asemenea, pot fi dificil de distribuit geografic, mai ales dacă sunt implicate baze de date. Generatoarele de site-uri statice pot fi găzduite pe orice server capabil să livreze fișiere statice și sunt ușor de distribuit.
O soluție comună astăzi, care combină aceste abordări, este JAMstack. „JAM” înseamnă JavaScript, API-uri și markup și descrie elementele de bază ale unei aplicații JAMstack: un generator de site static generează fișiere statice pentru livrare către client, dar stiva are o componentă dinamică sub formă de JavaScript care rulează pe client - această componentă client poate folosi apoi API-uri pentru a oferi funcționalitate dinamică utilizatorului.
Hugo
Hugo este un popular generator de site-uri static. Este scris în Go, iar faptul că Go este un limbaj de programare compilat indică unele dintre beneficiile și dezavantajele Hugos. În primul rând, Hugo este foarte rapid , ceea ce înseamnă că generează foarte repede site-uri statice. Desigur, acest lucru nu are nicio legătură cu cât de rapide sau lente sunt site-urile create cu Hugo pentru utilizatorul final, dar pentru dezvoltator, faptul că Hugo compile chiar și site-uri mari într-o clipă este destul de valoros.
Cu toate acestea, deoarece Hugo este scris într-un limbaj compilat, extinderea acestuia este dificilă . Alți generatori de site vă permit să introduceți propriul cod - în limbi precum Ruby, Python sau JavaScript - în procesul de compilare. Pentru a extinde Hugo, ar trebui să adăugați codul la Hugo însuși și să-l recompilați - în caz contrar, sunteți blocat cu funcțiile șablon pe care Hugo vine din nou.
Deși oferă o varietate bogată de funcții, acest fapt poate deveni limitativ dacă generarea paginilor dvs. implică o logică complicată. După cum am constatat, având un site dezvoltat inițial care rulează pe o platformă dinamică, cazurile în care ați luat de la sine înțeles posibilitatea de a introduce codul personalizat tind să se înmulțească.
Echipa noastră menține o varietate de site-uri web legate de produsul nostru principal, clientul Tower Git, și recent ne-am uitat la mutarea unora dintre acestea la un generator de site static. Unul dintre site-urile noastre, site-ul „Învățați”, a părut foarte potrivit pentru un proiect pilot. Acest site conține o varietate de materiale de învățare gratuite, inclusiv videoclipuri, cărți electronice și întrebări frecvente despre Git, dar și alte subiecte tehnice.
Conținutul său este în mare parte de natură statică și orice caracteristici interactive care există (cum ar fi înscrierile la buletine informative) erau deja alimentate de JavaScript. La sfârșitul anului 2020, am convertit acest site din CMS-ul nostru anterior în Hugo , iar astăzi rulează ca un site static. Desigur, în acest proces am învățat multe despre Hugo. Acest articol este o modalitate de a împărtăși unele dintre lucrurile pe care le-am învățat.
Exemplul nostru
Pe măsură ce acest articol a luat naștere din munca noastră de conversie a paginilor noastre în Hugo, pare natural să punem împreună o pagină de destinație ipotetică (foarte!) simplificată ca exemplu. Obiectivul nostru principal va fi un așa-numit șablon „listă” reutilizabil.
Pe scurt, Hugo va folosi un șablon de listă pentru orice pagină care conține subpagini. Există mai multe în ierarhia șabloanelor Hugos decât atât, dar nu trebuie să implementați fiecare șablon posibil. Un șablon de listă unică face un drum lung. Acesta va fi utilizat în orice situație care necesită un șablon de listă în care nu este disponibil un șablon mai specializat.
Cazurile de utilizare potențiale includ o pagină de pornire, un index de blog sau o listă de întrebări frecvente. Șablonul nostru de listă reutilizabil se va afla în layouts/_default/list.html
în proiectul nostru. Din nou, restul fișierelor necesare pentru compilarea exemplului nostru sunt disponibile pe GitHub, unde puteți, de asemenea, să vedeți mai bine cum se potrivesc piesele. Depozitul GitHub vine și cu un șablon single.html
- așa cum sugerează și numele, acest șablon este folosit pentru paginile care nu au subpagini, dar acționează ca bucăți de conținut unice în sine.
Acum că am pregătit scena și am explicat ce vom face, să începem!
Contextul sau „Punctul”
Totul începe cu punctul. Într-un șablon Hugo, obiectul .
— „punctul” — se referă la contextul actual. Ce inseamna asta? Fiecare șablon redat în Hugo are acces la un set de date - contextul său . Acesta este setat inițial la un obiect care reprezintă pagina în curs de redare, inclusiv conținutul acesteia și unele metadate. Contextul include, de asemenea, variabile la nivel de site, cum ar fi opțiunile de configurare și informații despre mediul curent. Veți accesa un câmp precum titlul paginii curente folosind .Title
și versiunea Hugo utilizată prin .Hugo.Version
- cu alte cuvinte, accesați câmpurile din .
structura.
Important este că acest context se poate schimba, făcând o referință ca „.Titlu” de mai sus să indice altceva sau chiar făcându-l invalid. Acest lucru se întâmplă, de exemplu, când treceți peste o colecție de un fel folosind range
, sau când împărțiți șabloanele în șabloane parțiale și de bază . Ne vom uita la asta în detaliu mai târziu!
Hugo folosește pachetul „șabloane” Go, așa că atunci când ne referim la șabloanele Hugo în acest articol, vorbim cu adevărat despre șabloanele Go. Hugo adaugă multe funcții de șabloane care nu sunt disponibile în șabloanele Go standard.
În opinia mea, contextul și posibilitatea de a-l relega este una dintre cele mai bune caracteristici Hugos. Pentru mine, este foarte logic să ai mereu „punctul” să reprezinte orice obiect care este principalul obiectiv al șablonului meu la un anumit moment, relegandu-l pe măsură ce este necesar. Desigur, este posibil să te bagi și tu într-o mizerie încurcată, dar am fost mulțumit de ea până acum, în măsura în care am început rapid să-l lipsească în orice alt generator de site static la care m-am uitat.
Cu aceasta, suntem gata să ne uităm la punctul de pornire umil al exemplului nostru - șablonul de mai jos, care se află în locația layouts/_default/list.html
din proiectul nostru:
<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>
Cea mai mare parte a șablonului constă dintr-o structură HTML simplă, cu un link pentru foaia de stil, un meniu pentru navigare și câteva elemente și clase suplimentare utilizate pentru stil. Lucrurile interesante sunt între bretele , care îi semnalează lui Hugo să intervină și să-și facă magia, înlocuind orice se află între bretele cu rezultatul evaluării unei expresii și, eventual, al manipulării contextului.
După cum puteți ghici, {{ .Title }}
din eticheta de titlu se referă la titlul paginii curente, în timp ce {{ .Site.Title }}
se referă la titlul întregului site, stabilit în configurația Hugo . O etichetă precum {{ .Title }}
îi spune pur și simplu lui Hugo să înlocuiască acea etichetă cu conținutul câmpului Title
în contextul curent.
Deci, am accesat unele date aparținând paginii într-un șablon. De unde provin aceste date? Acesta este subiectul secțiunii următoare.
Conținut și materie frontală
Unele dintre variabilele disponibile în context sunt furnizate automat de Hugo. Altele sunt definite de noi, în principal în fișierele de conținut . Există și alte surse de date, cum ar fi fișiere de configurare, variabile de mediu, fișiere de date și chiar API-uri. În acest articol, accentul nostru se va concentra pe fișierele de conținut ca sursă de date.
În general, un singur fișier de conținut reprezintă o singură pagină. Un fișier de conținut tipic include conținutul principal al acelei pagini, dar și metadate despre pagină, cum ar fi titlul acesteia sau data la care a fost creată. Hugo acceptă mai multe formate atât pentru conținutul principal, cât și pentru metadate. În acest articol, vom alege probabil cea mai comună combinație: conținutul este furnizat ca Markdown într-un fișier care conține metadatele ca element frontal YAML.
În practică, asta înseamnă că fișierul de conținut începe cu o secțiune delimitată de o linie care conține trei liniuțe la fiecare capăt. Această secțiune constituie subiectul principal și aici metadatele sunt definite folosind o key: value
(După cum vom vedea în curând, YAML acceptă și structuri de date mai elaborate). Prima pagină este urmată de conținutul real, specificat folosind limbajul de marcare Markdown.
Să facem lucrurile mai concrete uitându-ne la un exemplu. Iată un fișier de conținut foarte simplu, cu un câmp principal și un paragraf de conținut:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Acest fișier se află la content/_index.md
în proiectul nostru, cu _index.md
desemnând fișierul de conținut pentru o pagină care are subpagini. Din nou, depozitul GitHub arată clar unde ar trebui să meargă ce fișier.)
Redat folosind șablonul de mai devreme, împreună cu unele stiluri și fișiere periferice (toate găsite pe GitHub), rezultatul arată astfel:
S-ar putea să vă întrebați dacă numele câmpurilor din partea din față a fișierului nostru de conținut sunt predeterminate sau dacă putem adăuga orice câmp ne place. Răspunsul este „amândouă”. Există o listă de câmpuri predefinite, dar putem adăuga și orice alt câmp cu care putem veni. Cu toate acestea, aceste câmpuri sunt accesate puțin diferit în șablon. În timp ce un câmp predefinit, cum ar fi title
este accesat simplu ca .Title
, un câmp personalizat precum author
este accesat folosind .Params.author
.
(Pentru o referință rapidă asupra câmpurilor predefinite, împreună cu lucruri precum funcții, parametrii funcției și variabilele de pagină, consultați propria noastră fișă de cheat Hugo!)
Variabila .Content
, folosită pentru a accesa conținutul principal din fișierul de conținut din șablonul dvs., este specială. Hugo are o funcție „shortcode” care vă permite să utilizați câteva etichete suplimentare în conținutul dvs. Markdown. De asemenea, vă puteți defini pe propria dvs. Din păcate, aceste coduri scurte vor funcționa numai prin variabila .Content
- deși puteți rula orice altă parte de date printr-un filtru Markdown, acesta nu va gestiona codurile scurte din conținut.
O notă aici despre variabilele nedefinite: accesarea unui câmp predefinit precum .Date
funcționează întotdeauna, chiar dacă nu ați setat-o — va fi returnată o valoare goală în acest caz. Accesarea unui câmp personalizat nedefinit, cum ar fi .Params.thisHasNotBeenSet
, funcționează, de asemenea, returnând o valoare goală. Cu toate acestea, accesarea unui câmp de nivel superior nepredefinit precum .thisDoesNotExist
va împiedica compilarea site-ului.
După cum au indicat mai devreme .Params.author
, precum și .Hugo.version
și .Site.title
, invocările înlănțuite pot fi folosite pentru a accesa un câmp imbricat într-o altă structură de date. Putem defini astfel de structuri în materia noastră frontală. Să ne uităm la un exemplu, în care definim o hartă sau dicționar, specificând unele proprietăți pentru un banner de pe pagina din fișierul nostru de conținut. Iată 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!
Acum, să adăugăm un banner la șablonul nostru, referindu-ne la datele bannerului folosind .Params
în modul descris mai sus:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Iată cum arată acum site-ul nostru:
Bine! În acest moment, accesăm câmpurile din contextul implicit fără probleme. Cu toate acestea, așa cum am menționat mai devreme, acest context nu este fix, dar se poate schimba.
Să aruncăm o privire la cum s-ar putea întâmpla asta.
Controlul debitului
Declarațiile de control al fluxului sunt o parte importantă a unui limbaj de șabloane, permițându-vă să faceți diferite lucruri în funcție de valoarea variabilelor, să faceți bucla prin date și multe altele. Șabloanele Hugo oferă setul așteptat de constructe, inclusiv if/else
pentru logica condiționată și range
pentru buclă. Aici, nu vom acoperi controlul fluxului în Hugo în general (pentru mai multe despre aceasta, consultați documentația), ci ne vom concentra asupra modului în care aceste declarații afectează contextul. În acest caz, cele mai interesante afirmații sunt with
și range
.
Să începem with
. Această declarație verifică dacă o expresie are o valoare „nevide” și, dacă o are, leagă contextul pentru a se referi la valoarea expresiei respective . O etichetă de end
indică punctul în care influența declarației with
se oprește, iar contextul revine la orice a fost înainte. Documentația Hugo definește o valoare non-vid ca fals, 0 și orice matrice, felie, hartă sau șir de lungime zero.
În prezent, șablonul nostru de listă nu face deloc multe listări. Ar putea avea sens ca un șablon de listă să prezinte într-un fel unele dintre subpaginile sale . Acest lucru ne oferă o oportunitate perfectă pentru exemple ale declarațiilor noastre de control al fluxului.
Poate că vrem să afișăm un conținut prezentat în partea de sus a paginii noastre. Acesta ar putea fi orice conținut - o postare pe blog, un articol de ajutor sau o rețetă, de exemplu. În acest moment, să presupunem că site-ul nostru exemplu Tower are câteva pagini care îi evidențiază caracteristicile, cazurile de utilizare, o pagină de ajutor, o pagină de blog și o pagină „platformă de învățare”. Acestea sunt toate localizate în directorul content/
. Configuram ce componentă de conținut să apară adăugând un câmp în fișierul de conținut pentru pagina noastră de pornire, content/_index.md
. La pagina se face referire prin calea sa, presupunând că directorul de conținut este rădăcină, astfel:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
În continuare, șablonul nostru de listă trebuie modificat pentru a afișa acest conținut. Hugo are o funcție de șablon, .GetPage
, care ne va permite să ne referim la obiecte de pagină, altele decât cele pe care le redăm în prezent. Amintiți-vă cum contextul, .
, a fost inițial legat de un obiect reprezentând pagina în curs de redare? Folosind .GetPage
și with
, putem relega temporar contextul la o altă pagină, referindu-ne la câmpurile acelei pagini atunci când afișăm conținutul nostru prezentat:
<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>
Aici, {{ .Title }}
, {{ .Summary }}
și {{ .Permalink }}
între etichetele with
și cele de end
se referă la acele câmpuri din pagina prezentată și nu la cel principal care este redat.
Pe lângă faptul că avem o bucată de conținut prezentată, haideți să enumerăm câteva piese de conținut mai jos. La fel ca și conținutul prezentat, părțile de conținut listate vor fi definite în content/_index.md
, fișierul de conținut pentru pagina noastră de start. Vom adăuga o listă de căi de conținut la subiectul nostru principal astfel (în acest caz, specificând și titlul secțiunii):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
Motivul pentru care pagina de blog are propriul director și un fișier _index.md
este că blogul va avea subpagini proprii — postări de blog.
Pentru a afișa această listă în șablonul nostru, vom folosi range
. Deloc surprinzător, această declarație va trece peste o listă, dar va lega și contextul la fiecare element al listei pe rând. Acest lucru este foarte convenabil pentru lista noastră de conținut.
Rețineți că, din perspectiva lui Hugo, „listarea” conține doar câteva șiruri. Pentru fiecare iterație a buclei „gamă”, contextul va fi legat de unul dintre acele șiruri . Pentru a obține acces la obiectul pagină real, furnizăm șirul său de cale (acum valoarea .
) ca argument pentru .GetPage
. Apoi, vom folosi din nou instrucțiunea with
pentru a relega contextul la obiectul paginii listate, mai degrabă decât șirul său de cale. Acum, este ușor să afișați pe rând conținutul fiecărei pagini enumerate:
<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>
Iată cum arată site-ul în acest moment:
Dar stai, există ceva ciudat în șablonul de mai sus - în loc să apelăm .GetPage
, numim $.GetPage
. Puteți ghici de ce .GetPage
nu ar funcționa?
Notația .GetPage
indică faptul că funcția GetPage
este o metodă a contextului curent. Într-adevăr, în contextul implicit, există o astfel de metodă, dar tocmai am mers înainte și am schimbat contextul ! Când apelăm .GetPage
, contextul este legat de un șir, care nu are această metodă. Modul în care rezolvăm acest lucru este subiectul secțiunii următoare.
Contextul global
După cum am văzut mai sus, există situații în care contextul a fost schimbat, dar am dori totuși să accesăm contextul original. Aici, pentru că vrem să apelăm o metodă existentă în contextul original - o altă situație comună este atunci când dorim să accesăm o proprietate a paginii principale care este redată. Nicio problemă, există o modalitate ușoară de a face asta.
Într-un șablon Hugo, $
, cunoscut sub numele de context global , se referă la valoarea originală a contextului - contextul așa cum era atunci când a început procesarea șablonului. În secțiunea anterioară, a fost folosit pentru a apela metoda .GetPage
chiar dacă am restabilit contextul într-un șir. Acum, îl vom folosi și pentru a accesa un câmp al paginii care este redată.
La începutul acestui articol, am menționat că șablonul nostru de listă este reutilizabil. Până acum, l-am folosit doar pentru pagina de pornire, redând un fișier de conținut situat la content/_index.md
. În exemplul de depozit, există un alt fișier de conținut care va fi redat folosind acest șablon: content/blog/_index.md
. Aceasta este o pagină de index pentru blog și, la fel ca pagina de pornire, arată o bucată de conținut prezentată și enumeră câteva - postări de blog, în acest caz.
Acum, să presupunem că vrem să arătăm conținutul listat ușor diferit pe pagina principală - nu suficient pentru a garanta un șablon separat, ci ceva ce putem face cu o declarație condiționată în șablonul în sine. De exemplu, vom afișa conținutul listat într-o grilă cu două coloane, spre deosebire de o listă cu o singură coloană, dacă detectăm că redăm pagina de pornire.
Hugo vine cu o metodă de pagină, .IsHome
, care oferă exact funcționalitatea de care avem nevoie. Ne vom ocupa de schimbarea reală a prezentării adăugând o clasă la bucățile individuale de conținut atunci când descoperim că suntem pe pagina de pornire, permițând fișierului nostru CSS să facă restul.
Am putea, desigur, să adăugăm clasa la elementul body sau la un element care îl conține, dar asta nu ar permite o demonstrație la fel de bună a contextului global. Până când scriem codul HTML pentru conținutul listat, .
se referă la pagina listată , dar IsHome
trebuie apelat pe pagina principală care se redă. Contextul global ne vine în ajutor:
<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>
Indexul blogului arată exact ca pagina noastră de pornire, deși cu conținut diferit:
…dar pagina noastră de start își afișează acum conținutul prezentat într-o grilă:
Șabloane parțiale
Când construiți un site web real, devine rapid util să vă împărțiți șabloanele în părți. Poate doriți să reutilizați o anumită parte a unui șablon sau poate doriți doar să împărțiți un șablon uriaș și greu de manevrat în bucăți coerente. În acest scop, șabloanele parțiale ale lui Hugo sunt calea de urmat.
Dintr-o perspectivă de context, lucrul important aici este că atunci când includem un șablon parțial, îi transmitem în mod explicit contextul pe care vrem să-l punem la dispoziție. O practică obișnuită este de a trece în context așa cum este atunci când parțialul este inclus, astfel: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Dacă punctul de aici se referă la pagina care este redată, aceasta este ceea ce va fi trecut la parțial; dacă contextul a fost revenit la altceva, asta este transmis.
Puteți, desigur, să relegați contextul în șabloane parțiale la fel ca în cele normale. În acest caz, contextul global, $
, se referă la contextul original trecut la parțial, nu la pagina principală care este redată (cu excepția cazului în care acesta este ceea ce a fost transmis).
Dacă dorim ca un șablon parțial să aibă acces la o anumită bucată de date, s-ar putea să întâmpinăm probleme dacă îl transmitem doar celui parțial. Vă amintiți problema noastră de mai devreme cu accesarea metodelor de pagină după relegarea contextului? Același lucru este valabil și pentru părțile , dar în acest caz contextul global nu ne poate ajuta - dacă am trecut, de exemplu, un șir într-un șablon parțial, contextul global din parțial se va referi la acel șir și am câștigat nu pot apela metode definite în contextul paginii.
Soluția la această problemă constă în transmiterea a mai mult de o bucată de date la includerea parțială. Cu toate acestea, avem voie să oferim un singur argument apelului parțial. Putem, totuși, să facem din acest argument un tip de date compus, de obicei o hartă (cunoscută ca dicționar sau hash în alte limbaje de programare).
În această hartă, putem, de exemplu, să avem o cheie Page
setată la obiectul paginii curente, împreună cu alte chei pentru transmiterea oricăror date personalizate. Obiectul pagină va fi apoi disponibil ca .Page
în parțial, iar celălalt valorile hărții sunt accesate în mod similar. O hartă este creată folosind funcția șablon dict
, care ia un număr par de argumente, interpretate alternativ ca o cheie, valoarea sa, o cheie, valoarea sa și așa mai departe.
În șablonul nostru exemplu, să mutăm codul pentru conținutul nostru prezentat și listat în părți parțiale. Pentru conținutul prezentat, este suficient să treci în obiectul paginii prezentate. Conținutul listat, totuși, are nevoie de acces la metoda .IsHome
, în plus față de conținutul listat care este redat. După cum am menționat mai devreme, în timp ce .IsHome
este disponibil și pe obiectul paginii pentru pagina listată, asta nu ne va oferi răspunsul corect - vrem să știm dacă pagina principală care este redată este pagina de pornire.
În schimb, am putea trece un set boolean la rezultatul apelării .IsHome
, dar poate că parțialul va avea nevoie de acces la alte metode de pagină în viitor - să trecem cu trecerea obiectului pagina principală , precum și a obiectului pagină listată. În exemplul nostru, pagina principală se găsește în $
, iar pagina listată în .
. Deci, în harta transmisă parțialului listed
, cheia Page
primește valoarea $
în timp ce cheia „Listată” primește valoarea .
. Acesta este șablonul principal actualizat:
<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>
Conținutul parțialului nostru „reprezentat” nu se modifică în comparație cu când făcea parte din șablonul de listă:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Cu toate acestea, parțial nostru pentru conținutul listat reflectă faptul că obiectul original al paginii se găsește acum în .Page
, în timp ce conținutul listat se găsește în .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 oferă, de asemenea, funcționalitate de șablon de bază, care vă permite să extindeți un șablon de bază comun , spre deosebire de includerea subșabloanelor. În acest caz, contextul funcționează similar: atunci când extindeți un șablon de bază, furnizați datele care vor constitui contextul original în acel șablon.
Variabile personalizate
De asemenea, este posibil să atribuiți și să reatribuiți propriile variabile personalizate într-un șablon Hugo. Acestea vor fi disponibile în șablonul în care sunt declarate, dar nu vor intra în niciun șablon parțial sau de bază decât dacă le transmitem în mod explicit. O variabilă personalizată declarată în interiorul unui „bloc” precum cea specificată de o instrucțiune if
va fi disponibilă numai în interiorul acelui bloc - dacă vrem să ne referim la ea în afara blocului, trebuie să o declarăm în afara blocului, apoi să o modificăm în interiorul blocului. blocați după cum este necesar.
Variabilele personalizate au nume prefixate de un semn dolar ( $
). Pentru a declara o variabilă și a-i da o valoare în același timp, utilizați operatorul :=
. Atribuțiile ulterioare variabilei folosesc operatorul =
(fără două puncte). O variabilă nu poate fi atribuită înainte de a fi declarată și nu poate fi declarată fără a-i da o valoare.
Un caz de utilizare pentru variabilele personalizate este simplificarea apelurilor lungi de funcții prin atribuirea unui rezultat intermediar unei variabile numite corespunzător. De exemplu, am putea atribui obiectul paginii prezentate unei variabile numite $featured
și apoi să furnizăm această variabilă instrucțiunii with
. Am putea, de asemenea, să punem datele de furnizat parțialului „listat” într-o variabilă și să le dăm apelului parțial.
Iată cum ar arăta șablonul nostru cu aceste modificări:
<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>
Pe baza experienței mele cu Hugo, aș recomanda utilizarea liberală a variabilelor personalizate de îndată ce încercați să implementați o logică mai implicată într-un șablon. Deși este firesc să încerci să-ți păstrezi codul concis, acest lucru poate face lucrurile mai puțin clare decât ar putea fi, derutându-te pe tine și pe alții.
În schimb, utilizați variabile cu nume descriptiv pentru fiecare pas și nu vă faceți griji cu privire la utilizarea a două linii (sau trei, sau patru, etc.) acolo unde ar face unul.
.Zgârietură
În cele din urmă, să acoperim mecanismul .Scratch
. În versiunile anterioare ale lui Hugo, variabilele personalizate puteau fi atribuite o singură dată; nu a fost posibil să se redefinească o variabilă personalizată. În zilele noastre, variabilele personalizate pot fi redefinite, ceea ce face ca .Scratch
să fie mai puțin important, deși își are încă utilizările.
Pe scurt, .Scratch
este o zonă scratch care vă permite să setați și să modificați propriile variabile , cum ar fi variabilele personalizate. Spre deosebire de variabilele personalizate, .Scratch
aparține contextului paginii, așa că trecerea acestui context la un parțial, de exemplu, va aduce variabilele scratch împreună cu el în mod automat.
Puteți seta și prelua variabile pe .Scratch
apelând metodele Set
și Get
. Există mai multe metode decât acestea, de exemplu pentru setarea și actualizarea tipurilor de date compuse, dar acestea două vor fi suficiente pentru nevoile noastre aici. Set
are doi parametri : cheia și valoarea pentru datele pe care doriți să le setați. Get
ia doar unul: cheia pentru datele pe care doriți să le recuperați.
Mai devreme, am folosit dict
pentru a crea o structură de date hărți pentru a transmite mai multe bucăți de date către un parțial. Acest lucru a fost făcut astfel încât parțialul pentru o pagină listată să aibă acces atât la contextul original al paginii, cât și la obiectul specific al paginii listate. Utilizarea .Scratch
nu este neapărat o modalitate mai bună sau mai proastă de a face acest lucru - oricare este de preferat poate depinde de situație.
Să vedem cum ar arăta șablonul nostru de listă folosind .Scratch
în loc de dict
pentru a trece date către parțial. $.Scratch.Get
(din nou folosind contextul global) pentru a seta variabila scratch „listed” la .
— în acest caz, obiectul pagină listat. Apoi trecem doar obiectul pagină, $
, la parțial. Variabilele scratch vor urma automat.
<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>
Acest lucru ar necesita și unele modificări ale listed.html
parțiale - contextul original al paginii este acum disponibil ca „punctul” în timp ce pagina listată este preluată din obiectul .Scratch
. Vom folosi o variabilă personalizată pentru a simplifica accesul la pagina listată:
<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>
Un argument pentru a face lucrurile în acest fel este consecvența. Folosind .Scratch
, vă puteți transforma în obiceiul de a trece întotdeauna obiectul paginii curente la orice parțial, adăugând orice date suplimentare ca variabile scratch. Apoi, ori de câte ori scrieți sau editați parțialele, știți că .
este un obiect de pagină. Desigur, puteți stabili o convenție pentru dvs. folosind și o hartă transmisă: trimiteți întotdeauna de-a lungul obiectului pagină ca .Page
, de exemplu.
Concluzie
Când vine vorba de context și date, un generator de site static aduce atât beneficii, cât și limitări. Pe de o parte, o operațiune care este prea ineficientă atunci când este rulată pentru fiecare vizită de pagină poate fi perfect bună când este rulată o singură dată pe măsură ce pagina este compilată. Pe de altă parte, s-ar putea să vă surprindă cât de des ar fi util să aveți acces la o parte din cererea de rețea chiar și pe un site predominant static.
Pentru a gestiona parametrii șirului de interogări , de exemplu, pe un site static, ar trebui să recurgeți la JavaScript sau la o soluție proprietară, cum ar fi redirecționările Netlify. Ideea aici este că, deși saltul de la un site dinamic la unul static este simplu în teorie, este nevoie de o schimbare a mentalității. La început, este ușor să renunți la vechile obiceiuri, dar practica va fi perfectă.
Cu asta, încheiem privirea noastră asupra gestionării datelor în generatorul de site-uri static 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.
Lectură suplimentară
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.