Trafaret

Trafaret — генератор статических сайтов из markdown файлов. Поддерживает теги, категории, архив, пагинацию, черновики, избранное и многое другое. Работает на Typescript.

С переводом своей персональной базы знаний в Obsidian и оценив удобство хранить данные и заметки в отдельных markdown-файлах, решил что неплохо бы и сайт обновить: отказаться от базы данных, все записи перевести в md-файлы, сделать простые HTML+JS страницы, тёмную тему и т.д. В итоге, получился этот проект 😅

Много лет сайт работал на блоговом движке Эгея. У него есть плюсы: быстрая установка, простая настройка, удобный минимализм. Но это всё-таки инструмент для блога. Со временем появилась необходимость в дополнительных функциях, которые и реализовал с помощью Trafaret.

SSG

Если сравнивать с какой-нибудь популярной CMS типа Wordpress, Joomla, Drupal, то Trafaret — это больше гиковская вещь, наверное как и другие SSG. Это не менюшки и drag’n’drop, а файлы, код и терминал 😁 Тут нет визуальных редакторов и настроек, всё руками в конфиг файлах. Мне такой вариант подходит, большую часть времени у меня открыт VS Code.

Почему не Jekyll, Hugo, Gatsby, Astro, etc

Скорее всего — потому что могу. Хотелось разобраться в теме, размять мозг и сделать что-то своё. Подойдёт ли Trafaret кому-то ещё? Скорее всего. Не знаю. Но он простой, выполняет свою задачу, хотя какие-то навыки программирования тут наверное пригодятся.

Возможности

  • генерация страниц из markdown файлов
  • теги
  • категории
  • избранное
  • темы
  • локализация
  • архив: по месяцам, тегам, категориям
  • черновики
  • пагинация
  • поиск по сайту
  • пользовательские URL
  • пользовательские расширения файлов
  • доступ по паролю
  • поддержка OG
  • RSS
  • Sitemap
  • гибкая настройка параметров
  • упаковка и оптимизация Parcel

Библиотеки

Основной код и генерация написаны на Typescript и Node.js. Для преобразования md-файлов в HTML страницы используется парсер и шаблонизатор. Перед публикацией всё оптимизируется и упаковывается с помощью Parcel.

Быстрый старт

  1. Склонируйте проект к себе на компьютер или скачайте со страницы релизов
  2. Установите все зависимости
  3. Запустите dev-сервер

Сделать это можно так:

git clone git@github.com:mopsicus/trafaret.git
cd trafaret
npm install
npm run gen
npm run dev

После этого можно открыть в браузере http://localhost:1234, демо-сайт должен работать. Параметры сайта для разработки находятся в файле .env.dev, если этого файла нет, то данные будут браться из основного файла .evn.

По умолчанию все страницы находятся в папке content. Для того чтобы добавить новую страницу на сайт, просто создайте новый файл в этой папке с расширением .md, добавьте минимальные метаданные о странице и запустите генерацию командой npm run gen.

Вот пример для новой страницы new-page.md:

---
title: 'Новая страница'
layout: 'page'
slug: 'new-page'
---
# Заголовок

Новая страница создана

Теперь перейдя на http://localhost:1234/new-page.html вы увидите вашу новую страницу. Хотя тут slug можно было не использовать, потому что адрес бы сформировался из имени файла. Это свойство нужно чтобы задать произвольное значение.

В документации вы узнаете как:

  • создавать и оформлять страницы
  • использовать плагины для markdown
  • создавать разделы и пагинацию
  • использовать метаданные
  • сделать запароленную страницу
  • генерировать RSS ленту и Sitemap
  • создавать специальные страницы тегов и категорий
  • сортировать и показывать списки страниц
  • использовать блоки, хелперы и писать свои
  • подключить свои JS и CSS
  • подготовить и опубликовать сайт

Метаданные

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

Есть несколько параметров которые используются при генерации, остальные пользовательские.

  • title — заголовок страницы
  • layout — название шаблона
  • slug — пользовательский URL
  • tags — список тегов
  • date — дата создания/изменения страницы
  • ext — расширение файла, отличное от .html
  • password — пароль для страницы
  • items — количество постов на странице, для разделов
  • draft — черновик или нет
  • static — отлючение шаблонизатора
  • category — название категории
  • exclude — исключить из архива
  • skip — не отображать в общем списке, для разделов

ВАЖНО Обязательных параметров только два: title и layout.

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

---
title: 'Trafaret'
description: 'Генератор статических сайтов, SSG, static site generator...'
layout: 'project'
favorite: true
date: '2025-02-03 16:55'
tags: ['ssg', 'markdown', 'website|сайт', ...]
banner: '/assets/trafaret.jpg|Trafaret — генератор статических сайтов'
info: 'Trafaret — простой генератор статических сайтов из markdown файлов...'
---

Темы

По умолчанию тема оформления хранится в папке themes/default. Также в этой же папке (кроме ваших картинок, CSS, JS и других файлов) хранятся шаблоны (layouts) и модули (partials), их ещё где-то называют миксины — это часть шаблона которую можно выделить в самостоятельный блок и повторно использовать.

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

Например, карта этого сайта (sitemap) строится с помощью вот такой конструкции:

{{#each data.pagelist}}
{{#unless (contains ../data.list this.data.url)}}
<url>
  <loc>{{this.data.url}}</loc>
  <lastmod>{{datetime this.data.date}}</lastmod>
</url>
{{/unless}}
{{/each}}

Тут data.pagelist содержит массив всех страниц сайта, data.list содержит массив паттернов-исключений. В цикле each с помощью хелпера contains проверяется отсутствие паттерна в адресе страницы и она добавляется в карту сайта.

Отладочная информация

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

Для этого добавьте модуль debug в свой шаблон и передайте ему текущие данные страницы. Модуль может принимать 3 параметра: raw, obj и item. Но обязательный только один параметр: raw.

Перед использованием модуля debug включите режим отладки, добавив ключ DEBUG=true в файл .env.

Как вывести отладочную информацию:

{{> debug raw=data obj='custom-key' item=0}}

Если вы передадите только raw без obj, вы получите все доступные ключи этого объекта. Если вы передадите raw и obj, вы получите объект из raw по ключу obj или доступные ключи, если ключ не найден. Если вы добавите item, вы сможете получить указанный элемент массива, если он существует.

Пример вывода, если ключ не найден:

key not found: custom-key, available keys: title, description, layout, tags, exclude, url, date, source, env, base, language, generator, content, pagelist, taglist, categorylist, monthlist, theme, navigation

Пример вывода, если данные есть:

taglist: [
  {
    "name": "unity",
    "level": 1,
    "links": [
      "https://mopsicus.ru/blog/sea-battle-online-game.html",
      "https://mopsicus.ru/blog/unity-builder-bot.html",
      "https://mopsicus.ru/blog/unity-crop-scale-texture.html",
      ...
    ],
    "url": "https://mopsicus.ru/tags/unity.html",
    "key": "unity"
  },
  {
    "name": "plugin",
    "level": 0.1,
    "links": [
      "https://mopsicus.ru/projects/umi.html"
    ],
    "url": "https://mopsicus.ru/tags/plugin.html",
    "key": "plugin"
  },
  ...
]

Хелперы

Вместе с Trafaret доступно некоторое количество хелперов, которые используются для работы с массивами, строками, форматирования ссылок и прочее. Но при необходимости вы можете написать и добавить свои, просто добавив новый файл в папку helpers.

В доке Handlebars.js подробно расписано как работать с шаблонами, блоками и хелперами.

Вот пример моего хелпера contains из кода выше:

import pico from 'picocolors';

export const contains = (list: Array<string>, target: string) => {
  try {
    if (target && list) {
      return list.find(part => target.includes(part)) !== undefined;
    }
    return false;
  } catch (e) {
    console.error(pico.bgRed(`contains failed: ${list}, target: ${target}, error: ${e}`));
  }
};

Также можно подсмотреть какие-то решения и установить себе пак из 130 хелперов с Github.

Поиск

Так как Trafaret генерирует HTML страницы и база данных отсутствует, то организация поиска по сайту становится отдельной задачей. Есть разные готовые решения: Lunr.js, MiniSearch, Fuse, Google Site Search, Algolia и другие.

Для этого сайта я выбрал Fuse.js.

Принцип работы прост: в качестве «базы данных» для поиска выступает сгенерированный вместе с сайтом JSON файл, по которому и ищется информация с помощью библиотеки Fuse.js. Есть некоторое количество настроек: по каким полям искать, регистрозависимость, можно настраивать «вес» каждого поля и прочее.

Комментарии

С комментариями примерно такая же история как и с поиском: базы данных то нет! Можно использовать подключаемые комментарии Disqus, Commento, Hyvor и другие. Но не хотелось чтобы какие-либо данные хранились на стороне, этот вариант не подошёл.

Также есть куча selfhosted решений: Schnack!, Remark42, CommentBox, Cusdis, Comentario, giscus. Можно выбрать на любой вкус. Но тут тоже нужна база данных.

В итоге сделал свой велосипед 😁

Есть один PHP скрипт который записывает данные в JSON файл, ключом является адрес страницы. Всё остальное делает JS скрипт, который подгружает комментарии и строит дерево. Как-нибудь подробнее напишу про эту нехитрую систему, может кому-то понадобится.

JS

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

  • fotorama.io — галерея изображений
  • Jouele — аудиоплеер
  • Emerge — координатор загрузки страниц
  • prettify – подсветка синтаксиса
  • fitvids.js — встраивание видео

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

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

Все исходники доступны на Github