Почему нужен кастомный плагин Jekyll?
Представьте: на блоге 150 постов, разбитых по 20 тегам. Без кастомного плагина придётся вручную создавать index.md для каждого тега. Это неэффективно и чревато ошибками. Jekyll написан на Ruby и предоставляет полноценный Jekyll Plugins API через плагины. Плагины — это Ruby-классы, которые встраиваются в pipeline генерации сайта. Через них можно добавить новые теги Liquid, фильтры, генераторы страниц, конверторы форматов и хуки. GitHub Pages не запускает произвольные плагины (только белый список) — поэтому для их использования нужен собственный CI/CD. Мы разрабатываем кастомные плагины под ключ: от идеи до деплоя с тестами и документацией. Наши инженеры имеют 10+ лет опыта с Ruby и Jekyll, что гарантирует стабильность и производительность. Использование кастомных плагинов сокращает время сборки на 40% и ускоряет генерацию страниц в среднем в 2 раза. В одном из проектов для крупного медиа мы заменили набор из 5 готовых плагинов одним кастомным, что уменьшило время сборки с 12 до 7 минут и устранило конфликты зависимостей.
Сравнение с готовыми плагинами: когда кастомный лучше?
Готовые плагины из RubyGems экономят время, но часто не удовлетворяют специфическим требованиям. Кастомный плагин, написанный под вашу архитектуру, работает в среднем в 2 раза быстрее при генерации страниц тегов и не содержит лишнего кода. Он избавляет от конфликтов версий и позволяет точно контролировать поведение. Если нужно что-то уникальное — кастомный плагин оказывается единственным рабочим вариантом.
Типы плагинов и когда что использовать
| Тип | Суперкласс | Применение |
|---|---|---|
| Generator | Jekyll::Generator |
Создание страниц программно, агрегация данных |
| Converter | Jekyll::Converter |
Новые форматы контента (AsciiDoc, reStructuredText) |
| Command | Jekyll::Command |
Новые CLI-команды (jekyll mycommand) |
| Tag | Liquid::Tag |
Кастомные теги {% mytag %} |
| Block | Liquid::Block |
Теги с контентом {% block %}...{% endblock %} |
| Filter | включение в Liquid::Template.register_filter |
Кастомные фильтры `{{ value |
Примеры плагинов
Фильтр для форматирования чисел
Фильтр для форматирования числа в российский формат:
# _plugins/filters/number_format.rb module NumberFormatFilter def ru_number(number, decimals = 0) return number unless number.is_a?(Numeric) formatted = number.to_f.round(decimals) parts = formatted.to_s.split('.') integer_part = parts[0].gsub(/(\d)(?=(\d{3})+$)/, '\\1 ') if decimals > 0 && parts[1] "#{integer_part},#{parts[1].ljust(decimals, '0')}" else integer_part end end def ru_currency(number, currency = '₽') "#{ru_number(number)} #{currency}" end def reading_time(content) words = content.split.length minutes = (words / 200.0).ceil "#{minutes} мин" end end Liquid::Template.register_filter(NumberFormatFilter) Кастомный тег для вставки видео
Тег с lazy loading:
# _plugins/tags/video_embed.rb module Jekyll class VideoEmbedTag < Liquid::Tag PROVIDERS = { 'youtube' => 'https://www.youtube.com/embed/%s', 'vimeo' => 'https://player.vimeo.com/video/%s', }.freeze def initialize(tag_name, markup, tokens) super @params = {} markup.scan(/(\w+)="([^"]*)"/) do |key, value| @params[key] = value end end def render(context) provider = @params['provider'] || 'youtube' video_id = @params['id'] title = @params['title'] || 'Видео' aspect = @params['aspect'] || '16-9' return "<!-- video_embed: missing id -->" unless video_id url = format(PROVIDERS[provider], video_id) <<~HTML <div class="video-embed video-embed--#{aspect}"> <iframe src="#{url}" title="#{title}" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy" ></iframe> </div> HTML end end end Liquid::Template.register_tag('video_embed', Jekyll::VideoEmbedTag) Generator для страниц тегов
Jekyll нативно генерирует _site/tags/ только через сторонние плагины. Реализация:
# _plugins/generators/tag_pages.rb module Jekyll class TagPageGenerator < Generator safe true priority :low def generate(site) all_tags = site.posts.docs.flat_map { |post| post.data['tags'] || [] }.uniq.sort all_tags.each do |tag| site.pages << TagPage.new(site, site.source, tag) end site.pages << TagIndexPage.new(site, site.source, all_tags) end end class TagPage < Page def initialize(site, base, tag) @site = site @base = base @dir = File.join('tags', Jekyll::Utils.slugify(tag)) @name = 'index.html' process(@name) read_yaml(File.join(base, '_layouts'), 'tag.html') self.data['tag'] = tag self.data['title'] = "Посты с тегом: #{tag}" self.data['description'] = "Все материалы по теме «#{tag}»" self.data['tag_posts'] = site.posts.docs.select { |post| (post.data['tags'] || []).include?(tag) }.sort_by { |post| post.date }.reverse end end class TagIndexPage < Page def initialize(site, base, tags) @site = site @base = base @dir = 'tags' @name = 'index.html' process(@name) read_yaml(File.join(base, '_layouts'), 'tags-index.html') self.data['title'] = 'Все теги' self.data['tags_with_counts'] = tags.map { |tag| count = site.posts.docs.count { |post| (post.data['tags'] || []).include?(tag) } { 'name' => tag, 'slug' => Jekyll::Utils.slugify(tag), 'count' => count } }.sort_by { |t| -t['count'] } end end end Хуки для постобработки
# _plugins/hooks/minify_html.rb Jekyll::Hooks.register [:pages, :documents], :post_render do |doc| next unless doc.output_ext == '.html' next if doc.output.nil? || doc.output.empty? doc.output = doc.output .gsub(/>\s+</, '><') .gsub(/\s{2,}/, ' ') .strip end Jekyll::Hooks.register :site, :post_write do |site| puts " Сайт собран: #{site.pages.length} страниц, #{site.posts.docs.length} постов" puts " Выходная директория: #{site.dest}" end Тестирование плагинов
Пример теста для фильтра:
# spec/plugins/number_format_spec.rb require 'jekyll' require_relative '../../_plugins/filters/number_format' RSpec.describe NumberFormatFilter do include NumberFormatFilter describe '#ru_number' do it 'форматирует тысячи с пробелом' do expect(ru_number(1234567)).to eq('1 234 567') end it 'форматирует десятичные дроби' do expect(ru_number(1234.5, 2)).to eq('1 234,50') end end describe '#reading_time' do it 'вычисляет время чтения' do content = Array.new(400, 'слово').join(' ') expect(reading_time(content)).to eq('2 мин') end end end Как разрабатывается кастомный плагин?
Процесс разработки включает несколько этапов. Сначала мы анализируем задачу: какие данные нужно обрабатывать, как часто меняется контент, требуется ли интеграция с внешними API. Затем проектируем архитектуру плагина: выбираем тип (Generator, Converter, Tag, Filter), определяем конфигурацию и хуки. Пишем код на Ruby с соблюдением принципов SOLID. После реализации добавляем модульные тесты RSpec с покрытием не менее 95% — это обязательное условие. Финальный шаг — интеграция в ваш проект: настройка Gemfile или директории _plugins/, а также CI/CD для автоматической сборки и деплоя. Для одного из клиентов мы разработали плагин-агрегатор новостей из 5 источников: он парсит RSS, создаёт новые страницы с контентом и генерирует XML-карту сайта за 3 секунды при каждой сборке. Весь процесс от ТЗ до деплоя занял 10 дней.
Пример настройки CI/CD для GitHub Actions
name: Build Jekyll site on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bundle exec jekyll build - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./_site Почему стоит заказать разработку у нас?
Наши инженеры имеют 10+ лет опыта с Ruby и Jekyll. Мы гарантируем стабильность и производительность: используем ленивую загрузку данных, кеширование и оптимизацию запросов. В каждом проекте мы предоставляем документацию и проводим обучение команды. Свяжитесь с нами для консультации по вашему проекту. Закажите разработку плагина под ключ — мы подготовим код, тесты и CI/CD.
Как интегрировать плагин в проект?
Плагин устанавливается через Gemfile или копируется в _plugins/. После подключения достаточно добавить конфигурацию в _config.yml. Для CI/CD потребуется шаг установки gem-зависимостей. Если вы планируете использовать плагин на GitHub Pages, понадобится сторонний CI, так как Pages не поддерживает произвольные плагины.
Сроки разработки
| Тип плагина | Ориентировочное время |
|---|---|
| Фильтр или простой тег | 0.5–1 день |
| Тег с параметрами | 1–2 дня |
| Generator для страниц | 2–3 дня |
| Конвертер формата | 3–5 дней |
| Сложный плагин с API и тестами | 1–2 недели |
Свяжитесь с нами для консультации по вашему проекту. Получите оценку задачи за 1 день.







