Использование шаблонизатора Liquid в Jekyll
Продолжаем разбираться с Jekyll. Основным компонентом для создания полезного функционала сайта является некий Liquid. Его же надо знать для модификации сайта и добавления "рюшечек" и "свистелок".
Liquid - это язык генерации страниц из шаблонов, используемый в Jekyll. В нём есть переменные, циклы и некоторые базовые операции, а потому формально он может считаться языком программирования (впрочем, очень ограниченным).
У Liquid есть собственная документация. Она подходит для справки, но в контексте сайта/блога её не хватает, т.к. Jekyll использует расширенную вариацию Liquid, с тремя дополнительными тегами, а также множеством фильтров и некоторыми Jekyll-специфическими переменными. В заметке рассмотрен именно этот вариант Liquid.
Компанией Cloudcannon составлен детальный справочник по переменным, тегам, фильтрам и прочим элементам Jekyll/Liquid.
Терминология: в Liquid есть теги и фильтры. "Теги" Liquid никак не связаны с тегами (метками) в обычном смысле (как в "облаке тегов"), а являются аналогами "тегов HTML", т.е. выполняют роль разметки. Фильтры используются внутри тегов и выполняют преобразования над переменными: от замены текста до сортировки.
Обработка Liquid тегов и фильтров происходит в первую очередь, до преобразования Markdown-разметки в HTML. Можно посмотреть исходный текст этой записи как источник примеров.
Для отладки ошибок Liquid см. раздел ниже.
Далее по порядку разобраны компоненты Liquid:
Ещё раз подчеркну, что некоторые переменные и теги существуют только в "Liquid для Jekyll", а не "чистого Liquid".
Переменные Liquid
Переменные - ядро Liquid. В большинстве случаев их можно только читать, использовать, но не изменять. В документации Liquid они отделены от "объектов", но для простоты удобно считать "объекты" просто переменными для чтения.
Переменные1 "появляются" несколькими способами:
-
берутся из YAML-разметки, а именно:
- из заголовков страниц/записей блога (Front Matter),
- из конфигурационного файла Jekyll
_config.yml
.
Они включают пользовательские переменные, внутренние переменные Jekyll, также переменные со значениями по умолчанию2.
-
создаются тегами
assign
иcapture
, см. ниже.
Переменными просто пользоваться, но некоторые вещи банально не документированы. Они возникают на стыке YAML (данные), Liquid (обработка данных) и Ruby (как языка, на котором написан Jekyll и обработчик Liquid).
Некоторые факты:
- Liquid лояльно относится3 к несуществующим переменным, полям, индексам массивов и возвращает специальное пустое значение Nil (когда результат Liquid-кода "пуст"). При выводе это значение соответствует пустой строке:
"{{ not_a_var }}", "{{ some_var.no_such_attribute }}" и "{{ list[-3] }}" выведут "", "" и "".
- Кавычки (и двойные, и одинарные) определяют строку, а не переменную. В примере текст специально взят в квадратные скобки. Кавычки не входят в состав строки (переменная
some_var
не определена):[{{ some_var }}], [{{ "some_var" }}] и [{{ 'some_var' }}] выведут [], [some_var] и [some_var].
- Есть ключевые слова, например,
true
,false
(чувствительны к регистру)"{{ true }}", "{{ false }}" и "{{ False }}" выведут "true", "false" и ""
- Есть типы, сравнение между которыми выдаст ошибку (например, строка и число). С другой стороны, может происходить автоматическое преобразование типов, например, в фильтрах.
- При выводе тегом
{{ ... }}
содержимое преображается в строку.
В YAML можно задать скаляры, массивы и словари (см. заметку по YAML), а Liquid формально может использовать только скаляры и массивы.
Это не мешает использовать доступ к полям словаря с помощью синтаксиса dict_name.field_name
или равноправного dict_name[field_name]
, см. примеры YAML. Эти значения можно использовать, но не изменять.
"{{ site.url }}", "{{ site["url"] }}" и "{{ page.title }}" выведут "https://atremba.github.io", "https://atremba.github.io" и "Использование шаблонизатора Liquid в Jekyll"
Здесь использованы предопределённые переменные, связанные с сайтом и конкретно с этой страницей.
Доступ к элементам массива осуществляется по числовому индексу (начинается с 0):
array_name[0], array_name[1], ...
Наконец, в Jekyll определены два фильтра: sort
и where
, имеющие доступ к полям (properties).
А именно, они могут сортировать и фильтровать массивы словарей, исходя из значений конкретных полей.
Создание переменных в Liquid
Иметь дело с переменными в Liquid не так просто, в первую очередь из-за их постоянства. Как уже говорилось, изменять некоторые сложные переменные динамически нельзя (в частности, словари и их поля).
Зато можно создавать новые переменные.
Можно определить и создать переменную прямо внутри тега assign
:
{% assign my_variable = ... %}
А парные теги {% capture my_variable %} ... {% endcapture %}
"захватывают" в переменную my_variable
всё содержимое между ними.
Списки (массивы) можно породить либо тегом split
(из строк, см. пример в разделе про фильтры), либо применением фильтров к существующим массивам/словарям.
Модификация переменных
В Liquid предусмотрены особые теги итерации {% increment var_name %}
и {% decrement var_name %}
, создающие и модифицирующие целочисленные переменные (начиная с нуля). Некоторый юмор в том, что созданная таким образом переменная var_name
отличается от переменной var_name
с тем же именем, но созданной с помощью {% assign var_name = ... %}
Как с этим работать, я так и не понял. Но для себя сделал вывод, что Liquid немного странный.
Теги Liquid
Теги бывают двух видов4. Они могут:
-
Подставить текст переменной. Они состоят из двойных открывающих и закрывающих фигурных скобок, например
{{ page.some_var }}
выведет переменнуюsome_var
, заданную (без префикса!5) в заголовке этой страницы:Текст в переменной some_var.
. Перед выводом переменная может быть преобразована фильтром, например, заменяющим часть строки"Текст в"
на"Data in"
:Data in переменной some_var.
. -
Выполнить команду в формате
{% command ... %}
. Эти теги, в свою очередь, делятся на- одиночные.
Например{% post_url 2019-04-20-yaml %}
формирует ссылку на запись про YAML, которая находится в файле_posts/2019-04-20-yaml.md
, - парные управляющие конструкции, типа
for
,if
, switch-конструкции (называютсяcase/when
), циклы сbreak/continue
и пр.
{% for counter in page.example_list %}{{ counter }}{% endfor %}
Выведет:
1234567
У тегов есть нетривиальные особенности работы с окружающими пробелами, особенно с вложенными многокомпонентными конструкциями (типа
if - elseif - else - endif
), поэтому удобнее всего их использовать в HTML-шаблонах, а не markdown-разметке4. - одиночные.
Комментарии, ограниченные тегами {% comment %} ... {% endcomment %}
, будет исключены из текста.
Чтобы отменить интерпретацию тегов ({{ ... }}
и {% ... %}
), их надо обрамлять {%
raw
%}
и {%
endraw
%}
.
Специфические теги Jekyll
Помимо стандартных Liquid-тегов, в Jekyll добавлено ещё три:
- для подсветки синтаксиса (средствами HTML!). Мне кажется, что этот функционал дублирует блочные записи c
```lang ... ```
в Markdown. - для ссылок на произвольные страницы (
link
) и записи блога (post_url
). Они весьма полезны, т.к. выполняют валидацию ссылок, и если соответствующей страницы/записи нет, сборка Jekyll прервётся, а в режиме запущенного веб-сервера (jekyll serve
) будет выдано сообщение об ошибке, как в примерах выше.
К сожалению, если сайт располагается не в корне, то надо добавлять префикс baseurl
(задаваемый в _config.yml
):
[Name of Link]({{ site.baseurl }}{% post_url 2010-07-21-name-of-post %})
Фильтры Liquid
Фильтры применяются для форматирования вывода и преобразования выражений.
Для вывода используется тег {{ ... }}
, а преобразования полезны для формирования переменных, например, это способ создать массив:
{% assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}
создаст список my_array
, который будет выведен как
antsbugsbeesbugsants
-
Фильтры бывают без параметров, например,
{{ 123.456 | ceil }}
выведет124
-
Фильтры с параметрами:
{{ 7 | divided_by: 2.0 }}
выведет3.5
-
Фильтры можно применять последовательно:
{{ my_array | uniq | join: ", " }}
выведетants, bugs, bees
Кроме полного списка фильтров Jekyll и Liquid, удобно использовать справочник с группировкой по типам объектам (целые числа, массивы, строке), к которым применяются фильтры. Заодно в этом справочнике приведены встроенные переменные и список управляющих тегов (условия, циклы и пр.)
Отладка Liquid
Удобнее всего экспериментировать с Liquid, запустив Jekyll локально, а потом редактировать файл, периодически его сохраняя. Исключение - главный конфигурационный файл _config.yml
; для учёта изменений в нём надо перезапустить сервер.
Интересно, что запуск Jekyll в режиме сервера (jekyll serve
и bundle exec jekyll serve
) прервётся, если будет обнаружена ошибка в Liquid. Однако если запустить его на "хорошем" коде, а потом внести ошибку в код, то работа сервера не прервётся.
При обнаружении ошибок или предупреждений Jekyll/Liquid выдаёт в консоль, откуда был запущен, содержательную информацию, включающую номер строки. Правда, иногда он не соответствует строкам markdown-кода из-за заголовка6. Примеры:
Liquid Warning: Liquid syntax error (line 47): Expected end_of_string but found pipe in ""now" | date: "%d" <= 10" in <path_to_blog>/_posts/2019-05-08-jekyll-and-liquid.md
Liquid Exception: Could not find post "2019-04-29-local-jekyll" in tag 'post_url'. Make sure the post exists and the name is correct. in <path_to_blog>/_posts/2019-05-08-jekyll-and-liquid.md
Error: Could not find post "2019-04-28-local-jekyll" in tag 'post_url'. Make sure the post exists and the name is correct.
После исправления ошибки выдача станет нормальной:
Regenerating: 1 file(s) changed at 2019-05-12 10:53:49
_posts/2019-05-08-jekyll-and-liquid.md
...done in 3.0911087 seconds.
Для подробного вывода можно запустить jekyll build --trace
, точнее,
>bundle exec jekyll build --trace
Заключение
Язык Liquid чаще всего используется для создания шаблонов или тем сайтов/блогов.
Однако с его помощью можно и формировать страницы динамически, и даже организовывать подобие базы данных с помощью файлов данных (в режиме чтение).
В обычных записях можно использовать ссылки на другие записи.
Например, некоторые записи этого блога начинаются со ссылок вида
[текст]({% post_url 2019-04-09-blog-on-github-pages.md %})
.
Ссылки
-
https://shopify.github.io/liquid/basics/introduction/ - оригинальная документация и введение в Liquid. Исходный код Liquid лежит в Github, а исходники документации на Liquid+Markdown доступна в ветке
gh-pages
. -
https://jekyllrb.com/docs/liquid/ - документация Liquid и его модификации на сайте Jekyll.
-
https://learn.cloudcannon.com/jekyll-cheat-sheet/ - отличный компактный справочник/cheatsheet по переменным, тегам и фильтрам Liquid (включая таковые для Jekyll).
-
https://help.shopify.com/en/themes/liquid - альтернативная, немного устаревшая документация на сайте Shopify, создателя Liquid. Здесь
- фильтры сгруппированы по темам,
- ещё один справочник/cheatsheet, подробный и удобный, но для варианта Liquid-для-Shopify.
- есть примеры кода,
- объяснения, как работают различные теги -
sort
,where
иgroup_by
.
-
Формально переменные являются "объектами", и их значения выводятся с помощью конструкции
{{ ... }}
. Для простоты я называю эти двойные фигурные скобки тоже "тегами". ↩ -
Переменные по умолчанию есть и в заголовках, и в конфигурационном файле. Github Pages добавляет к ним метаданные репозитория. Кроме того, в Jekyll можно добавить свои переменные по умолчанию для нужных страниц или записей, сделав раздел
defaults:
в_config.yml
. ↩ -
Согласно документации, технически это поведение (обработку неопределённых переменных и фильтров) можно изменить при запуске обработчика Liquid-кода. Но как включить этот режим из Jekyll - не знаю. ↩
-
В спецификации Liquid есть вариации тегов, включающие дефисы:
{{- ... -}}
, и{%- ... -%}
. Они "отключают" пробелы вокруг результата выполнения тега, что важно в контексте Markdown-разметки. Похоже, что они удаляют пробельные символы до/после тега, "приклеивая" его содержимое к предыдущему/последующему тексту. Эти дефисы-модификаторы могут быть не парными. Так,{{ ... -}}
отключит пробелы после вывода. По моему мнению, поведение этих тегов в сложных и вложенных конструкциях типаfor ... if-then-else-endif ... endfor
документировано недостаточно. ↩ -
Все заданные в заголовке (Front Matter) переменные добавляются как поля в словаре
page
. Аналогично, все переменные, заданные в_config.yml
, являются полями словаряsite
, и доступны в тексте любой страницы. ↩ -
YAML-заголовок (Front Matter) считывается и убирается из текста до обработчика Liquid. Это логично, ведь собственно из него и берётся часть переменных. Поэтому номер строки ошибки, предупреждения или исключения Liquid надо увеличивать на число строк Front Matter - включая начальные и закрывающие строки с "
---
". ↩