#Введение

Контекст

Zola — генератор статических сайтов, она из шаблонов и статей в markdown один раз генерирует веб-страницы, которые можно статически хостить, без всяких сложных бэкендов и фронтэндов.

Недавно я перевёл свой блог с Jekyll на Zola. Поэтому данная статья сфокусируется на том как проходил этот переход, и в процессе вы узнаете зачем стоит или не стоит использовать золу для себя. В данной статье будет рассказно:

  • Как я переехал на золу, с какими проблемами и удобствами столкнулся.
  • Плюсы и минусы золы.
  • Мысли по поводу этого статического генератора и как может быть лучше.

В данной статье я не буду пересказывать документацию и объяснять вам как поднять блог. Это обзорная статья.

#Переезд

#2Почему

Всё началось с того что я начал играться с шорткодами в золе, и это вылилось в то, что я создал очень много новых фич, про которые написал большую статью: Новые фичи данного блога.

Потом я начал играться с функцией resize_image, почувствовал силу оптимизаций, и оптимизировал весь сайт настолько хорошо и интересно, что это тоже пришлось писать отдельную статью: Оптимизация скорости статического сайта.

По конкретно этим вещам вы можете ознакомиться с соответствующими статьями. А далее я сфокусируюсь на вопросах конкретного использования золы.

#2Подготовка

Прежде чем переезжать на золу, я сделал несколько вещей:

Тут хочется отметить огромный плюс золы в том, что она маленькая. Соответственно всю документацию можно прочитать от корки до корки за несколько часов, и узнать все мелкие детали которые только есть.

В этом плане золу очень хвалят, ибо я читал в интернете, что если попробовать то же самое сделать с Hugo, то не получится. В нём невероятно большая документация.

Правда если вы до этого не писали блоги и только хотите начать, то ни в коем случае не делайте как я, выбирайте любую готовую тему и используйте её, пишите контент как можно быстрее, а со всем остальным разберётесь когда-нибудь потом. Как говорит Виталий Голованов:

Не звени яйцами.

Благодаря тому что я прочитал всю документаци, нашлось очень много интересностей и дальше я смог сразу знать что делать для перевода нестандартных фич моего блога.

#2Алиасы

Удобно что в золе можно для каждой страницы задать опцию aliases, в которой можно написать массив адресов, которые должны вести на текущую страницу. Это хорошо подходит когда ты мигрируешь свой блог на золу. Можно сделать так, чтобы старые ссылки сохранились.

#2Перенос комментариев

У меня комментарии были реализованы через utterances, так что отдельная проблема была — перенести их в новый репозиторий и под новые названия страниц. Но тут всё прекрасно, ишью можно transfer'нуть в любой репозиторий, а ещё если изменить адрес страницы в названии ишью, то комментарии переедут на эту страницу, так что все комментарии что были у меня остались на месте.

#В золе всё* сделано как надо

Есть множество фич, и зола знает что пользователю они нужны, поэтому они встроены в движок сразу! А то чо заставлять писать людей какие-то свои костыльные решения через язык-же, ну...

#2Шорткоды

Это возможность в статье на markdown вставить какой-то кастомно-настроенный кусок html-я. Настолько великая возможность, что я написал отдельную статью про новые фичи своего блога, которые реализовал с помощью этого.

В Jekyll можно что-то такое сделать костылями, но таким никто активно не пользуется, да и в документации про это ни слова, поэтому я тогда не знал что так можно...

#2Одна папка — одна статья

Алилуйя! В Jekyll все картинки и прочие материалы хранятся в папке assets, а посты хранятся в одной папке _posts, и там нельзя создать папку внутри которой можно положить пост и картинки, эта папка сразу превратится в категорию. Да и картинки там положить нельзя. Я про это писал в Один репозиторий = Один пост в блоге, там я хранил картинки рядом с постом, да, но чтобы они работали добавил JS, который фиксит пути картинок при загрузке страницы...

А здесь это из коробки нормально работает, каждый пост по умолчанию симулирует папку со своим названием и как будто там лежит файл index.md. В общем я щас все картинки и прочие штуки храню рядом с постами и очень кайфую от этого. А то создавать каждый раз папку в папке assets было неприятно.

Цитата документации отсюда

As you can see, creating an about.md file is equivalent to creating an about/index.md file. The only difference between the two methods is that creating the about directory allows you to use asset co-location, as discussed in the overview section.

#2Table Of Contents

Или по-русски «Содержание».

Опять же, вспоминая Jekyll, что нужно чтобы сделать содержание? Нуу, надо либо плагин, либо костыль. Вот пример костыля, который я тупо скопипастил себе в блог.

А что тем временем в золе? А там парсинг содержания уже встроен в движок, и оно автоматически доступно для каждой страницы, и это содержание представляет собой удобный структурированный объект с прописанным типом

Цитата документации отсюда

Both page and section templates have a toc variable that corresponds to an array of Header. A Header has the following fields:

// The hX level
level: 1 | 2 | 3 | 4 | 5 | 6;
// The generated slug id
id: String;
// The text of the header
title: String;
// A link pointing directly to the header, using the inserted anchor
permalink: String;
// All lower level headers below this header
children: Array<Header>;

То есть ты тупо берёшь и по этому примеру генерируешь такое содержание как тебе нужно.

#2Таксономии

Например, мы хотим чтобы у наших статей были тэги, и эти тэги потом можно было посмотреть где-нибудь.

Что же мы делаем в Jekyll? Правильно, изобретаем всё с нуля, и гуглим как бы это сделать. Вот результат.

А что тем временем в золе? А там есть такая концепция, как таксономии. Можно попробовать прочитать в документации, но там надо сосредоточиться, ибо концепция немного нетривиальная. С помощью этой концепции можно на уровне движка объявить что у вас есть тэги и писать именно их в своих статьях, а потом с удобным интерфейсом, и снова с удобным типом получать эти тэги и удобно их обрабатывать.

Когда я переводил страницу с тэгами на золу, то перевод был просто тривиальный, настолько просто и удобно там всё сделано.

А с этой фичёй можно делать куда более сложные и интересные штуки, как например с фильмами.

А ещё для таксономий можно автоматически генерировать RSS или Atom Feed! Что для того же Jekyll снова пришлось бы делать с большими костылями.

#2Обработка картинок

Как я уже говорил, обработка картинок — это просто имба. Невероятно крутая фича, которая сильно повысит юзабельность вашего блога. Это уже подробно раскрыто в статье про оптимизацию блога.

Что я там не раскрыл — это возможность узнать размеры картинки прямо на этапе компиляции сайта, что отлично подходит для вставки атрибутов width, height или даже для вертикального центрирования!

#2Блоки и наследование страниц

В золе имеется возможность в html странице написать что какие-то участки являются блоками, а затем в других страницах наследовать эту страницу и переопределять что делают блоки. Например у нас есть страница base.html:

<title>{% block title %}My blog{% endblock title %}</title>

Затем мы хотим создать новую страницу post.html на основе base.html и сделать там другой заголовок, тогда можно написать следующее:

{% extends "base.html" %}

{% block title %}My post{% endblock title %}

И теперь страница, сгенерированная по шаблону post.html будет во всём похожа на base.html, только заголовок будет My post.

А если хочется иметь ещё и старый заголовок, то можно внутри блока просто использовать {{ super() }}.

Ещё удобно создавать блоки, в которых изначально ничего нет, например extra_head, чтобы наследующий мог дополнительно определить что ему нужно в <head> секции.

Хоть наследование и считается немного плохой практикой в программировании, но тут страниц очень мало, так что вполне можно. Так что это удобный механизм. В Jekyll я такого не видел, и там формирование страницы делается через какие-то костыли с include'ингом.

#2Дебаг

Цитата документации отсюда

A magical variable is available in every template if you want to print the current context: __tera_context.

Это очень удобно при первичной разработке, чтобы постоянно не смотреть в документацию, а сразу видеть какие значения принимают какие переменные. Я использую дебаг следующим образом, чтобы прямо на странице получать результат в удобном виде:

<pre><code>
{{ __tera_context | escape_xml | safe }}
</code></pre>

А обычные переменные дебажу следующим образом:

<pre><code>
{{ variable | json_encode(pretty=true) | safe }}
</code></pre>

Правда тут есть одна проблема, что когда ваш блог наполняется контентом, то пользоваться дебагом через __tera_context уже невозможно из-за того что она содержит в себе весь контент, и страница начинает очень много весить и нагружать браузер. По этому поводу попросил фичу для новой переменной аналогичного действия, но без контента, так что заходите, ставьте лайки.

#2load_data

Есть такая функция load_data, которая позволяет считать данные в каком-нибудь структурированном формате и потом это использовать в рендеринге страниц

#3arewegameyet.rs

На сайте arewegameyet.rs это используется для того чтобы в структурированном формате хранить все крейты и ссылки на игры, а потом рендерить это на сайте.

Вот ссылка на data.toml, а вот ссылка на то как это используется.

Так что золу можно использовать не только для блогов, но и вот таких вот сайтов-списков с нетривиальным функционалом.

#3Внешние утилиты

А ещё эта функция load_data умеет считывать данные по ссылке из интернета. Я это использовал для создания чего-то типо «плагина», которай может выполнять сложный код, который зависит от внешних утилит. Например, если вы хотите:

  • Кодировать формулы в svg при помощи консольной утилиты tex2svg.
  • Автоматически перекодировать видео при помощи ffmpeg аналогично тому как это делается с картинками.

То для этого не нужны никакие фичи. Надо просто поднять локальный сервис, к которому зола будет обращаться с соответствующими запросами.

О том как это можно сделать я написал небольшую инструкцию на форуме золы.

Я уже было сам хотел это использовать, но потом понял что для этого надо поднимать этот локальный сервис, а потмо ещё при переустановке системы снова устанавливать эту утилиту для конвертации и забил. Всё-таки идея «забудьте зависимости» подкупает.

#2Пагинация

Если у вас очень много постов, то в золе из коробки поддерживается возможность разложить все посты по страницам чтобы реализовать 2-ю, 3-ю и остальные страницы с контентом.

Я этим не пользуюсь из принципа, но для других может быть полезно.

Я этим не пользуюсь, потому что считаю что:

  • Я не напишу столько статей чтобы их было слишком много на одной странице чтобы приходилось пользоваться пагинацией.
  • Я хочу чтобы все мои посты были на одной странице, потому что никто не смотрит дальше второй страницы. А если всё на одной странице и надо только листать вниз, то любой пользователь вам скажет: «почему бы и нет ¯\_(ツ)_/¯».

Ещё меня дико бесит когда люди делают пагинацию, которая состоит только из кнопок «Следующая страница», «Предыдущая страница», без указания количества страниц!!! Просто к слову, раз уж поднял тему.

#2По мелочи

  • Хорошие сообщения об ошибках, они показывают даже место где произошла ошибка. Но иногда они слабоваты, и конкретное место приходится искать самому с помощью дедукции.
  • Работает очень быстро. На Rust написано же.
  • Когда запускаешь в режиме serve, то на страницу добавляется скрипт, который автоматически обновляет страницу при изменении в золе.
  • Один бинарник, никаких зависимостей. В отличие от Jekyll, для которого надо замусорить свою систему Ruby, потом ещё чем-то, потом ещё миллионом вещей, и только потом можешь запустить свой сайт.

#А что же не так с золой

Прошлый большой пункт не зря со звёздочкой, ибо в золе не всё так радужно как хотелось бы.

#2Шорткоды с телом

В золе есть такая возможность:

Цитата документации отсюда

Let's imagine that we have the following shortcode quote.html template:

<blockquote>
    {{ body }} <br>
    -- {{ author }}
</blockquote>

We could use it in our Markdown file like so:

As someone said:

{% quote(author="Vincent") %}
A quote
{% end %}

The body of the shortcode will be automatically passed down to the rendering context as the body variable and needs to be on a new line.

Проблема в том что body передаётся в виде текста. То есть если мы там напишем какой-то markdown или другой шорткод, то они передадутся в виде обычного текста. Окей, чтобы это распарсить надо написать всего-лишь:

{{ body | markdown | safe }}

Только тут есть несколько проблем:

  • Если там есть заголовки, то они не попадут в содержание.
  • Шорткоды внутри этого вроде как работает.
  • А вот функции resize_image уже нет.

Из-за этого шорткоды с телом становятся абсолютно неюзабельны. Поэтому все мои фичи состоят из двух частей — начала и конца:

{{ pros_start(text="Плюсы: (_фичи_)") }}
* плюс1
* плюс2
{{ pros_end() }}

Где pros_start.html:

<div class="pros">
<span style="color: #090"><big><b>{%/* if text*/ %}{{/*text | markdown(inline=true) | safe/*}}{%/* else */%}Плюсы:{%/* endif */%}</b></big></span>

а pros_end.html:

</div>

Таким образом у меня «тело» шорткода pros парсится автоматически движком золы, а лишь вставляю инфу между ними. Я считаю что шорткоды с телом должны работать так по умолчанию.

Минусы моего подхода:
  • Приходится создавать на один файлик больше
  • Приходится использовать на одно имя больше
  • Если я что-то не закрыл, зола не поругается на меня, потому что это два разных шорткода

Но что поделать, приходится делать так.

#2Язык описания шаблонов

#3Фильтры

Есть небольшая проблема, что некоторые вещи можно получить только через механизм фильтров, например:

{{ [1, 2, 3] | length }}

Вот только этот механизм нельзя засунуть в скобки. И если хочется добавить единицу к результату, то придётся писать в две строчки:

{% set temp = [1, 2, 3] | length %}
{{ temp + 1 }}

И фильтры нельзя применять в виде length(value=[1, 2, 3]), что довольно печально. Я написал по этому поводу фич-реквест, так что заходим, ставим лайки.

#3Falsy

Цитата документации отсюда

Undefined variables are considered falsy. This means that you can test for the presence of a variable in the current context by writing:

{% if my_var %}
    {{ my_var }}
{% else %}
    Sorry, my_var isn't defined.
{% endif %}

В этом проблема. Мне кажется было бы намного лучше если бы присутствие переменной проверялось через функцию, например: exists("my_var") и exists_field(config.extra, "variable"); а обращение к несуществующему полю возвращало не null-подобную штуку, а сразу выкидывало сообщение об ошибке.

Я так на этом прокололся, и опубликовал сайт без одно важной фичи, которую долго не мог найти, потому что в {% if config.extra.enable_something %} в enable_something допустил опечатку.

Вообще весь язык создаёт впечатление такого расслабленного динамически-слабо-типизированного языка по типу JS, Pyhton. Лично мне хотелось бы чтобы порождение Rust'а была таким же строгим как и Rust. Но к сожалению имеем что имеем.

#2Сырость

В данный момент в золе есть следующие проблемы с картинками:

  • Нельзя ресайзить webp картинки.
  • Нельзя ресайзить некоторые png картинки в webp, что-то там вылетает и не хочет работать.

У всего этого одна ошибка в стороннем крейте работы с webp, я написал об этом issue. Ждём когда исправят.

Другой момент — в золе в рендеринг шорткодов в markdown даётся тяжело, с этим есть пара багов:

Ну и ещё вспомним мои предложения по фичам, а ещё есть много чего остального, что не поддерживается. Можно сказать что на данный момент зола довольно сыра. Как минимум в сравнении с каким-нибудь Hugo.

#2diff

Подсветка синтаксиса для diff ужасна. Она вроде во всех темах такая плохая.

+added line
-removed line

#Заключение

Так получилось что для меня переезд на золу — это не только про переезд, но и повод:

  • переписать весь код, чтобы знать за что отвечает каждая строчка шаблона;
  • выкинуть лишнее;
  • сделать новые фичи, которые так долго откладывал;
  • пофиксить проблемы которые так долго лежали в бэклоге.

Как видите, мой блог очень похорошел при Сергее Семёновиче Золе.

#2Плюсы и минусы

Давайте подведём итоги и соберём в одно место все аргументы «за» и «против» золы:

Плюсы:
  • Одна папка — одна статья, картинки можно хранить локально со статьёй.
  • Содеражние парсится из коробки.
  • Таксономии поддерживаются из коробки и позволяют легко сделать тэги.
  • Ресайзинг картинок и получение их мета-данных из коробки.
  • Страницы можно наследовать и концепция блоков.
  • Хороший дебаг.
  • load_data.
  • Сообщения об ошибках.
  • Быстрая скорость работы.
  • Лайв-релоадинг при изменениях в режиме zola serve.
  • Не требуется никаких зависимостей, всё в одном бинаре.
Минусы:
  • Шорткоды с телом не удобны в использовании.
  • Фильтры нельзя написать в скобках.
  • Концепция falsy позволяет игнорировать ошибки.
  • Довольно сыра.

#2Ссылка на все мои фич-реквесты и баг-репорты

Фич-реквесты:

Баг-репорты: