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

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомного плагіна Jekyll (Ruby)
Середня
~2-3 робочих дні
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка кастомного плагіна 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 en_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 en_currency(number, currency = '$')
    "#{currency}#{en_number(number)}"
  end

  def reading_time(content)
    words = content.split.length
    minutes = (words / 200.0).ceil
    "#{minutes} хв"
  end
end

Liquid::Template.register_filter(NumberFormatFilter)

Використання в шаблоні:

{{ 1234567 | en_number }}        → 1,234,567
{{ 9990.5 | en_currency }}       → $9,990
{{ 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 '#en_number' do
    it 'форматує тисячі з комою' do
      expect(en_number(1234567)).to eq('1,234,567')
    end

    it 'форматує десяткові дроби' do
      expect(en_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 тижні.