Цикл "Статический генератор сайтов"
- Генератор сайтов с блэкджеком и плагинами
Содержание
Доведя свой генератор 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.