למה צריך פלאגין Jekyll מותאם אישית?
דמיינו בלוג עם 150 פוסטים ב-20 תגיות. ללא פלאגין מותאם אישית, תצטרכו ליצור ידנית Jekyll::Generator עבור כל תגית. זה לא יעיל ומועד לשגיאות. Jekyll כתוב ב-Ruby ומספק Jekyll Plugins API מלא דרך פלאגינים. פלאגינים הם מחלקות Ruby שמתחברות לצינור יצירת האתר. הם מאפשרים הוספת תגיות Liquid חדשות, מסננים, מחוללי עמודים, ממירי פורמט ו-hooks. GitHub Pages לא מריץ פלאגינים שרירותיים (רק רשימה לבנה), ולכן השימוש בהם דורש CI/CD משלכם. אנו מפתחים פלאגינים מותאמים אישית במפתח מלא: מרעיון ועד פריסה עם בדיקות ותיעוד. למהנדסים שלנו יש ניסיון של 10+ שנים עם Ruby ו-Jekyll, מה שמבטיח יציבות וביצועים. שימוש בפלאגינים מותאמים אישית מקצר את זמן הבנייה ב-40% ומאיץ את יצירת העמודים בממוצע פי 2. בפרויקט אחד עבור חברת מדיה גדולה, החלפנו סט של 5 פלאגינים מוכנים בפלאגין מותאם אחד, וקיצרנו את זמן הבנייה מ-12 ל-7 דקות וביטלנו התנגשויות תלויות.
מותאם אישית לעומת פלאגינים מוכנים: מתי מותאם עדיף?
פלאגינים מוכנים מ-RubyGems חוסכים זמן, אך לעתים קרובות לא עומדים בדרישות ספציפיות. פלאגין מותאם אישית שנכתב עבור הארכיטקטורה שלכם רץ בממוצע פי 2 מהר יותר ביצירת עמודי תגיות ואינו מכיל קוד מיותר. הוא מונע התנגשויות גרסאות ונותן לכם שליטה מדויקת על ההתנהגות. אם אתם צריכים משהו ייחודי, פלאגין מותאם אישית הוא לעתים קרובות האפשרות היחידה.
סוגי פלאגינים ומתי להשתמש במה
| סוג | מחלקת-על | מקרה שימוש |
|---|---|---|
| מחולל | Jekyll::Converter |
יצירת עמודים פרוגרמטית, איסוף נתונים |
| ממיר | Jekyll::Command |
פורמטי תוכן חדשים (AsciiDoc, reStructuredText) |
| פקודה | jekyll mycommand |
פקודות CLI חדשות (Liquid::Tag) |
| תגית | {% mytag %} |
תגיות מותאמות אישית Liquid::Block |
| בלוק | {% block %}...{% endblock %} |
תגיות עם תוכן Liquid::Template.register_filter |
| מסנן | כלול ב-# _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) |
מסננים מותאמים אישית `{{ 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) תגית מותאמת אישית להטמעת סרטונים
תגית עם טעינה עצלה:
# _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)
מחולל לעמודי תגיות
Jekyll יוצר # _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) באופן טבעי רק דרך פלאגינים של צד שלישי. יישום:
# _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
Hooks לעיבוד לאחר
# _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 איך מפתחים פלאגין מותאם אישית?
תהליך הפיתוח כולל מספר שלבים. ראשית, אנו מנתחים את המשימה: איזה נתונים צריך לעבד, באיזו תדירות התוכן משתנה, האם נדרשת אינטגרציה עם APIs חיצוניים. לאחר מכן אנו מתכננים את ארכיטקטורת הפלאגין: בוחרים את הסוג (מחולל, ממיר, תגית, מסנן), מגדירים קונפיגורציה ו-hooks. אנו כותבים קוד Ruby בהתאם לעקרונות SOLID. לאחר היישום, אנו מוסיפים בדיקות יחידה RSpec עם כיסוי של לפחות 95% — זה חובה. השלב האחרון הוא אינטגרציה לפרויקט שלכם: הגדרת Gemfile או תיקיית _site/tags/, יחד עם 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. אנו מבטיחים יציבות וביצועים: אנו משתמשים בטעינת נתונים עצלה, caching ואופטימיזציית שאילתות. כל פרויקט כולל תיעוד והדרכת צוות. צרו קשר לייעוץ על הפרויקט שלכם. הזמינו פיתוח פלאגין במפתח מלא — נספק קוד, בדיקות ו-CI/CD.
איך לשלב את הפלאגין בפרויקט?
התקינו את הפלאגין דרך Gemfile או העתיקו אותו לתיקיית # _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 . עבור CI/CD, נדרש שלב להתקנת תלויות gem. אם אתם מתכננים להשתמש בפלאגין ב-GitHub Pages, תצטרכו CI של צד שלישי, מכיוון ש-Pages לא תומך בפלאגינים שרירותיים.
לוחות זמנים לפיתוח
| סוג פלאגין | זמן משוער |
|---|---|
| מסנן או תגית פשוטה | 0.5–1 יום |
| תגית עם פרמטרים | 1–2 ימים |
| מחולל לעמודים | 2–3 ימים |
| ממיר פורמט | 3–5 ימים |
| פלאגין מורכב עם API ובדיקות | 1–2 שבועות |
צרו קשר לייעוץ על הפרויקט שלכם. קבלו הערכת משימה תוך יום אחד.







