6 минут

Цикл "Статический генератор сайтов"

  1. Генератор сайтов с блэкджеком и плагинами

Доведя свой генератор Open Graph изображений до логичного финала, я пришёл к выводу, что Rust - это не мой язык. Мне неприятно на нём писать и я больше времени трачу на попытки победить злой компилятор, чем на добавление нового функционала. Поэтому я стартую новый цикл статей о разработке своего генератора статических сайтов (да, грядёт очередная миграция сайта 😂).

Да, Zola (нынешний генератор, на котором крутится этот сайт) меня во многом устраивает. Он быстрый и хорошо выполняет свою задачу. Мне очень понравилась его идея "one binary to rule them all", благодаря чему мне не нужен целый Node.js проект с кучей зависимостей, чтобы поднять примитивный сайт, как я работал раньше (во времена Astro, например).

Но некоторого функционала в нём всё же не хватает. Взять ту же генерацию Open Graph изображений для публикации в соцсетях. Да, я написал свой генератор на абсолютно не знакомом мне языке с мыслью о том, чтобы потом встроить его в Zola.

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

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

Название

Вдохновляться я буду Zola, поэтому и идею названия позаимствую оттуда. Эмиль Золя, в честь которого и был назван Zola - это французский поэт и журналист 19 века. Я же возьму русского поэта. Русские фамилии плохо выглядят на латинице, поэтому возьму что-то звучащее более интересно. Например, Иосифа Бродского. И стихи красивые, и имя латиницей выглядит хорошо. Живых проектов на гитхабе с таким названием нет - берём!

Brodsky, мне кажется, неплохо.

Функционал

Что я хочу видеть в SSG?

  • Формат распространения: Один бинарник с дружелюбным CLI
  • Функционал:
    • Сборка Markdown документов в HTML-страницы по шаблону (ну это база)
    • Поддержка CSS-препроцессора (писать чистый CSS больно)
    • Сжатие изображений
    • Поддержка тем
    • Поддержка плагинов (простое встраивание дополнительного функционала в процесс сборки сайта)
    • Генерация Open Graph мета-тегов
    • Поддержка resume.json с кастомными темами
    • Поддержка связывания статей в циклы

Технология

Полагаясь на требования, выберу технологию, на которой я буду писать. У меня три варианта:

  • Родной C#;
  • F#, который я не знаю, но он всё ещё часть мира .NET, поэтому погружение не должно занять много времени
  • Незнакомый мне Go

И хотя C# самый очевидный и простой вариант, потому что это моя основная специализация и моя full-time работа связана с ним, я не смогу его взять для этого проекта из-за того, что он не может обеспечить необходимыми библиотеками (например, там нет современных нативных реализаций CSS-препроцессоров).

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

Остаётся Go. Он даёт большую часть необходимого инструментария, работает быстро, компилируется в нативные бинарники, выглядит просто и читаемо. А что ещё нужно?

Да, тут тоже нет реализации ни одного CSS-препроцессора, но по остальному функционалу - Go даёт больше всех возможностей. Только у Sass есть не-node.js реализация, но она на Dart. Брать язык, о котором я знаю ещё меньше только из-за референсной реализации одной из множества функций в ущерб остальным (Dart обделён другими вещами, например создание SVG и их рендер), кажется, не самая удачная идея.

Самый простой и удобный вариант решения этой проблемы - использовать оболочку godartsass, которая общается с установленным в системе dart-sass, но вместо того, чтобы заставлять пользователя скачивать компилятор самостоятельно, при первой сборке (если Sass используется в проекте) компилятор будет скачан автоматически для текущей операционной системы и положен рядом с бинарником brodsky.

Концепция

Структура проекта

Важно максимально сохранить обратную совместимость с Zola, чтобы упростить миграцию. Поэтому структура сайта в общих чертах будет выглядеть так:

.
├── content/
│   └── section/
│       ├── page.md
│       ├── page2/
│       │   ├── image.png
│       │   └── index.md
│       └── _index.md
├── sass/
│   └── main.scss
├── public
├── static/
│   ├── index.js
│   └── resume.json
├── themes/
│   └── theme1/
│       └── theme.toml
├── templates/
│   └── index.liquid
└── config.toml
  • В корне проекта находится файл конфигурации в формате TOML, по умолчанию считается, что он называется config.toml, но параметром -c можно указать кастомный путь до файла конфигурации. Путь до файла конфигурации становится корневым для всего проекта;
  • В папке content находятся страницы в формате Markdown, кроме того там же можно расположить статические файлы (например, изображения);
    • При сборке проверяются ссылки (например, если изображение используется на страницах, оно сжимается и в готовый документ попадает ссылка на сжатый файл; если изображение нигде не используется, оно не попадает в итоговую сборку);
    • Для страниц должен быть механизм замены символов, то есть, например, если на странице, встречается двойной дефис
      (--), в итоговом рендере он превратится в тире (). При этом такие пары должны настраиваться.
  • В перспективе можно добавить поддержку тем с возможностью переопределения отдельных файлов темы, которые бы располагались в папке themes;
  • Из папки sass берутся Sass/SCSS-файлы для компиляции в CSS;
  • Содержимое папки static копируется в выходную директорию (по умолчанию public) с опциональным сжатием;
  • В папке templates лежат шаблоны и макросы на языке Liquid;
  • В файле static/resume.json находится резюме в формате JSON Resume, которое автоматически создаст новую страницу с резюме, встроенном в настраиваемый шаблон

Командный интерфейс

Нужно реализовать несколько команд:

  • init - создание нового проекта с минимальной корректной структурой
  • build - сбор проекта в директорию public (настраивается)
  • serve - сбор проекта (если реализуемо — в память, если нет — на диск) и поднятие локального веб-сервера с наблюдением за изменениями на диске и пересборкой проекта/отдельных файлов при их появлении
  • check - попытка сбора проекта без фактического рендера

Текущее состояние

Уже готова базовая структура (перечень команд, опции командной строки, система плагинов), автоматическая сборка бинарников под все операционные системы с публикацией пре-релиза на Github и команда init:

Заключение

Сейчас я перешёл на фиксированный график публикации статей (одна статья в последнюю субботу месяца), поэтому я просто в свободное время буду заниматься этим проектом и показывать, что изменилось. Возможно, будут и другие темы вперемешку с этой.

Исходники, как всегда, открыты, идеи и вклад приветствуются: Github.