Contexto e variáveis no gerador de site estático Hugo
Publicados: 2022-03-10Neste artigo, veremos como o contexto funciona no gerador de site estático Hugo. Examinaremos como os dados fluem do conteúdo para os modelos, como certas construções alteram quais dados estão disponíveis e como podemos passar esses dados para parciais e modelos base.
Este artigo não é uma introdução ao Hugo . Você provavelmente tirará o máximo proveito disso se tiver alguma experiência com Hugo, pois não vamos repassar todos os conceitos do zero, mas sim focar no tópico principal de contexto e variáveis. No entanto, se você consultar toda a documentação do Hugo, poderá acompanhar mesmo sem experiência anterior!
Estudaremos vários conceitos construindo uma página de exemplo. Nem todos os arquivos necessários para o site de exemplo serão abordados em detalhes, mas o projeto completo está disponível no GitHub. Se você quiser entender como as peças se encaixam, esse é um bom ponto de partida. Observe também que não abordaremos como configurar um site Hugo ou executar o servidor de desenvolvimento — as instruções para executar o exemplo estão no repositório.
O que é um gerador de site estático?
Se o conceito de geradores de sites estáticos é novo para você, aqui está uma rápida introdução! Os geradores de sites estáticos talvez sejam melhor descritos comparando-os com sites dinâmicos. Um site dinâmico como um CMS geralmente monta uma página do zero para cada visita, talvez buscando dados de um banco de dados e combinando vários modelos para isso. Na prática, o uso de cache significa que a página não é regenerada com tanta frequência, mas para fins dessa comparação, podemos pensar dessa maneira. Um site dinâmico é adequado para conteúdo dinâmico : conteúdo que muda com frequência, conteúdo que é apresentado em várias configurações diferentes dependendo da entrada e conteúdo que pode ser manipulado pelo visitante do site.
Em contraste, muitos sites raramente mudam e aceitam poucas informações dos visitantes. Uma seção de “ajuda” para um aplicativo, uma lista de artigos ou um eBook podem ser exemplos de tais sites. Nesse caso, faz mais sentido montar as páginas finais uma vez quando o conteúdo mudar, depois servir as mesmas páginas para todos os visitantes até que o conteúdo mude novamente.
Os sites dinâmicos têm mais flexibilidade, mas exigem mais do servidor em que estão sendo executados. Eles também podem ser difíceis de distribuir geograficamente, especialmente se os bancos de dados estiverem envolvidos. Os geradores de sites estáticos podem ser hospedados em qualquer servidor capaz de fornecer arquivos estáticos e são fáceis de distribuir.
Uma solução comum hoje, que mistura essas abordagens, é o JAMstack. “JAM” significa JavaScript, APIs e marcação e descreve os blocos de construção de um aplicativo JAMstack: um gerador de site estático gera arquivos estáticos para entrega ao cliente, mas a pilha tem um componente dinâmico na forma de JavaScript em execução no cliente — esse componente cliente pode usar APIs para fornecer funcionalidade dinâmica ao usuário.
Hugo
Hugo é um gerador de sites estáticos popular. Está escrito em Go, e o fato de Go ser uma linguagem de programação compilada sugere algumas das vantagens e desvantagens do Hugos. Por um lado, Hugo é muito rápido , o que significa que gera sites estáticos muito rapidamente. Claro, isso não tem relação com a velocidade ou lentidão dos sites criados usando Hugo para o usuário final, mas para o desenvolvedor, o fato de Hugo compilar sites grandes em um piscar de olhos é bastante valioso.
No entanto, como Hugo está escrito em uma linguagem compilada, é difícil estendê-lo . Alguns outros geradores de sites permitem que você insira seu próprio código — em linguagens como Ruby, Python ou JavaScript — no processo de compilação. Para estender o Hugo, você precisaria adicionar seu código ao próprio Hugo e recompilá-lo — caso contrário, você ficará preso às funções de modelo que o Hugo vem pronto para usar.
Embora forneça uma rica variedade de funções, esse fato pode se tornar limitante se a geração de suas páginas envolver alguma lógica complicada. Como descobrimos, tendo um site desenvolvido originalmente rodando em uma plataforma dinâmica, os casos em que você considerou a capacidade de inserir seu código personalizado como garantida tendem a se acumular.
Nossa equipe mantém uma variedade de sites relacionados ao nosso produto principal, o cliente Tower Git, e recentemente analisamos a possibilidade de mover alguns deles para um gerador de sites estáticos. Um de nossos sites, o site “Aprenda”, parecia particularmente adequado para um projeto piloto. Este site contém uma variedade de material de aprendizado gratuito, incluindo vídeos, eBooks e perguntas frequentes sobre o Git, mas também outros tópicos de tecnologia.
Seu conteúdo é em grande parte de natureza estática, e quaisquer recursos interativos que existam (como inscrições em boletins informativos) já foram alimentados por JavaScript. No final de 2020, convertemos este site do nosso CMS anterior para Hugo e hoje funciona como um site estático. Naturalmente, aprendemos muito sobre Hugo durante esse processo. Este artigo é uma maneira de compartilhar algumas das coisas que aprendemos.
Nosso Exemplo
Como este artigo surgiu do nosso trabalho na conversão de nossas páginas para Hugo, parece natural montar uma página de destino hipotética (muito!) simplificada como exemplo. Nosso foco principal será um modelo reutilizável chamado “lista”.
Resumindo, Hugo usará um modelo de lista para qualquer página que contenha subpáginas. Há mais na hierarquia de templates do Hugo do que isso, mas você não precisa implementar todos os templates possíveis. Um único modelo de lista percorre um longo caminho. Ele será usado em qualquer situação que exija um modelo de lista em que nenhum modelo mais especializado esteja disponível.
Os casos de uso potenciais incluem uma página inicial, um índice de blog ou uma lista de perguntas frequentes. Nosso modelo de lista reutilizável residirá em layouts/_default/list.html
em nosso projeto. Novamente, o restante dos arquivos necessários para compilar nosso exemplo estão disponíveis no GitHub, onde você também pode ver melhor como as peças se encaixam. O repositório do GitHub também vem com um template single.html
— como o nome sugere, esse template é usado para páginas que não possuem subpáginas, mas atuam como peças únicas de conteúdo por direito próprio.
Agora que preparamos o cenário e explicamos o que faremos, vamos começar!
O contexto ou “o ponto”
Tudo começa com o ponto. Em um modelo Hugo, o objeto .
— “o ponto” — refere-se ao contexto atual. O que isto significa? Cada template renderizado no Hugo tem acesso a um conjunto de dados — seu contexto . Isso é definido inicialmente para um objeto que representa a página que está sendo renderizada no momento, incluindo seu conteúdo e alguns metadados. O contexto também inclui variáveis de todo o site, como opções de configuração e informações sobre o ambiente atual. Você acessaria um campo como o título da página atual usando .Title
e a versão de Hugo sendo usada por meio de .Hugo.Version
— em outras palavras, você está acessando campos do .
estrutura.
É importante ressaltar que esse contexto pode mudar, fazendo uma referência como `.Title` acima apontar para outra coisa ou até mesmo torná-la inválida. Isso acontece, por exemplo, quando você faz um loop em uma coleção de algum tipo usando range
, ou quando você divide templates em parciais e templates base . Veremos isso em detalhes mais tarde!
Hugo usa o pacote Go “templates”, então quando nos referimos aos templates Hugo neste artigo, estamos falando realmente sobre os templates Go. Hugo adiciona muitas funções de template não disponíveis em templates Go padrão.
Na minha opinião, o contexto e a possibilidade de religá-lo é uma das melhores características de Hugo. Para mim, faz muito sentido sempre ter “o ponto” representando qualquer objeto que seja o foco principal do meu modelo em um determinado ponto, religando-o conforme necessário à medida que prossigo. Claro, é possível se meter em uma confusão também, mas estou feliz com isso até agora, na medida em que rapidamente comecei a sentir falta de qualquer outro gerador de site estático que eu olhei.
Com isso, estamos prontos para olhar para o humilde ponto de partida do nosso exemplo - o modelo abaixo, que reside no local layouts/_default/list.html
em nosso projeto:
<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>
A maior parte do modelo consiste em uma estrutura HTML básica, com um link de folha de estilo, um menu para navegação e alguns elementos e classes extras usados para estilizar. O interessante está entre as chaves , que sinalizam para Hugo intervir e fazer sua mágica, substituindo o que estiver entre as chaves com o resultado de avaliar alguma expressão e potencialmente manipular o contexto também.
Como você pode adivinhar, {{ .Title }}
na tag de título se refere ao título da página atual, enquanto {{ .Site.Title }}
se refere ao título de todo o site, definido na configuração do Hugo . Uma tag como {{ .Title }}
simplesmente diz a Hugo para substituir essa tag pelo conteúdo do campo Title
no contexto atual.
Então, acessamos alguns dados pertencentes à página em um template. De onde vêm esses dados? Esse é o tema da seção a seguir.
Conteúdo e Matéria Frontal
Algumas das variáveis disponíveis no contexto são fornecidas automaticamente pelo Hugo. Outros são definidos por nós, principalmente em arquivos de conteúdo . Existem também outras fontes de dados como arquivos de configuração, variáveis de ambiente, arquivos de dados e até APIs. Neste artigo, nosso foco será nos arquivos de conteúdo como fonte de dados.
Em geral, um único arquivo de conteúdo representa uma única página. Um arquivo de conteúdo típico inclui o conteúdo principal dessa página, mas também metadados sobre a página, como seu título ou a data em que foi criada. Hugo suporta vários formatos tanto para o conteúdo principal quanto para os metadados. Neste artigo, usaremos talvez a combinação mais comum: o conteúdo é fornecido como Markdown em um arquivo contendo os metadados como assunto principal do YAML.
Na prática, isso significa que o arquivo de conteúdo começa com uma seção delimitada por uma linha contendo três traços em cada extremidade. Esta seção constitui o assunto principal , e aqui os metadados são definidos usando uma key: value
(como veremos em breve, YAML também suporta estruturas de dados mais elaboradas). O assunto principal é seguido pelo conteúdo real, especificado usando a linguagem de marcação Markdown.
Vamos tornar as coisas mais concretas olhando para um exemplo. Aqui está um arquivo de conteúdo muito simples com um campo de assunto e um parágrafo de conteúdo:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Este arquivo reside em content/_index.md
em nosso projeto, com _index.md
denotando o arquivo de conteúdo para uma página que tem subpáginas. Novamente, o repositório GitHub deixa claro para onde qual arquivo deve ir.)
Renderizado usando o modelo anterior, juntamente com alguns estilos e arquivos periféricos (todos encontrados no GitHub), o resultado é assim:
Você pode estar se perguntando se os nomes dos campos no início do nosso arquivo de conteúdo são predeterminados ou se podemos adicionar qualquer campo que desejarmos. A resposta é “ambos”. Há uma lista de campos predefinidos, mas também podemos adicionar qualquer outro campo que pudermos criar. No entanto, esses campos são acessados de forma um pouco diferente no modelo. Enquanto um campo predefinido como title
é acessado simplesmente como .Title
, um campo personalizado como author
é acessado usando .Params.author
.
(Para uma referência rápida sobre os campos predefinidos, juntamente com coisas como funções, parâmetros de função e variáveis de página, veja nossa própria folha de dicas do Hugo!)
A variável .Content
, usada para acessar o conteúdo principal do arquivo de conteúdo em seu modelo, é especial. Hugo tem um recurso de “código de acesso” que permite usar algumas tags extras em seu conteúdo Markdown. Você também pode definir o seu próprio. Infelizmente, esses códigos de acesso funcionarão apenas por meio da variável .Content
— embora você possa executar qualquer outra parte dos dados por meio de um filtro Markdown, isso não lidará com os códigos de acesso no conteúdo.
Uma observação aqui sobre variáveis indefinidas: acessar um campo predefinido como .Date
sempre funciona, mesmo que você não o tenha definido — um valor vazio será retornado neste caso. Acessar um campo personalizado indefinido, como .Params.thisHasNotBeenSet
, também funciona, retornando um valor vazio. No entanto, acessar um campo de nível superior não predefinido como .thisDoesNotExist
impedirá a compilação do site.
Conforme indicado por .Params.author
, bem como por .Hugo.version
e .Site.title
anteriormente, invocações encadeadas podem ser usadas para acessar um campo aninhado em alguma outra estrutura de dados. Podemos definir tais estruturas em nossa matéria de frente. Vejamos um exemplo, onde definimos um mapa , ou dicionário, especificando algumas propriedades para um banner na página em nosso arquivo de conteúdo. Aqui está o 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!
Agora, vamos adicionar um banner ao nosso template, referenciando os dados do banner usando .Params
da forma descrita acima:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Veja como nosso site está agora:
Tudo bem! No momento, estamos acessando campos do contexto padrão sem problemas. No entanto, como mencionado anteriormente, esse contexto não é fixo, mas pode mudar.
Vamos dar uma olhada em como isso pode acontecer.
Controle de fluxo
As instruções de controle de fluxo são uma parte importante de uma linguagem de modelagem, permitindo que você faça coisas diferentes dependendo do valor das variáveis, faça um loop pelos dados e muito mais. Os modelos Hugo fornecem o conjunto esperado de construções, incluindo if/else
para lógica condicional e range
para loop. Aqui, não abordaremos o controle de fluxo no Hugo em geral (para saber mais sobre isso, consulte a documentação), mas focaremos em como essas declarações afetam o contexto. Nesse caso, as declarações mais interessantes são with
e range
.
Vamos começar with
. Essa instrução verifica se alguma expressão tem um valor “não vazio” e, se tiver, revincula o contexto para se referir ao valor dessa expressão . Uma tag end
indica o ponto em que a influência da instrução with
termina e o contexto é reencaminhado para o que era antes. A documentação do Hugo define um valor não vazio como false, 0 e qualquer array, slice, map ou string de comprimento zero.
Atualmente, nosso modelo de lista não está fazendo muitas listas. Pode fazer sentido para um modelo de lista realmente apresentar algumas de suas subpáginas de alguma forma. Isso nos dá uma oportunidade perfeita para exemplos de nossas declarações de controle de fluxo.
Talvez queiramos exibir algum conteúdo em destaque no topo de nossa página. Pode ser qualquer conteúdo – uma postagem de blog, um artigo de ajuda ou uma receita, por exemplo. Agora, digamos que nosso site de exemplo Tower tenha algumas páginas destacando seus recursos, casos de uso, uma página de ajuda, uma página de blog e uma página de “plataforma de aprendizado”. Todos eles estão localizados no diretório content/
. Configuramos qual parte do conteúdo apresentar adicionando um campo no arquivo de conteúdo para nossa página inicial, content/_index.md
. A página é referenciada por seu caminho, assumindo o diretório de conteúdo como root, assim:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
Em seguida, nosso modelo de lista deve ser modificado para exibir esse conteúdo. Hugo tem uma função de modelo, .GetPage
, que nos permitirá fazer referência a objetos de página diferentes do que estamos renderizando no momento. Lembre-se de como o contexto, .
, foi inicialmente vinculado a um objeto que representa a página que está sendo renderizada? Usando .GetPage
e with
, podemos reassociar temporariamente o contexto a outra página, referindo-se aos campos dessa página ao exibir nosso conteúdo em destaque:
<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>
Aqui, {{ .Title }}
, {{ .Summary }}
e {{ .Permalink }}
entre as tags with
e end
referem-se a esses campos na página em destaque , e não ao principal que está sendo renderizado.
Além de ter um conteúdo em destaque, vamos listar mais alguns conteúdos mais abaixo. Assim como o conteúdo em destaque, os conteúdos listados serão definidos em content/_index.md
, o arquivo de conteúdo para nossa página inicial. Adicionaremos uma lista de caminhos de conteúdo ao nosso assunto principal assim (neste caso, também especificando o título da seção):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
A razão pela qual a página do blog tem seu próprio diretório e um arquivo _index.md
é que o blog terá suas próprias subpáginas — postagens de blog.
Para exibir essa lista em nosso modelo, usaremos range
. Sem surpresa, essa instrução fará um loop sobre uma lista, mas também revinculará o contexto a cada elemento da lista por sua vez. Isso é muito conveniente para nossa lista de conteúdo.
Observe que, na perspectiva de Hugo, “listagem” contém apenas algumas strings. Para cada iteração do loop “range”, o contexto será vinculado a uma dessas strings . Para obter acesso ao objeto de página real, fornecemos sua string de caminho (agora o valor de .
) como um argumento para .GetPage
. Em seguida, usaremos a instrução with
novamente para religar o contexto ao objeto de página listado em vez de sua string de caminho. Agora, é fácil exibir o conteúdo de cada página listada por vez:
<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>
Veja como está o site neste momento:
Mas espere, há algo estranho no modelo acima - em vez de chamar .GetPage
, estamos chamando $.GetPage
. Você consegue adivinhar por que .GetPage
não funcionaria?
A notação .GetPage
indica que a função GetPage
é um método do contexto atual. De fato, no contexto padrão, existe tal método, mas apenas seguimos em frente e alteramos o contexto ! Quando chamamos .GetPage
, o contexto é vinculado a uma string, que não possui esse método. A maneira como trabalhamos em torno disso é o tópico da próxima seção.
O contexto global
Como visto acima, há situações em que o contexto foi alterado, mas ainda gostaríamos de acessar o contexto original. Aqui, é porque queremos chamar um método existente no contexto original — outra situação comum é quando queremos acessar alguma propriedade da página principal que está sendo renderizada. Não tem problema, há uma maneira fácil de fazer isso.
Em um modelo Hugo, $
, conhecido como contexto global , refere-se ao valor original do contexto — o contexto como era quando o processamento do modelo foi iniciado. Na seção anterior, ele foi usado para chamar o método .GetPage
mesmo que tivéssemos religado o contexto para uma string. Agora, também o usaremos para acessar um campo da página que está sendo renderizada.
No início deste artigo, mencionei que nosso modelo de lista é reutilizável. Até agora, usamos apenas para a página inicial, renderizando um arquivo de conteúdo localizado em content/_index.md
. No repositório de exemplo, há outro arquivo de conteúdo que será renderizado usando este modelo: content/blog/_index.md
. Esta é uma página de índice para o blog e, assim como a página inicial, ela mostra um conteúdo em destaque e lista mais alguns – postagens do blog, neste caso.
Agora, digamos que queremos mostrar o conteúdo listado de forma ligeiramente diferente na página inicial — não o suficiente para garantir um modelo separado, mas algo que podemos fazer com uma instrução condicional no próprio modelo. Como exemplo, exibiremos o conteúdo listado em uma grade de duas colunas, em vez de uma lista de coluna única, se detectarmos que estamos renderizando a página inicial.
Hugo vem com um método de página, .IsHome
, que fornece exatamente a funcionalidade que precisamos. Cuidaremos da mudança real na apresentação adicionando uma classe às partes individuais do conteúdo quando descobrirmos que estamos na página inicial, permitindo que nosso arquivo CSS faça o resto.
Poderíamos, é claro, adicionar a classe ao elemento body ou a algum elemento contido, mas isso não permitiria uma demonstração tão boa do contexto global. No momento em que escrevemos o HTML para o conteúdo listado, .
refere-se à página listada , mas IsHome
precisa ser chamado na página principal que está sendo renderizada. O contexto global vem em nosso socorro:
<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>
O índice do blog se parece com a nossa página inicial, embora com conteúdo diferente:
…mas nossa página inicial agora exibe seu conteúdo em destaque em uma grade:
Modelos parciais
Ao construir um site real, rapidamente se torna útil dividir seus modelos em partes. Talvez você queira reutilizar alguma parte específica de um modelo, ou talvez queira apenas dividir um modelo enorme e pesado em partes coerentes. Para isso, os templates parciais de Hugo são o caminho a seguir.
De uma perspectiva de contexto, o importante aqui é que, quando incluímos um modelo parcial, passamos explicitamente o contexto que queremos disponibilizar para ele. Uma prática comum é passar no contexto como está quando a parcial é incluída, assim: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Se o ponto aqui se referir à página que está sendo renderizada, é isso que será passado para a parcial; se o contexto foi reencaminhado para outra coisa, é isso que é transmitido.
Você pode, é claro, religar o contexto em templates parciais como nos normais. Nesse caso, o contexto global, $
, refere-se ao contexto original passado para o parcial, não à página principal que está sendo renderizada (a menos que seja isso que foi passado).
Se quisermos que um modelo parcial tenha acesso a algum dado específico, podemos ter problemas se passarmos apenas isso para o parcial. Lembre-se do nosso problema anterior com o acesso aos métodos da página após religar o contexto? O mesmo vale para parciais , mas neste caso o contexto global não pode nos ajudar - se passamos, digamos, uma string para um modelo parcial, o contexto global na parcial se referirá a essa string e ganhamos não pode chamar métodos definidos no contexto da página.
A solução para este problema está em passar mais de um dado ao incluir o parcial. No entanto, só podemos fornecer um argumento para a chamada parcial. Podemos, no entanto, fazer desse argumento um tipo de dado composto, comumente um mapa (conhecido como dicionário ou hash em outras linguagens de programação).
Neste mapa, podemos, por exemplo, ter uma chave Page
definida para o objeto de página atual, juntamente com outras chaves para qualquer dado personalizado a ser passado. O objeto de página estará disponível como .Page
na parcial e o outro os valores do mapa são acessados da mesma forma. Um mapa é criado usando a função de modelo dict
, que recebe um número par de argumentos, interpretados alternadamente como uma chave, seu valor, uma chave, seu valor e assim por diante.
Em nosso modelo de exemplo, vamos mover o código de nosso conteúdo em destaque e listado para parciais. Para o conteúdo em destaque, basta passar o objeto da página em destaque. O conteúdo listado, no entanto, precisa de acesso ao método .IsHome
além do conteúdo listado específico que está sendo renderizado. Como mencionado anteriormente, embora .IsHome
esteja disponível no objeto de página para a página listada, isso não nos dará a resposta correta — queremos saber se a página principal que está sendo renderizada é a página inicial.
Em vez disso, poderíamos passar um conjunto booleano para o resultado da chamada .IsHome
, mas talvez o parcial precise de acesso a outros métodos de página no futuro — vamos passar o objeto de página principal , bem como o objeto de página listado. Em nosso exemplo, a página principal é encontrada em $
e a página listada em .
. Assim, no mapa passado para a parcial listed
, a chave Page
recebe o valor $
enquanto a chave “Listed” recebe o valor .
. Este é o template principal atualizado:
<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>
O conteúdo da nossa parcial “em destaque” não muda em comparação com quando fazia parte do modelo de lista:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Nossa parcial para o conteúdo listado, no entanto, reflete o fato de que o objeto de página original agora é encontrado em .Page
enquanto o conteúdo listado é encontrado em .Listed
:
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
O Hugo também fornece a funcionalidade de modelo base que permite estender um modelo base comum , em vez de incluir submodelos. Nesse caso, o contexto funciona de forma semelhante: ao estender um template base, você fornece os dados que constituirão o contexto original naquele template.
Variáveis personalizadas
Também é possível atribuir e reatribuir suas próprias variáveis personalizadas em um modelo Hugo. Eles estarão disponíveis no modelo em que são declarados, mas não entrarão em nenhum modelo parcial ou base, a menos que os passemos explicitamente. Uma variável personalizada declarada dentro de um “bloco” como a especificada por uma instrução if
só estará disponível dentro desse bloco — se quisermos nos referir a ela fora do bloco, precisamos declará-la fora do bloco e modificá-la dentro do bloco. bloquear conforme necessário.
Variáveis personalizadas têm nomes prefixados por um cifrão ( $
). Para declarar uma variável e dar-lhe um valor ao mesmo tempo, use o operador :=
. Atribuições subsequentes à variável usam o operador =
(sem dois pontos). Uma variável não pode ser atribuída antes de ser declarada e não pode ser declarada sem dar um valor a ela.
Um caso de uso para variáveis personalizadas é simplificar chamadas de função longas atribuindo algum resultado intermediário a uma variável nomeada apropriadamente. Por exemplo, poderíamos atribuir o objeto de página em destaque a uma variável chamada $featured
e, em seguida, fornecer essa variável à instrução with
. Também poderíamos colocar os dados para fornecer à parcial “listada” em uma variável e dar isso à chamada parcial.
Veja como ficaria nosso modelo com essas alterações:
<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>
Com base na minha experiência com Hugo, recomendo usar variáveis personalizadas de forma liberal assim que você estiver tentando implementar alguma lógica mais envolvida em um modelo. Embora seja natural tentar manter seu código conciso, isso pode facilmente tornar as coisas menos claras do que poderiam ser, confundindo você e os outros.
Em vez disso, use variáveis nomeadas descritivamente para cada etapa e não se preocupe em usar duas linhas (ou três, ou quatro, etc.) onde seria necessário.
.Arranhar
Finalmente, vamos cobrir o mecanismo .Scratch
. Nas versões anteriores do Hugo, as variáveis personalizadas só podiam ser atribuídas uma vez; não foi possível redefinir uma variável personalizada. Hoje em dia, variáveis personalizadas podem ser redefinidas, o que torna .Scratch
menos importante, embora ainda tenha seus usos.
Resumindo, .Scratch
é uma área de rascunho que permite definir e modificar suas próprias variáveis , como variáveis personalizadas. Ao contrário das variáveis personalizadas, .Scratch
pertence ao contexto da página, portanto, passar esse contexto para uma parcial, por exemplo, trará as variáveis de rascunho junto com ele automaticamente.
Você pode definir e recuperar variáveis no .Scratch
chamando seus métodos Set
e Get
. Existem mais métodos do que esses, por exemplo, para definir e atualizar tipos de dados compostos, mas esses dois serão suficientes para nossas necessidades aqui. Set
leva dois parâmetros : a chave e o valor para os dados que você deseja definir. Get
leva apenas um: a chave para os dados que você deseja recuperar.
Anteriormente, usamos dict
para criar uma estrutura de dados de mapa para passar vários dados para um parcial. Isso foi feito para que a parcial de uma página listada tivesse acesso ao contexto da página original e ao objeto de página listado específico. Usar .Scratch
não é necessariamente uma maneira melhor ou pior de fazer isso — o que for preferível pode depender da situação.
Vamos ver como ficaria nosso modelo de lista usando .Scratch
em vez de dict
para passar dados para o parcial. Chamamos $.Scratch.Get
(novamente usando o contexto global) para definir a variável de rascunho “listed” como .
— neste caso, o objeto de página listado. Em seguida, passamos apenas o objeto de página, $
, para o parcial. As variáveis de rascunho seguirão 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>
Isso exigiria alguma modificação na parcial listed.html
também — o contexto da página original agora está disponível como “o ponto” enquanto a página listada é recuperada do objeto .Scratch
. Usaremos uma variável personalizada para simplificar o acesso à página listada:
<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>
Um argumento para fazer as coisas dessa maneira é a consistência. Usando .Scratch
, você pode criar o hábito de sempre passar o objeto da página atual para qualquer parcial, adicionando quaisquer dados extras como variáveis de rascunho. Então, sempre que você escreve ou edita suas parciais, você sabe que .
é um objeto de página. Claro, você também pode estabelecer uma convenção para si mesmo usando um mapa passado: sempre enviando junto o objeto de página como .Page
, por exemplo.
Conclusão
Quando se trata de contexto e dados, um gerador de site estático traz benefícios e limitações. Por um lado, uma operação que é muito ineficiente quando executada para cada visita à página pode ser perfeitamente boa quando executada apenas uma vez enquanto a página é compilada. Por outro lado, você pode se surpreender com a frequência com que seria útil ter acesso a alguma parte da solicitação de rede, mesmo em um site predominantemente estático.
Para lidar com parâmetros de query string , por exemplo, em um site estático, você teria que recorrer ao JavaScript ou alguma solução proprietária como os redirecionamentos da Netlify. O ponto aqui é que, embora o salto de um site dinâmico para um estático seja simples em teoria, é preciso uma mudança de mentalidade. No começo, é fácil voltar aos velhos hábitos, mas a prática levará à perfeição.
Com isso, concluímos nosso olhar sobre o gerenciamento de dados no gerador de site estático 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.
Leitura adicional
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.