Контекст и переменные в генераторе статических сайтов Hugo
Опубликовано: 2022-03-10В этой статье мы подробно рассмотрим, как контекст работает в генераторе статических сайтов Hugo. Мы рассмотрим, как данные передаются из содержимого в шаблоны, как определенные конструкции изменяют доступные данные и как мы можем передавать эти данные в частичные и базовые шаблоны.
Эта статья не является введением в Хьюго . Вы, вероятно, получите максимальную отдачу от этого, если у вас есть некоторый опыт работы с Hugo, поскольку мы не будем рассматривать каждую концепцию с нуля, а сосредоточимся на основной теме контекста и переменных. Однако, если вы будете ссылаться на документацию Hugo повсюду, вы вполне сможете следовать ей даже без предыдущего опыта!
Мы изучим различные концепции, создав пример страницы. Не каждый отдельный файл, необходимый для примера сайта, будет подробно рассмотрен, но весь проект доступен на GitHub. Если вы хотите понять, как части сочетаются друг с другом, это хорошая отправная точка. Также обратите внимание, что мы не будем рассказывать, как настроить сайт Hugo или запустить сервер разработки — инструкции по запуску примера находятся в репозитории.
Что такое генератор статических сайтов?
Если концепция генераторов статических сайтов для вас нова, вот краткое введение! Генераторы статических сайтов лучше всего описываются сравнением их с динамическими сайтами. Динамический сайт, такой как CMS, обычно собирает страницу с нуля для каждого посещения, возможно, извлекая данные из базы данных и комбинируя для этого различные шаблоны. На практике использование кэширования означает, что страница регенерируется не так часто, но для целей этого сравнения мы можем думать об этом именно так. Динамический сайт хорошо подходит для динамического контента : контента, который часто меняется, контента, представленного во множестве различных конфигураций в зависимости от ввода, и контента, которым может манипулировать посетитель сайта.
Напротив, многие сайты редко меняются и принимают мало информации от посетителей. Примерами таких сайтов могут быть раздел «помощь» для приложения, список статей или электронная книга. В этом случае имеет смысл собрать конечные страницы один раз при изменении контента, а затем показывать одни и те же страницы каждому посетителю до тех пор, пока контент не изменится снова.
Динамические сайты обладают большей гибкостью, но предъявляют более высокие требования к серверу, на котором они работают. Их также может быть трудно распределить географически, особенно если речь идет о базах данных. Генераторы статических сайтов могут размещаться на любом сервере, способном доставлять статические файлы, и их легко распространять.
Распространенным сегодня решением, сочетающим эти подходы, является JAMstack. «JAM» означает JavaScript, API и разметку и описывает строительные блоки приложения JAMstack: генератор статических сайтов генерирует статические файлы для доставки клиенту, но стек имеет динамический компонент в виде JavaScript, работающего на клиенте — затем этот клиентский компонент может использовать API для предоставления пользователю динамических функций.
Хьюго
Hugo — популярный генератор статических сайтов. Он написан на Go, и тот факт, что Go является компилируемым языком программирования, намекает на некоторые преимущества и недостатки Hugo. Во-первых, Hugo очень быстрый , а это означает, что он очень быстро генерирует статические сайты. Конечно, это не имеет никакого отношения к тому, насколько быстры или медленны сайты, созданные с помощью Hugo, для конечного пользователя, но для разработчика тот факт, что Hugo компилирует даже большие сайты в мгновение ока, весьма ценен.
Однако, поскольку Hugo написан на компилируемом языке, расширить его сложно . Некоторые другие генераторы сайтов позволяют вам вставлять свой собственный код — на таких языках, как Ruby, Python или JavaScript — в процесс компиляции. Чтобы расширить Hugo, вам нужно будет добавить свой код в сам Hugo и перекомпилировать его — в противном случае вы застрянете с функциями шаблона, которые Hugo поставляется из коробки.
Хотя он предоставляет широкий спектр функций, этот факт может стать ограничивающим, если создание ваших страниц связано с какой-то сложной логикой. Как мы обнаружили, при наличии сайта, изначально разработанного для работы на динамической платформе, случаи, когда вы восприняли возможность добавления своего пользовательского кода как должное, имеют тенденцию накапливаться.
Наша команда поддерживает множество веб-сайтов, связанных с нашим основным продуктом, клиентом Tower Git, и недавно мы рассматривали возможность переноса некоторых из них на генератор статических сайтов. Один из наших сайтов, сайт «Обучение», выглядел особенно подходящим для пилотного проекта. Этот сайт содержит множество бесплатных учебных материалов, включая видео, электронные книги и часто задаваемые вопросы по Git, а также другие технические темы.
Его контент в основном статичен, и любые интерактивные функции (например, подписка на новостную рассылку) уже работают на JavaScript. В конце 2020 года мы перевели этот сайт с нашей предыдущей CMS на Hugo , и сегодня он работает как статический сайт. Естественно, в ходе этого процесса мы многое узнали о Хьюго. Эта статья — способ поделиться некоторыми вещами, которые мы узнали.
Наш пример
Поскольку эта статья выросла из нашей работы по преобразованию наших страниц в Hugo, кажется естественным собрать (очень!) упрощенную гипотетическую целевую страницу в качестве примера. Наше основное внимание будет сосредоточено на многоразовом так называемом шаблоне «список».
Короче говоря, Hugo будет использовать шаблон списка для любой страницы, содержащей подстраницы. В иерархии шаблонов Hugos есть нечто большее, но вам не нужно реализовывать все возможные шаблоны. Единый шаблон списка имеет большое значение. Он будет использоваться в любой ситуации, требующей шаблона списка, когда нет более специализированного шаблона.
Потенциальные варианты использования включают домашнюю страницу, указатель блога или список часто задаваемых вопросов. Наш повторно используемый шаблон списка будет находиться в layouts/_default/list.html
нашего проекта. Опять же, остальные файлы, необходимые для компиляции нашего примера, доступны на GitHub, где вы также можете лучше понять, как эти части сочетаются друг с другом. Репозиторий GitHub также поставляется с шаблоном single.html
— как следует из названия, этот шаблон используется для страниц, которые не имеют подстраниц, но действуют как отдельные части контента сами по себе.
Теперь, когда мы подготовили сцену и объяснили, что мы будем делать, давайте начнем!
Контекст или «точка»
Все начинается с точки. В шаблоне Hugo объект .
— «точка» — относится к текущему контексту. Что это значит? Каждый шаблон, отображаемый в Hugo, имеет доступ к набору данных — своему контексту . Изначально это объект, представляющий отображаемую в данный момент страницу, включая ее содержимое и некоторые метаданные. Контекст также включает переменные для всего сайта, такие как параметры конфигурации и информацию о текущей среде. Вы бы получили доступ к такому полю, как заголовок текущей страницы, используя .Title
а используемая версия Hugo — через .Hugo.Version
— другими словами, вы получаете доступ к полям файла .
структура.
Важно отметить, что этот контекст может меняться, делая ссылку, например `.Title` выше, указывающей на что-то другое или даже делая ее недействительной. Это происходит, например, когда вы перебираете какую-то коллекцию с помощью range
или когда вы разделяете шаблоны на частичные и базовые шаблоны . Мы рассмотрим это подробно позже!
Hugo использует пакет «шаблонов» Go, поэтому, когда мы ссылаемся на шаблоны Hugo в этой статье, мы действительно говорим о шаблонах Go. Hugo добавляет множество шаблонных функций, недоступных в стандартных шаблонах Go.
На мой взгляд, контекст и возможность пересвязать его — одна из лучших особенностей Хьюго. Для меня имеет большой смысл всегда иметь «точку», представляющую любой объект, который является основным фокусом моего шаблона в определенный момент, перепривязывая его по мере необходимости по мере продвижения. Конечно, вы также можете попасть в запутанный беспорядок, но я был доволен этим до такой степени, что быстро начал терять его в любом другом генераторе статических сайтов, на который я смотрел.
Теперь мы готовы взглянуть на скромную отправную точку нашего примера — шаблон ниже, расположенный в layouts/_default/list.html
в нашем проекте:
<html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>
Большая часть шаблона состоит из простой HTML-структуры со ссылкой на таблицу стилей, меню для навигации и некоторыми дополнительными элементами и классами, используемыми для стилей. Самое интересное находится между фигурными скобками , которые сигнализируют Хьюго о том, что нужно вмешаться и творить чудеса, заменяя все, что находится между фигурными скобками, результатом оценки некоторого выражения и потенциального манипулирования контекстом.
Как вы могли догадаться, {{ .Title }}
в теге title относится к заголовку текущей страницы, а {{ .Site.Title }}
относится к заголовку всего сайта, установленному в конфигурации Hugo. . Такой тег, как {{ .Title }}
, просто говорит Hugo заменить этот тег содержимым поля Title
в текущем контексте.
Итак, мы получили доступ к некоторым данным , принадлежащим странице в шаблоне. Откуда берутся эти данные? Это тема следующего раздела.
Содержание и вступительная часть
Некоторые из переменных, доступных в контексте, автоматически предоставляются Hugo. Другие определяются нами, в основном в файлах содержимого . Существуют также другие источники данных, такие как файлы конфигурации, переменные среды, файлы данных и даже API. В этой статье мы сосредоточимся на файлах содержимого как на источнике данных.
Как правило, один файл содержимого представляет собой одну страницу. Типичный файл содержимого включает в себя основной контент этой страницы, а также метаданные о странице, такие как ее заголовок или дата ее создания. Hugo поддерживает несколько форматов как для основного контента, так и для метаданных. В этой статье мы рассмотрим, пожалуй, наиболее распространенную комбинацию: контент предоставляется в формате Markdown в файле, содержащем метаданные в виде вводной части YAML.
На практике это означает, что файл содержимого начинается с раздела, разделенного строкой, содержащей три тире на каждом конце. Этот раздел представляет собой вводную часть , и здесь метаданные определяются с использованием синтаксиса key: value
(как мы скоро увидим, YAML также поддерживает более сложные структуры данных). За вступительной частью следует фактическое содержимое, указанное с использованием языка разметки Markdown.
Давайте сделаем вещи более конкретными, взглянув на пример. Вот очень простой файл содержимого с одним полем вступительной части и одним абзацем содержимого:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Этот файл находится в content/_index.md
в нашем проекте, где _index.md
обозначает файл содержимого для страницы с подстраницами. Опять же, репозиторий GitHub дает понять, куда какой файл должен идти.)
При визуализации с использованием предыдущего шаблона вместе с некоторыми стилями и периферийными файлами (все найдено на GitHub) результат выглядит следующим образом:
Вы можете задаться вопросом, предопределены ли имена полей в начале нашего файла содержимого или мы можем добавить любое поле, которое нам нравится. Ответ «оба». Существует список предопределенных полей, но мы также можем добавить любое другое поле, какое только сможем придумать. Однако доступ к этим полям в шаблоне осуществляется немного по-другому. В то время как доступ к предопределенным полям, таким как title
, осуществляется просто как .Title
, доступ к настраиваемым полям, таким как author
, осуществляется с помощью .Params.author
.
(Для краткого ознакомления с предопределенными полями, а также такими вещами, как функции, параметры функций и переменные страницы, см. нашу собственную шпаргалку Hugo!)
Переменная .Content
, используемая для доступа к основному содержимому из файла содержимого в вашем шаблоне, является специальной. У Hugo есть функция «шорткода», позволяющая использовать дополнительные теги в вашем контенте Markdown. Вы также можете определить свои собственные. К сожалению, эти шорткоды будут работать только через переменную .Content
— хотя вы можете запустить любой другой фрагмент данных через фильтр Markdown, он не будет обрабатывать шорткоды в контенте.
Обратите внимание на неопределенные переменные: доступ к предварительно определенному полю, такому как .Date
, всегда работает, даже если вы его не установили — в этом случае будет возвращено пустое значение. Доступ к неопределенным настраиваемым полям, таким как .Params.thisHasNotBeenSet
, также работает, возвращая пустое значение. Однако доступ к непредопределенным полям верхнего уровня, таким как .thisDoesNotExist
, предотвратит компиляцию сайта.
Как указывалось .Params.author
, а также .Hugo.version
и .Site.title
ранее, цепные вызовы могут использоваться для доступа к полю, вложенному в какую-либо другую структуру данных. Мы можем определить такие структуры в нашем начальном материале. Давайте рассмотрим пример, где мы определяем карту или словарь, указав некоторые свойства для баннера на странице в нашем файле содержимого. Вот обновленный 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!
Теперь давайте добавим баннер в наш шаблон, ссылаясь на данные баннера, используя .Params
, как описано выше:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Вот как сейчас выглядит наш сайт:
Хорошо! На данный момент мы без проблем обращаемся к полям контекста по умолчанию. Однако, как упоминалось ранее, этот контекст не является фиксированным, а может меняться.
Давайте посмотрим, как это может произойти.
Управление потоком
Операторы управления потоком являются важной частью языка шаблонов, позволяя вам делать разные вещи в зависимости от значения переменных, циклически обрабатывать данные и многое другое. Шаблоны Hugo предоставляют ожидаемый набор конструкций, включая if/else
для условной логики и range
для циклов. Здесь мы не будем рассматривать управление потоком в Hugo в целом (подробнее об этом см. в документации), а сосредоточимся на том, как эти операторы влияют на контекст. В этом случае наиболее интересны операторы with
и range
.
Начнем с with
. Этот оператор проверяет, имеет ли какое-либо выражение «непустое» значение, и, если оно есть, повторно привязывает контекст, чтобы ссылаться на значение этого выражения . end
тег указывает точку, в которой прекращается влияние оператора with
, и контекст возвращается к тому, что было раньше. Документация Hugo определяет непустое значение как false, 0 и любой массив нулевой длины, срез, карту или строку.
В настоящее время наш шаблон списка вообще не содержит большого количества листингов. Для шаблона списка может иметь смысл каким-то образом отображать некоторые из его подстраниц . Это дает нам прекрасную возможность для примеров наших операторов управления потоком.
Возможно, мы хотим отобразить избранный контент в верхней части нашей страницы. Это может быть любой элемент контента — например, запись в блоге, справочная статья или рецепт. Прямо сейчас предположим, что на нашем примере сайта Tower есть несколько страниц с описанием его функций, вариантов использования, страница справки, страница блога и страница «учебной платформы». Все они находятся в каталоге content/
. Мы настраиваем, какую часть контента отображать, добавляя поле в файл контента для нашей домашней страницы, content/_index.md
. На страницу ссылаются по ее пути, предполагая, что каталог содержимого является корневым, например:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
Затем наш шаблон списка должен быть изменен для отображения этого фрагмента контента. У Hugo есть функция шаблона .GetPage
, которая позволит нам ссылаться на объекты страницы, отличные от той, которую мы сейчас отображаем. Вспомните, как контекст, .
, изначально был привязан к объекту, представляющему отображаемую страницу? Используя .GetPage
и with
, мы можем временно привязать контекст к другой странице, ссылаясь на поля этой страницы при отображении нашего избранного контента:
<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>
Здесь {{ .Title }}
, {{ .Summary }}
и {{ .Permalink }}
между тегами with
и end
относятся к этим полям на избранной странице , а не к основному отображаемому.
В дополнение к избранному фрагменту контента, давайте перечислим еще несколько фрагментов контента ниже. Как и рекомендуемый контент, перечисленные фрагменты контента будут определены в content/_index.md
, файле контента для нашей домашней страницы. Мы добавим список путей к содержимому в нашу вступительную статью следующим образом (в данном случае также указав заголовок раздела):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
Причина, по которой страница блога имеет свой собственный каталог и файл _index.md
, заключается в том, что у блога будут собственные подстраницы — посты блога.
Чтобы отобразить этот список в нашем шаблоне, мы будем использовать range
. Неудивительно, что этот оператор будет циклически перебирать список, но он также по очереди перепривязывает контекст к каждому элементу списка. Это очень удобно для нашего списка контента.
Обратите внимание, что, с точки зрения Хьюго, «список» содержит только некоторые строки. Для каждой итерации цикла «диапазон» контекст будет привязан к одной из этих строк . Чтобы получить доступ к фактическому объекту страницы, мы передаем его строку пути (теперь значение .
) в качестве аргумента для .GetPage
. Затем мы снова воспользуемся оператором with
, чтобы повторно привязать контекст к указанному объекту страницы, а не к его строке пути. Теперь легко отобразить содержимое каждой страницы в списке по очереди:
<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>
Вот как сайт выглядит на данный момент:
Но подождите, в приведенном выше шаблоне есть что-то странное — вместо вызова .GetPage
мы вызываем $.GetPage
. Можете ли вы догадаться, почему .GetPage
не работает?
Обозначение .GetPage
указывает, что функция GetPage
является методом текущего контекста. Действительно, в контексте по умолчанию есть такой метод, но мы просто пошли дальше и изменили контекст ! Когда мы вызываем .GetPage
, контекст привязывается к строке, которая не имеет этого метода. То, как мы работаем с этим, является темой следующего раздела.
Глобальный контекст
Как видно выше, бывают ситуации, когда контекст был изменен, но мы все равно хотели бы получить доступ к исходному контексту. Здесь это потому, что мы хотим вызвать метод, существующий в исходном контексте — другая распространенная ситуация — это когда мы хотим получить доступ к некоторому свойству отображаемой главной страницы. Нет проблем, есть простой способ сделать это.
В шаблоне Hugo $
, известный как глобальный контекст , относится к исходному значению контекста — контексту, который был, когда началась обработка шаблона. В предыдущем разделе он использовался для вызова метода .GetPage
, несмотря на то, что мы повторно привязали контекст к строке. Теперь мы также будем использовать его для доступа к полю отображаемой страницы.
В начале этой статьи я упомянул, что наш шаблон списка можно использовать повторно. До сих пор мы использовали его только для домашней страницы, отображая файл содержимого, расположенный по адресу content/_index.md
. В репозитории примера есть еще один файл контента, который будет отображаться с использованием этого шаблона: content/blog/_index.md
. Это индексная страница для блога, и, как и на домашней странице, она показывает избранный контент и перечисляет еще несколько — в данном случае посты в блоге.
Теперь предположим, что мы хотим немного по-другому отображать перечисленный контент на домашней странице — недостаточно, чтобы требовать отдельного шаблона, но мы можем сделать это с помощью условного оператора в самом шаблоне. Например, мы будем отображать перечисленное содержимое в сетке из двух столбцов, а не в списке из одного столбца, если обнаружим, что отображаем домашнюю страницу.
Hugo поставляется с методом страницы .IsHome
, который обеспечивает именно ту функциональность, которая нам нужна. Мы обработаем фактическое изменение в представлении, добавив класс к отдельным частям контента, когда обнаружим, что находимся на домашней странице, позволив нашему файлу CSS сделать все остальное.
Мы могли бы, конечно, добавить класс к элементу body или к некоторому содержащему элементу, но это не позволило бы так же хорошо продемонстрировать глобальный контекст. К тому времени, когда мы напишем HTML для указанного фрагмента контента, .
ссылается на указанную страницу , но IsHome
необходимо вызывать на отображаемой главной странице. Нам на помощь приходит глобальный контекст:
<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>
Индекс блога выглядит так же, как и наша домашняя страница, хотя и с другим содержанием:
… но наша домашняя страница теперь отображает рекомендуемый контент в сетке:
Частичные шаблоны
При создании реального веб-сайта быстро становится полезным разделить ваши шаблоны на части. Возможно, вы хотите повторно использовать какую-то определенную часть шаблона или просто хотите разделить огромный громоздкий шаблон на согласованные части. Для этой цели лучше всего подходят частичные шаблоны Hugo.
С точки зрения контекста здесь важно то, что когда мы включаем частичный шаблон, мы явно передаем ему контекст , который мы хотим сделать доступным для него. Обычной практикой является передача контекста таким, какой он есть, когда включен партиал, например: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Если точка здесь относится к отображаемой странице, это то, что будет передано партиалу; если контекст был возвращен к чему-то другому, это то, что было передано.
Вы можете, конечно, перепривязать контекст в частичных шаблонах так же, как и в обычных. В этом случае глобальный контекст, $
, относится к исходному контексту, переданному частичному, а не к отображаемой основной странице (если это не то, что было передано).
Если мы хотим, чтобы частичный шаблон имел доступ к какой-то конкретной части данных, мы можем столкнуться с проблемами, если передадим только это в частичный. Помните нашу проблему с доступом к методам страницы после повторной привязки контекста? То же самое касается и партиалов , но в этом случае глобальный контекст нам не поможет — если мы передали, скажем, строку в шаблон партиала, глобальный контекст в партиале будет ссылаться на эту строку, и мы выиграли. не иметь возможности вызывать методы, определенные в контексте страницы.
Решение этой проблемы заключается в передаче более одного фрагмента данных при включении партиала. Однако нам разрешено предоставлять только один аргумент для частичного вызова. Однако мы можем сделать этот аргумент составным типом данных, обычно картой (известной как словарь или хэш в других языках программирования).
На этой карте мы можем, например, установить ключ Page
для текущего объекта страницы вместе с другими ключами для передачи любых пользовательских данных. Затем объект страницы будет доступен как .Page
в частичном, а другой Доступ к значениям карты осуществляется аналогичным образом. Карта создается с помощью шаблонной функции dict
, которая принимает четное количество аргументов, интерпретируемых поочередно как ключ, его значение, ключ, его значение и так далее.
В нашем примере шаблона давайте переместим код для рекомендуемого и перечисленного контента в частичные. Для рекомендуемого контента достаточно передать объект рекомендуемой страницы. Однако указанному содержимому требуется доступ к методу .IsHome
в дополнение к конкретному отображаемому содержимому. Как упоминалось ранее, хотя .IsHome
доступен и в объекте страницы для указанной страницы, это не даст нам правильного ответа — мы хотим знать, является ли отображаемая главная страница домашней страницей.
Вместо этого мы могли бы передать логическое множество в результат вызова .IsHome
, но, возможно, частичному элементу потребуется доступ к другим методам страницы в будущем — давайте передадим объект главной страницы, а также объект страницы в списке. В нашем примере главная страница находится в $
, а перечисленная страница — в .
. Таким образом, в карте, переданной listed
из списка, ключ Page
получает значение $
, а ключ «Listed» получает значение .
. Это обновленный основной шаблон:
<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>
Содержимое нашего «избранного» партиала не изменилось по сравнению с тем, когда оно было частью шаблона списка:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Наш частичный контент для перечисленного контента, однако, отражает тот факт, что исходный объект страницы теперь находится в .Page
, а указанный фрагмент контента находится в .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 также предоставляет функциональные возможности базового шаблона, которые позволяют расширять общий базовый шаблон , а не включать подшаблоны. В этом случае контекст работает аналогично: при расширении базового шаблона вы предоставляете данные, которые будут составлять исходный контекст в этом шаблоне.
Пользовательские переменные
Также можно назначать и переназначать собственные пользовательские переменные в шаблоне Hugo. Они будут доступны в шаблоне, где они объявлены, но не попадут в какие-либо частичные или базовые шаблоны, если мы не передадим их явно. Пользовательская переменная , объявленная внутри «блока» , например та, которая указана в операторе if
, будет доступна только внутри этого блока — если мы хотим сослаться на нее вне блока, нам нужно объявить ее вне блока, а затем изменить внутри блока. блокировать по мере необходимости.
Имя пользовательских переменных начинается со знака доллара ( $
). Чтобы объявить переменную и одновременно присвоить ей значение, используйте оператор :=
. Последующие присвоения переменной используют оператор =
(без двоеточия). Переменная не может быть присвоена до объявления, и ее нельзя объявить, не присвоив ей значение.
Одним из вариантов использования пользовательских переменных является упрощение длинных вызовов функций путем присвоения некоторого промежуточного результата переменной с соответствующим именем. Например, мы могли бы присвоить объект избранной страницы переменной с именем $featured
, а затем передать эту переменную оператору with
. Мы также можем поместить данные для «перечисленного» партиала в переменную и передать это частичному вызову.
Вот как будет выглядеть наш шаблон с этими изменениями:
<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>
Основываясь на своем опыте работы с Hugo, я бы рекомендовал свободно использовать пользовательские переменные , как только вы пытаетесь реализовать в шаблоне более сложную логику. Хотя вполне естественно пытаться сделать свой код кратким, это может легко сделать вещи менее ясными, чем они могли бы быть, запутав вас и других.
Вместо этого используйте переменные с описательными именами для каждого шага и не беспокойтесь об использовании двух строк (или трех, или четырех, и т. д.) вместо одной.
.Царапать
Наконец, давайте рассмотрим механизм .Scratch
. В более ранних версиях Hugo пользовательские переменные можно было назначить только один раз; было невозможно переопределить пользовательскую переменную. В настоящее время пользовательские переменные могут быть переопределены, что делает .Scratch
менее важным, хотя он по-прежнему используется.
Короче говоря, .Scratch
— это временная область, позволяющая вам устанавливать и изменять свои собственные переменные , такие как пользовательские переменные. В отличие от пользовательских переменных, .Scratch
принадлежит контексту страницы, поэтому передача этого контекста, например, партиалу автоматически принесет вместе с ним временные переменные.
Вы можете устанавливать и извлекать переменные в .Scratch
, вызывая его методы Set
и Get
. Есть и другие методы, например, для установки и обновления составных типов данных, но этих двух будет достаточно для наших нужд. Set
принимает два параметра : ключ и значение для данных, которые вы хотите установить. Get
принимает только одно: ключ для данных, которые вы хотите получить.
Ранее мы использовали dict
для создания структуры данных карты для передачи нескольких фрагментов данных в партиал. Это было сделано для того, чтобы партиал для страницы в списке имел доступ как к исходному контексту страницы, так и к конкретному объекту страницы в списке. Использование .Scratch
не обязательно лучший или худший способ сделать это — какой из них предпочтительнее, может зависеть от ситуации.
Давайте посмотрим, как будет выглядеть наш шаблон списка, используя .Scratch
вместо dict
для передачи данных в партиал. Мы вызываем $.Scratch.Get
(снова используя глобальный контекст), чтобы установить временную переменную «в списке» на .
— в данном случае указанный объект страницы. Затем мы передаем только объект страницы $
в партиал. Временные переменные будут следовать автоматически.
<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>
Это также потребует некоторых изменений в listed.html
— исходный контекст страницы теперь доступен как «точка», в то время как указанная страница извлекается из объекта .Scratch
. Мы будем использовать пользовательскую переменную, чтобы упростить доступ к указанной странице:
<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>
Одним из аргументов в пользу такого подхода является последовательность. Используя .Scratch
, вы можете сделать привычкой всегда передавать текущий объект страницы любому частичному элементу, добавляя любые дополнительные данные в качестве временных переменных. Затем, всякий раз, когда вы пишете или редактируете частичные фрагменты, вы знаете, что .
является объектом страницы. Конечно, вы также можете установить соглашение для себя, используя переданную карту: например, всегда отправлять объект страницы как .Page
.
Заключение
Когда дело доходит до контекста и данных, генератор статических сайтов имеет как преимущества, так и ограничения. С одной стороны, операция, которая слишком неэффективна при выполнении при каждом посещении страницы, может быть совершенно хорошей, если выполнять ее только один раз при компиляции страницы. С другой стороны, вас может удивить, как часто было бы полезно иметь доступ к какой-то части сетевого запроса даже на преимущественно статическом сайте.
Для обработки параметров строки запроса , например, на статическом сайте, вам придется прибегнуть к JavaScript или какому-нибудь проприетарному решению, такому как перенаправления Netlify. Дело в том, что, хотя переход от динамического сайта к статическому в теории прост, он требует изменения мышления. Поначалу легко вернуться к своим старым привычкам, но практика поможет.
На этом мы завершаем наш взгляд на управление данными в генераторе статических сайтов 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.
Дальнейшее чтение
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.