Разработка кастомного плагина Jekyll (Ruby)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного плагина Jekyll (Ruby)
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка кастомного плагина Jekyll (Ruby)

Jekyll написан на Ruby и предоставляет полноценный API расширения через плагины. Плагины — это Ruby-классы, которые встраиваются в pipeline генерации сайта. Через них можно добавить новые теги Liquid, фильтры, генераторы страниц, конверторы форматов, хуки. GitHub Pages не запускает плагины (только белый список) — нужен собственный CI/CD.

Типы плагинов и когда что использовать

Тип Суперкласс Применение
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 | myfilter }}

Структура плагина

Плагины размещаются в _plugins/:

_plugins/
├── image_optimizer.rb
├── reading_time.rb
├── related_posts.rb
└── generators/
    └── tag_pages.rb

Пример 1: Кастомный фильтр

Фильтр для форматирования числа в российский формат:

# _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)

Использование в шаблоне:

{{ 1234567 | ru_number }}        → 1 234 567
{{ 9990.5 | ru_currency }}       → 9 991 ₽
{{ page.content | reading_time }} → 5 мин

Пример 2: Кастомный тег с параметрами

Тег для вставки видео с 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)

Использование:

{% video_embed provider="youtube" id="dQw4w9WgXcQ" title="Демо проекта" aspect="16-9" %}

Пример 3: 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

Пример 4: Хуки для постобработки

# _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?

  # Базовая минификация HTML (убрать лишние пробелы между тегами)
  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

Распространение как gem

# myplugin.gemspec
Gem::Specification.new do |spec|
  spec.name        = "jekyll-myplugin"
  spec.version     = "1.0.0"
  spec.authors     = ["Ваше имя"]
  spec.summary     = "Описание плагина"
  spec.files       = Dir["lib/**/*", "LICENSE"]
  spec.require_paths = ["lib"]
  spec.add_dependency "jekyll", ">= 4.0"
end

Сроки

Простой фильтр или тег — полдня — 1 день. Generator для страниц тегов/авторов — 2–3 дня. Сложный плагин с обработкой изображений, внешними API, тестами — 1–2 недели.