Gostatic (эрзац-Hugo)

2024-06-262026-09-03   #article

Репозиторий.

Не раз мигрировал свой хобби-сайт на разные движки. Писал несколько своих проектов на PHP, превращающихся в разные статические генераторы.

Потом брал специализированные генераторы:

К Hugo приладил вспомогательный скрипт на Bash, причёсывающий сгенерированный результат. Собиралось это все равно на локальной машине и загружалось на удаленный сервер через Git. Подумал, почему бы не заменить Hugo на свою реализацию полностью на Bash, ведь там все просто: перебирай себе файлы, преобразуя Markdown в HTML, оборачивая в шаблон.

Так и появился первый вариант. Потом он был переписан на Go, оброс нужными мелочами и стал gostatic. Это по-прежнему не попытка сделать еще один большой генератор для всех. Скорее небольшой понятный инструмент под один нормальный способ собирать свой статический сайт.

Что делает gostatic

gostatic читает страницы из _source, применяет шаблоны из _template и складывает готовый сайт в public. Основной контент можно писать в Markdown или HTML, а картинки, стили и остальные файлы просто кладутся сразу в public и используются как есть.

Структура сайта задается папками, без отдельной возни с разделами и служебными файлами. Для папок можно собрать индекс со ссылками на соседние страницы, а для сайта - карту. Есть обычные вещи, которые хочется получить сразу: шаблоны, переменные из frontmatter, страница 404, оглавление, теги, вложенные разделы и нормальная очистка результата перед сборкой. Файлы и папки в public, начинающиеся с _, при этом остаются, поэтому на моём сайте папка с картинками называется _static.

Настройки лежат рядом с самим сайтом в .env. Их немного: можно включить или выключить шаблоны и Markdown, выбрать ссылки относительными, оставить страницы как name.html или положить их в name/index.html. Не нужно собирать конфигурацию из нескольких файлов или привыкать к особому языку шаблонов ради простых вещей.

В README есть установка, все настройки и точные команды. Обычно достаточно собрать один бинарник, а потом запускать его из папки сайта. Для работы над сайтом есть watch.sh: он поднимает локальный сервер и пересобирает сайт после изменений.

Зачем он нужен

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

У gostatic нет зависимостей и внешних пакетов для сборки. Он написан на Go, собирается в один исполняемый файл и не привязывает сайт к отдельной экосистеме. Это не значит, что он универсальнее Hugo или Jekyll. У них есть много функций, которые здесь намеренно не повторяются. Зато свой сайт собирается быстро, а поведение генератора можно понять по нескольким исходникам и README.

История

Первым этапом все было сделано на Bash. Система шаблонов получилась даже удобнее, чем у Hugo, как и управление разделами: теперь это просто контент в папках без каких-либо настроек и служебных файлов. Индексные файлы наполнялись ссылками на все остальные файлы из их папок.

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

Следующим этапом добавлена генерация страниц тегов. Еще во время первого прохода собирался список ссылок: к какому тегу какие статьи относятся. Потом все это записывалось в одноименные файлы /tags/*.html.

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

Как основа ссылок использовались пути до файлов без .html, которые перезаписывались через .htaccess, как и на других генераторах. Сначала контент записывался в name.html, в последний момент переделал на структуру name/index.html, как было в Hugo, для упрощения просмотра с локальной машины. Внешний результат получился такой же, как у Hugo.

Hugo и Jekyll поставляют свой веб-сервер с горячей перезагрузкой контента. Финальный билд делается для загрузки на внешний сервер. В моей версии, понятно, никакого сервера не было - это только генератор. Все ссылки обычно идут от корня /, поэтому для просмотра нужен локальный веб-сервер. Для предпросмотра приходилось собрать полный билд.

Скрипт вышел немного запутанный, т.к. Bash знаю плохо и активно пользовался ChatGPT, но совсем без понимания ничего бы реализовать не получилось.

Тогда Markdown обрабатывался через большой сторонний пакет pandoc, а для форматирования результата можно было подключить tidy. Парсер pandoc немного отличался от Hugo, и пришлось перебрать контент. В частности, pandoc требует отбивку списков пустыми строками. Я часто не отбивал список от параграфа. Еще он добавляет теги p внутрь li, что пришлось визуально скорректировать через CSS.

Заодно тестировал всевозможные Markdown: одни были быстрыми, но некорректно кодировали кириллические ссылки, другие не ставили хеши к заголовкам, третьи ломали контент или работали заметно дольше. В итоге написал свой Markdown, и все стало работать в тыщу раз быстрее. Полная сборка теперь занимает меньше секунды.

Промежуточная версия gostatic была даже функциональнее нынешней. Генерировались RSS, XML-карта сайта и листинги тегов. Но постепенно стало ясно, что лишних деталей в таком маленьком скрипте больше, чем пользы.

RSS оказался никому не нужен. XML-карту сайта тоже убрал, оставив только HTML-страницу со всеми постами. Она теперь не столько карта сайта, сколько общая лента. Генератор листингов тегов убрал - создавал больше проблем, чем толку, и дублировал часть кода gostatic. Крупные категории и так сделаны папками, а для остального теги не настолько важны.

Сначала основной скрипт переписал на Go, оставив на Bash только watch.sh. Потом убрал и остальные зависимости. Заодно старался держать весь Go-код в пределах тысячи строк. Так из довольно запутанного эрзац-Hugo постепенно получился gostatic - маленький генератор, который делает ровно то, что нужно этому сайту.