Contesto e variabili nel generatore di siti statici di Hugo
Pubblicato: 2022-03-10In questo articolo, daremo un'occhiata da vicino a come funziona il contesto nel generatore di siti statici di Hugo. Esamineremo come i dati fluiscono dal contenuto ai modelli, come determinati costrutti cambiano i dati disponibili e come possiamo trasmettere questi dati ai parziali e ai modelli di base.
Questo articolo non è un'introduzione a Hugo . Probabilmente ne otterrai il massimo se hai una certa esperienza con Hugo, poiché non esamineremo tutti i concetti da zero, ma ci concentreremo piuttosto sull'argomento principale del contesto e delle variabili. Tuttavia, se fai riferimento alla documentazione di Hugo in tutto, potresti essere in grado di seguire anche senza esperienza precedente!
Studieremo vari concetti costruendo una pagina di esempio. Non tutti i file richiesti per il sito di esempio verranno trattati in dettaglio, ma il progetto completo è disponibile su GitHub. Se vuoi capire come si incastrano i pezzi, questo è un buon punto di partenza. Tieni inoltre presente che non tratteremo come configurare un sito Hugo o eseguire il server di sviluppo: le istruzioni per eseguire l'esempio sono nel repository.
Che cos'è un generatore di siti statici?
Se il concetto di generatori di siti statici è nuovo per te, ecco una rapida introduzione! I generatori di siti statici sono forse meglio descritti confrontandoli con siti dinamici. Un sito dinamico come un CMS generalmente assembla una pagina da zero per ogni visita, magari prelevando dati da un database e combinando vari modelli per farlo. In pratica, l'uso della memorizzazione nella cache significa che la pagina non viene rigenerata così spesso, ma ai fini di questo confronto, possiamo pensarla in questo modo. Un sito dinamico si adatta bene al contenuto dinamico : contenuto che cambia spesso, contenuto presentato in molte configurazioni diverse a seconda dell'input e contenuto che può essere manipolato dal visitatore del sito.
Al contrario, molti siti cambiano raramente e accettano pochi input dai visitatori. Una sezione di "aiuto" per un'applicazione, un elenco di articoli o un eBook potrebbero essere esempi di tali siti. In questo caso, ha più senso assemblare le pagine finali una volta quando il contenuto cambia, quindi servire le stesse pagine a tutti i visitatori fino a quando il contenuto non cambia di nuovo.
I siti dinamici hanno maggiore flessibilità, ma richiedono una maggiore richiesta al server su cui sono in esecuzione. Possono anche essere difficili da distribuire geograficamente, soprattutto se sono coinvolti database. I generatori di siti statici possono essere ospitati su qualsiasi server in grado di fornire file statici e sono facili da distribuire.
Una soluzione comune oggi, che mescola questi approcci, è il JAMstack. "JAM" sta per JavaScript, API e markup e descrive gli elementi costitutivi di un'applicazione JAMstack: un generatore di siti statici genera file statici da inviare al client, ma lo stack ha un componente dinamico sotto forma di JavaScript in esecuzione sul client — questo componente client può quindi utilizzare le API per fornire funzionalità dinamiche all'utente.
Ugo
Hugo è un popolare generatore di siti statici. È scritto in Go e il fatto che Go sia un linguaggio di programmazione compilato suggerisce alcuni dei vantaggi e degli svantaggi di Hugos. Per uno, Hugo è molto veloce , il che significa che genera siti statici molto rapidamente. Naturalmente, questo non ha alcuna influenza sulla velocità o lentezza dei siti creati con Hugo per l'utente finale, ma per lo sviluppatore il fatto che Hugo compili anche siti di grandi dimensioni in un batter d'occhio è piuttosto prezioso.
Tuttavia, poiché Hugo è scritto in un linguaggio compilato, estenderlo è difficile . Alcuni altri generatori di siti ti consentono di inserire il tuo codice, in linguaggi come Ruby, Python o JavaScript, nel processo di compilazione. Per estendere Hugo, dovresti aggiungere il tuo codice a Hugo stesso e ricompilarlo, altrimenti sei bloccato con le funzioni del modello con cui Hugo viene fornito immediatamente.
Sebbene fornisca una ricca varietà di funzioni, questo fatto può diventare limitante se la generazione delle tue pagine comporta una logica complicata. Come abbiamo scoperto, avendo un sito originariamente sviluppato in esecuzione su una piattaforma dinamica, i casi in cui hai dato per scontata la possibilità di inserire il tuo codice personalizzato tendono ad accumularsi.
Il nostro team gestisce una varietà di siti Web relativi al nostro prodotto principale, il client Tower Git, e di recente abbiamo cercato di spostarne alcuni su un generatore di siti statici. Uno dei nostri siti, il sito "Learn", sembrava particolarmente adatto per un progetto pilota. Questo sito contiene una varietà di materiale didattico gratuito inclusi video, eBook e FAQ su Git, ma anche altri argomenti tecnologici.
Il suo contenuto è in gran parte di natura statica e qualunque funzionalità interattiva ci sia (come l'iscrizione alla newsletter) era già alimentata da JavaScript. Alla fine del 2020, abbiamo convertito questo sito dal nostro precedente CMS a Hugo e oggi funziona come un sito statico. Naturalmente, abbiamo imparato molto su Hugo durante questo processo. Questo articolo è un modo per condividere alcune delle cose che abbiamo imparato.
Il nostro esempio
Poiché questo articolo è nato dal nostro lavoro sulla conversione delle nostre pagine in Hugo, sembra naturale mettere insieme una pagina di destinazione ipotetica (molto!) semplificata come esempio. Il nostro obiettivo principale sarà un cosiddetto modello "elenco" riutilizzabile.
In breve, Hugo utilizzerà un modello di elenco per qualsiasi pagina che contenga sottopagine. C'è di più nella gerarchia dei modelli di Hugos, ma non è necessario implementare tutti i modelli possibili. Un singolo modello di elenco fa molto. Verrà utilizzato in qualsiasi situazione che richieda un modello di elenco in cui non sono disponibili modelli più specializzati.
I potenziali casi d'uso includono una home page, un indice del blog o un elenco di domande frequenti. Il nostro modello di elenco riutilizzabile risiederà in layouts/_default/list.html
nel nostro progetto. Ancora una volta, il resto dei file necessari per compilare il nostro esempio sono disponibili su GitHub, dove puoi anche dare un'occhiata migliore a come i pezzi si incastrano. Il repository GitHub include anche un modello single.html
: come suggerisce il nome, questo modello viene utilizzato per le pagine che non hanno sottopagine, ma agiscono come singoli contenuti a pieno titolo.
Ora che abbiamo preparato il terreno e spiegato cosa faremo, iniziamo!
Il contesto o "il punto"
Tutto inizia con il punto. In un modello Hugo, l'oggetto .
— “il punto” — si riferisce al contesto attuale. Cosa significa questo? Ogni modello renderizzato in Hugo ha accesso a un insieme di dati: il suo contesto . Questo è inizialmente impostato su un oggetto che rappresenta la pagina attualmente visualizzata, inclusi il suo contenuto e alcuni metadati. Il contesto include anche variabili a livello di sito come opzioni di configurazione e informazioni sull'ambiente corrente. Accederesti a un campo come il titolo della pagina corrente utilizzando .Title
e la versione di Hugo utilizzata tramite .Hugo.Version
— in altre parole, stai accedendo ai campi del file .
struttura.
È importante sottolineare che questo contesto può cambiare, facendo un riferimento come `.Title` sopra punta a qualcos'altro o addirittura rendendolo non valido. Ciò accade, ad esempio, quando si esegue il loop su una raccolta di qualche tipo utilizzando range
, o quando si suddividono i modelli in parziali e modelli di base . Vedremo questo in dettaglio più avanti!
Hugo utilizza il pacchetto "modelli" di Go, quindi quando ci riferiamo ai modelli di Hugo in questo articolo, stiamo davvero parlando di modelli di Go. Hugo aggiunge molte funzioni dei modelli non disponibili nei modelli Go standard.
Secondo me, il contesto e la possibilità di rilegarlo è una delle migliori caratteristiche di Hugo. Per me, ha molto senso che il "punto" rappresenti sempre qualsiasi oggetto sia l'obiettivo principale del mio modello a un certo punto, rilegandolo se necessario man mano che procedo. Certo, è anche possibile metterti in un pasticcio aggrovigliato, ma finora ne sono stato felice, nella misura in cui ho iniziato rapidamente a mancarlo in qualsiasi altro generatore di siti statici che ho visto.
Con questo, siamo pronti per dare un'occhiata all'umile punto di partenza del nostro esempio: il modello seguente, che risiede nella posizione layouts/_default/list.html
nel nostro progetto:
<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>
La maggior parte del modello consiste in una struttura HTML essenziale, con un collegamento a un foglio di stile, un menu per la navigazione e alcuni elementi e classi extra utilizzati per lo stile. La cosa interessante è tra le parentesi graffe , che segnalano a Hugo di intervenire e fare la sua magia, sostituendo qualsiasi cosa ci sia tra le parentesi graffe con il risultato di valutare alcune espressioni e potenzialmente anche manipolare il contesto.
Come puoi intuire, {{ .Title }}
nel tag title si riferisce al titolo della pagina corrente, mentre {{ .Site.Title }}
si riferisce al titolo dell'intero sito, impostato nella configurazione di Hugo . Un tag come {{ .Title }}
dice semplicemente a Hugo di sostituire quel tag con il contenuto del campo Title
nel contesto corrente.
Quindi, abbiamo avuto accesso ad alcuni dati appartenenti alla pagina in un modello. Da dove provengono questi dati? Questo è l'argomento della sezione seguente.
Contenuto e materia prima
Alcune delle variabili disponibili nel contesto sono fornite automaticamente da Hugo. Altri sono definiti da noi, principalmente nei file di contenuto . Esistono anche altre fonti di dati come file di configurazione, variabili di ambiente, file di dati e persino API. In questo articolo ci concentreremo sui file di contenuto come fonte di dati.
In generale, un singolo file di contenuto rappresenta una singola pagina. Un tipico file di contenuto include il contenuto principale di quella pagina ma anche metadati sulla pagina, come il titolo o la data in cui è stata creata. Hugo supporta diversi formati sia per il contenuto principale che per i metadati. In questo articolo andremo con la combinazione forse più comune: il contenuto è fornito come Markdown in un file contenente i metadati come argomento YAML.
In pratica, ciò significa che il file di contenuto inizia con una sezione delimitata da una riga contenente tre trattini a ciascuna estremità. Questa sezione costituisce l' argomento principale, e qui i metadati sono definiti utilizzando una key: value
(come vedremo presto, YAML supporta anche strutture di dati più elaborate). L'argomento introduttivo è seguito dal contenuto effettivo, specificato utilizzando il linguaggio di markup Markdown.
Rendiamo le cose più concrete guardando un esempio. Ecco un file di contenuto molto semplice con un campo introduttivo e un paragrafo di contenuto:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Questo file risiede in content/_index.md
nel nostro progetto, con _index.md
che denota il file di contenuto per una pagina che ha sottopagine. Ancora una volta, il repository GitHub chiarisce dove dovrebbe andare quale file.)
Resi utilizzando il modello di prima, insieme ad alcuni stili e file periferici (tutti trovati su GitHub), il risultato è simile al seguente:
Potresti chiederti se i nomi dei campi nella prima parte del nostro file di contenuto sono predeterminati o se possiamo aggiungere qualsiasi campo che ci piace. La risposta è "entrambi". C'è un elenco di campi predefiniti, ma possiamo anche aggiungere qualsiasi altro campo che possiamo inventare. Tuttavia, questi campi sono accessibili in modo leggermente diverso nel modello. Mentre un campo predefinito come il title
è accessibile semplicemente come .Title
, un campo personalizzato come l' author
è accessibile usando .Params.author
.
(Per un rapido riferimento sui campi predefiniti, insieme a cose come funzioni, parametri di funzione e variabili di pagina, consulta il nostro cheat sheet di Hugo!)
La variabile .Content
, utilizzata per accedere al contenuto principale dal file di contenuto nel modello, è speciale. Hugo ha una funzione "shortcode" che ti consente di utilizzare alcuni tag extra nei tuoi contenuti Markdown. Puoi anche definire il tuo. Sfortunatamente, questi shortcode funzioneranno solo tramite la variabile .Content
— mentre puoi eseguire qualsiasi altro dato tramite un filtro Markdown, questo non gestirà gli shortcode nel contenuto.
Una nota qui sulle variabili non definite: l'accesso a un campo predefinito come .Date
funziona sempre, anche se non l'hai impostato — in questo caso verrà restituito un valore vuoto. Anche l'accesso a un campo personalizzato non definito, come .Params.thisHasNotBeenSet
, funziona, restituendo un valore vuoto. Tuttavia, l'accesso a un campo di primo livello non predefinito come .thisDoesNotExist
impedirà la compilazione del sito.
Come indicato da .Params.author
così come da .Hugo.version
e .Site.title
precedenza, le chiamate concatenate possono essere utilizzate per accedere a un campo annidato in qualche altra struttura di dati. Possiamo definire tali strutture in prima linea. Diamo un'occhiata ad un esempio, dove definiamo una mappa , o dizionario, specificando alcune proprietà per un banner nella pagina nel nostro file di contenuto. Ecco il 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!
Ora aggiungiamo un banner al nostro template, facendo riferimento ai dati del banner usando .Params
nel modo descritto sopra:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Ecco come appare ora il nostro sito:
Bene! Al momento, stiamo accedendo ai campi del contesto predefinito senza problemi. Tuttavia, come accennato in precedenza, questo contesto non è fisso, ma può cambiare.
Diamo un'occhiata a come ciò potrebbe accadere.
Controllo del flusso
Le istruzioni di controllo del flusso sono una parte importante di un linguaggio di creazione di modelli, consentendoti di fare cose diverse a seconda del valore delle variabili, scorrere i dati e altro ancora. I modelli Hugo forniscono l'insieme previsto di costrutti, inclusi if/else
per la logica condizionale e l' range
per il ciclo. Qui, non tratteremo il controllo del flusso in Hugo in generale (per ulteriori informazioni, vedere la documentazione), ma ci concentreremo su come queste affermazioni influiscono sul contesto. In questo caso, le affermazioni più interessanti sono with
e range
.
Cominciamo with
. Questa istruzione controlla se un'espressione ha un valore "non vuoto" e, in tal caso, ricollega il contesto per fare riferimento al valore di quell'espressione . Un tag end
indica il punto in cui l'influenza dell'istruzione with
si interrompe e il contesto viene rimbalzato su ciò che era prima. La documentazione di Hugo definisce un valore non vuoto come false, 0 e qualsiasi array, slice, mappa o stringa di lunghezza zero.
Attualmente, il nostro modello di elenco non sta facendo molti elenchi. Potrebbe avere senso che un modello di elenco presenti effettivamente alcune delle sue sottopagine in qualche modo. Questo ci offre un'opportunità perfetta per esempi delle nostre dichiarazioni di controllo del flusso.
Forse vogliamo mostrare alcuni contenuti in primo piano nella parte superiore della nostra pagina. Potrebbe trattarsi di qualsiasi contenuto, ad esempio un post di un blog, un articolo della guida o una ricetta. In questo momento, supponiamo che il nostro sito di esempio Tower abbia alcune pagine che ne evidenziano le caratteristiche, i casi d'uso, una pagina di aiuto, una pagina di blog e una pagina di "piattaforma di apprendimento". Questi si trovano tutti nella directory content/
. Configuriamo quale contenuto presentare aggiungendo un campo nel file di contenuto per la nostra home page, content/_index.md
. La pagina è indicata dal suo percorso, assumendo la directory del contenuto come root, in questo modo:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
Successivamente, il nostro modello di elenco deve essere modificato per visualizzare questo contenuto. Hugo ha una funzione modello, .GetPage
, che ci permetterà di fare riferimento a oggetti della pagina diversi da quello che stiamo attualmente visualizzando. Ricorda come il contesto, .
, era inizialmente legato a un oggetto che rappresentava la pagina da renderizzare? Usando .GetPage
e with
, possiamo riassociare temporaneamente il contesto a un'altra pagina, facendo riferimento ai campi di quella pagina quando visualizziamo il nostro contenuto in evidenza:
<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>
Qui, {{ .Title }}
, {{ .Summary }}
e {{ .Permalink }}
tra i tag with
e end
si riferiscono a quei campi nella pagina in primo piano e non a quello principale visualizzato.
Oltre ad avere un contenuto in primo piano, elenchiamo alcuni altri contenuti più in basso. Proprio come il contenuto in evidenza, i contenuti elencati saranno definiti in content/_index.md
, il file di contenuto per la nostra home page. Aggiungeremo un elenco di percorsi di contenuto al nostro argomento in questo modo (in questo caso specificando anche il titolo della sezione):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
Il motivo per cui la pagina del blog ha una propria directory e un file _index.md
è che il blog avrà delle proprie sottopagine: i post del blog.
Per visualizzare questo elenco nel nostro modello, utilizzeremo range
. Non sorprende che questa affermazione scorrerà un elenco, ma ricollegherà anche il contesto a ciascun elemento dell'elenco a sua volta. Questo è molto conveniente per il nostro elenco di contenuti.
Nota che, dal punto di vista di Hugo, "listing" contiene solo alcune stringhe. Per ogni iterazione del ciclo "range", il contesto sarà associato a una di quelle stringhe . Per ottenere l'accesso all'oggetto della pagina reale, forniamo la sua stringa di percorso (ora il valore di .
) come argomento per .GetPage
. Quindi, useremo di nuovo l'istruzione with
per ricollegare il contesto all'oggetto della pagina elencato anziché alla sua stringa di percorso. Ora è facile visualizzare a turno il contenuto di ciascuna pagina elencata:
<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>
Ecco come appare il sito a questo punto:
Ma aspetta, c'è qualcosa di strano nel modello sopra — invece di chiamare .GetPage
, stiamo chiamando $.GetPage
. Riesci a indovinare perché .GetPage
non funzionerebbe?
La notazione .GetPage
indica che la funzione GetPage
è un metodo del contesto corrente. In effetti, nel contesto predefinito, esiste un tale metodo, ma siamo appena andati avanti e abbiamo cambiato il contesto ! Quando chiamiamo .GetPage
, il contesto è legato a una stringa, che non ha quel metodo. Il modo in cui risolviamo questo problema è l'argomento della prossima sezione.
Il contesto globale
Come visto sopra, ci sono situazioni in cui il contesto è stato modificato, ma vorremmo comunque accedere al contesto originale. Qui, è perché vogliamo chiamare un metodo esistente nel contesto originale: un'altra situazione comune è quando vogliamo accedere ad alcune proprietà della pagina principale che viene renderizzata. Nessun problema, c'è un modo semplice per farlo.
In un modello Hugo, $
, noto come contesto globale , si riferisce al valore originale del contesto, il contesto com'era quando è iniziata l'elaborazione del modello. Nella sezione precedente, è stato utilizzato per chiamare il metodo .GetPage
anche se il contesto è stato rebound a una stringa. Ora lo useremo anche per accedere a un campo della pagina in fase di rendering.
All'inizio di questo articolo, ho menzionato che il nostro modello di elenco è riutilizzabile. Finora, l'abbiamo usato solo per la home page, eseguendo il rendering di un file di contenuto che si trova in content/_index.md
. Nel repository di esempio, c'è un altro file di contenuto che verrà visualizzato utilizzando questo modello: content/blog/_index.md
. Questa è una pagina indice per il blog e, proprio come la home page, mostra un contenuto in primo piano e ne elenca alcuni altri: i post del blog, in questo caso.
Ora, supponiamo di voler mostrare il contenuto elencato in modo leggermente diverso sulla home page , non abbastanza da giustificare un modello separato, ma qualcosa che possiamo fare con un'istruzione condizionale nel modello stesso. Ad esempio, visualizzeremo il contenuto elencato in una griglia a due colonne, anziché in un elenco a colonna singola, se rileviamo che stiamo visualizzando la home page.
Hugo viene fornito con un metodo di pagina, .IsHome
, che fornisce esattamente la funzionalità di cui abbiamo bisogno. Gestiremo il cambiamento effettivo nella presentazione aggiungendo una classe ai singoli contenuti quando ci troviamo sulla home page, consentendo al nostro file CSS di fare il resto.
Potremmo, ovviamente, aggiungere la classe all'elemento body o a qualche elemento contenitore, ma ciò non consentirebbe una dimostrazione altrettanto valida del contesto globale. Nel momento in cui scriviamo l'HTML per il contenuto elencato, .
fa riferimento alla pagina elencata , ma IsHome
deve essere chiamato nella pagina principale che viene visualizzata. Il contesto globale viene in nostro soccorso:
<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>
L'indice del blog ha lo stesso aspetto della nostra home page, anche se con contenuti diversi:
…ma la nostra home page ora mostra il suo contenuto in evidenza in una griglia:
Modelli parziali
Quando crei un vero sito web, diventa rapidamente utile dividere i tuoi modelli in parti. Forse vuoi riutilizzare una parte particolare di un modello, o forse vuoi semplicemente dividere un modello enorme e ingombrante in parti coerenti. A tal fine, i modelli parziali di Hugo sono la strada da percorrere.
Dal punto di vista del contesto, la cosa importante qui è che quando includiamo un modello parziale, gli passiamo esplicitamente il contesto che vogliamo renderlo disponibile. Una pratica comune è quella di passare nel contesto così com'è quando è incluso il parziale, in questo modo: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Se il punto qui si riferisce alla pagina in fase di rendering, è ciò che verrà passato al parziale; se il contesto è stato rimbalzato su qualcos'altro, questo è ciò che viene tramandato.
Ovviamente puoi ricollegare il contesto in modelli parziali, proprio come in quelli normali. In questo caso, il contesto globale, $
, si riferisce al contesto originale passato al parziale, non alla pagina principale sottoposta a rendering (a meno che non sia ciò che è stato passato).
Se vogliamo che un modello parziale abbia accesso a un dato particolare, potremmo incorrere in problemi se passiamo solo questo al parziale. Ricordi il nostro problema in precedenza con l'accesso ai metodi della pagina dopo aver ricollegato il contesto? Lo stesso vale per partial , ma in questo caso il contesto globale non può aiutarci: se abbiamo passato, ad esempio, una stringa a un modello parziale, il contesto globale nel parziale si riferirà a quella stringa e abbiamo vinto non è possibile chiamare i metodi definiti nel contesto della pagina.
La soluzione a questo problema sta nel passare più di un dato quando si include il parziale. Tuttavia, siamo autorizzati a fornire un solo argomento alla chiamata parziale. Possiamo, tuttavia, rendere questo argomento un tipo di dati composto, comunemente una mappa (nota come dizionario o hash in altri linguaggi di programmazione).
In questa mappa, possiamo, ad esempio, avere una chiave Page
impostata sull'oggetto pagina corrente, insieme ad altre chiavi per qualsiasi dato personalizzato da trasferire. L'oggetto pagina sarà quindi disponibile come .Page
nel parziale e l'altro i valori della mappa sono accessibili in modo simile. Viene creata una mappa utilizzando la funzione dict
template, che accetta un numero pari di argomenti, interpretati alternativamente come una chiave, il suo valore, una chiave, il suo valore e così via.
Nel nostro modello di esempio, spostiamo il codice per il nostro contenuto in evidenza ed elencato in parziali. Per il contenuto in evidenza, è sufficiente passare l'oggetto pagina in evidenza. Il contenuto elencato, tuttavia, necessita dell'accesso al metodo .IsHome
oltre al particolare contenuto elencato di cui viene eseguito il rendering. Come accennato in precedenza, mentre .IsHome
è disponibile anche nell'oggetto della pagina per la pagina elencata, questo non ci darà la risposta corretta: vogliamo sapere se la pagina principale che viene visualizzata è la home page.
Potremmo invece passare un set booleano al risultato della chiamata .IsHome
, ma forse il parziale avrà bisogno di accedere ad altri metodi di pagina in futuro: andiamo con il passaggio dell'oggetto della pagina principale e dell'oggetto della pagina elencato. Nel nostro esempio, la pagina principale si trova in $
e la pagina elencata in .
. Quindi, nella mappa passata al parziale listed
, la chiave Page
ottiene il valore $
mentre la chiave "Listed" ottiene il valore .
. Questo è il modello principale aggiornato:
<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>
Il contenuto del nostro parziale "in primo piano" non cambia rispetto a quando faceva parte del modello di elenco:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Il nostro parziale per il contenuto elencato, tuttavia, riflette il fatto che l'oggetto della pagina originale si trova ora in .Page
mentre il contenuto elencato si trova in .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 fornisce anche la funzionalità del modello di base che ti consente di estendere un modello di base comune , invece di includere i modelli secondari. In questo caso, il contesto funziona in modo simile: quando si estende un modello di base, si forniscono i dati che costituiranno il contesto originale in quel modello.
Variabili personalizzate
È anche possibile assegnare e riassegnare le proprie variabili personalizzate in un modello Hugo. Questi saranno disponibili nel modello in cui sono dichiarati, ma non si faranno strada in alcun modello parziale o base a meno che non li trasmettiamo esplicitamente. Una variabile personalizzata dichiarata all'interno di un "blocco" come quella specificata da un'istruzione if
sarà disponibile solo all'interno di quel blocco — se vogliamo riferirci ad essa all'esterno del blocco, dobbiamo dichiararla all'esterno del blocco, quindi modificarla all'interno del blocco bloccare come richiesto.
Le variabili personalizzate hanno nomi preceduti dal simbolo del dollaro ( $
). Per dichiarare una variabile e assegnarle un valore allo stesso tempo, utilizzare l'operatore :=
. Successive assegnazioni alla variabile utilizzano l'operatore =
(senza due punti). Una variabile non può essere assegnata prima di essere dichiarata e non può essere dichiarata senza assegnarle un valore.
Un caso d'uso per le variabili personalizzate è la semplificazione delle chiamate di funzione lunghe assegnando un risultato intermedio a una variabile denominata in modo appropriato. Ad esempio, potremmo assegnare l'oggetto della pagina in primo piano a una variabile denominata $featured
e quindi fornire questa variabile all'istruzione with
. Potremmo anche inserire i dati da fornire al parziale "elencato" in una variabile e assegnarli alla chiamata parziale.
Ecco come sarebbe il nostro modello con queste modifiche:
<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>
Sulla base della mia esperienza con Hugo, consiglierei di utilizzare liberamente le variabili personalizzate non appena si tenta di implementare una logica più complessa in un modello. Sebbene sia naturale cercare di mantenere il codice conciso, questo potrebbe facilmente rendere le cose meno chiare di quanto potrebbero essere, confondendo te e gli altri.
Invece, usa variabili denominate in modo descrittivo per ogni passaggio e non preoccuparti di usare due righe (o tre, o quattro, ecc.) dove uno farebbe.
.Graffio
Infine, copriamo il meccanismo .Scratch
. Nelle versioni precedenti di Hugo, le variabili personalizzate potevano essere assegnate solo una volta; non è stato possibile ridefinire una variabile personalizzata. Al giorno d'oggi, le variabili personalizzate possono essere ridefinite, il che rende .Scratch
meno importante, sebbene abbia ancora i suoi usi.
In breve, .Scratch
è un'area scratch che ti consente di impostare e modificare le tue variabili , come le variabili personalizzate. A differenza delle variabili personalizzate, .Scratch
appartiene al contesto della pagina, quindi passare quel contesto a un parziale, ad esempio, porterà automaticamente le variabili scratch con esso.
Puoi impostare e recuperare variabili su .Scratch
chiamando i suoi metodi Set
e Get
. Esistono più metodi di questi, ad esempio per impostare e aggiornare i tipi di dati composti, ma questi due saranno sufficienti per le nostre esigenze qui. Set
accetta due parametri : la chiave e il valore per i dati che si desidera impostare. Get
ne richiede solo uno: la chiave per i dati che vuoi recuperare.
In precedenza, abbiamo utilizzato dict
per creare una struttura di dati della mappa per passare più dati a un parziale. Ciò è stato fatto in modo che il parziale di una pagina elencata avesse accesso sia al contesto della pagina originale che al particolare oggetto della pagina elencata. L'uso .Scratch
non è necessariamente un modo migliore o peggiore per farlo — qualunque sia preferibile può dipendere dalla situazione.
Vediamo come sarebbe il nostro modello di elenco usando .Scratch
invece di dict
per passare i dati al parziale. Chiamiamo $.Scratch.Get
(usando di nuovo il contesto globale) per impostare la variabile scratch "elencata" su .
— in questo caso, l'oggetto della pagina elencato. Quindi passiamo solo l'oggetto della pagina, $
, al parziale. Le variabili scratch seguiranno automaticamente.
<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>
Ciò richiederebbe anche alcune modifiche al parziale listed.html
: il contesto della pagina originale è ora disponibile come "il punto" mentre la pagina elencata viene recuperata dall'oggetto .Scratch
. Utilizzeremo una variabile personalizzata per semplificare l'accesso alla pagina elencata:
<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 argomento per fare le cose in questo modo è la coerenza. Usando .Scratch
, puoi prendere l'abitudine di passare sempre l'oggetto della pagina corrente a qualsiasi parziale, aggiungendo eventuali dati extra come variabili scratch. Quindi, ogni volta che scrivi o modifichi i tuoi parziali, sai che .
è un oggetto di pagina. Naturalmente, puoi anche stabilire una convenzione per te stesso usando una mappa passata: inviando sempre lungo la pagina l'oggetto come .Page
, per esempio.
Conclusione
Quando si tratta di contesto e dati, un generatore di siti statici offre vantaggi e limitazioni. Da un lato, un'operazione troppo inefficiente quando viene eseguita per ogni visita alla pagina può essere perfettamente valida se eseguita una sola volta durante la compilazione della pagina. D'altra parte, potrebbe sorprenderti quante volte sarebbe utile avere accesso a qualche parte della richiesta di rete anche su un sito prevalentemente statico.
Per gestire i parametri della stringa di query , ad esempio, su un sito statico, dovresti ricorrere a JavaScript o a qualche soluzione proprietaria come i reindirizzamenti di Netlify. Il punto qui è che mentre il passaggio da un sito dinamico a uno statico è semplice in teoria, richiede un cambiamento di mentalità. All'inizio è facile ripiegare sulle vecchie abitudini, ma la pratica renderà perfetti.
Con ciò, concludiamo il nostro sguardo alla gestione dei dati nel generatore di siti statici di 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.
Ulteriori letture
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.