024. JeKyll 커스터마이징 - (7) 플러그인

커스터마이징 - (7) 플러그인

본 카테고리는 정적 블로그 생성도구인 Jekyll에 관하여 작성하는 곳이다.

공식 사이트의 내용을 좀 더 풀어쓰고 스크린샷을 첨부하여 좀 더 상세히 기록하고자 한다.

참고 Jekyll 공식 사이트

플러그인

Jekyll은 사이트에 특정한 사용자 정의 컨텐츠를 생성할 수 있는 Hook을 가진 플러그인 시스템을 가지고 있다.

이를 통해 Jekyll의 소스 자체를 변경할 필요없이 사이트에 대한 사용자 지정 코드를 실행할 수 있다.

참고 Github Pages의 플러그인
Github Pages는 Jekyll을 사용하여 작동하지만 --safe 옵션을 사용하여 작동하므로 사용자 정의 플러그인은 동작하지 않는다.
번거롭지만 플러그인을 동작시키려면 사이트를 로컬에 생성하고 생성된 결과물을 Github로 푸시하면 된다.

플러그인 설치

플러그인은 3가지 방법으로 설치할 수 있다.

사이트 소스 폴더에 포함

사이트의 루트 디렉토리에 _plugins 폴더를 생성한 뒤 플러그인을 작성한다.

Jekyll은 사이트를 생성하기 전에 _plugins 폴더의 *.rb 파일을 로드한다.

환경 설정 파일에 추가

_config.yml 환결설정 파일에 gems 키를 사용하여 플러그인의 gem 이름을 선언한다.

1
2
gems: [jekyll-coffeescript, jekyll-watch, jekyll-assets]
# 각 gem을 자동으로 요구한다.

다음으로 _config.yml에 선언한 플러그인을 설치하면 된다.

1
gem install jekyll-coffeescript jekyll-watch jekyll-assets

Gemfile에 추가

루트 디렉토리에 있는 Gemfile에 아래와 같이 bundle을 그룹으로 작성한다.

1
2
3
4
group :jekyll_plugins do
gem "my-jekyll-plugin"
gem "another-jekyll-plugin"
end

다음으로 아래의 명령어를 실행하여 bundle group에 속한 모든 플러그인을 설치한다.

1
bundle install

참고 플러그인 추가 방법에 관하여
위의 3가지 플러그인 설치 방법인 _plugins, _config.yml, Gemfile을 동시에 사용할 수 있다.

일반적으로 플러그인은 아래의 5가지 카테고리 중 하나로 분류된다.

  • 생성기(Generators)
  • 변환기(Converters)
  • 명령어(Commands)
  • 태그(Tags)
  • 후크(Hooks)

이제부터 위의 카테고리들을 하나씩 소개한다.

생성기(Generators)

Jekyll에서 생성기를 이용하면 사용자가 지정한 규칙에 따라 추가 컨텐츠를 작성할 수 있다.

생성기는 Jekyll:Generator의 하위 클래스로 Jekyll::Site 객체를 정의하는 generate 메소드를 가지고 있다.

이때 generate의 반환값은 무시된다.

생성기는 기존에 존재하는 컨텐츠 목록이 만들어진 뒤에 사이트가 생성되기 전에 실행된다.

YAML 머리말을 가진 페이지는 Jekyll::Page의 객체로 저장되어 site.pages를 통해 접근할 수 있다.

정적 파일은 Jekyll::StaticFile의 객체로 저장되며 site.static_files를 통해 접근할 수 있다.

자세한 내용은 아래 변수 포스팅과 Jekyll::Site 페이지를 참고하자.

참고 Jekyll::Site 코드

참고 Jekyll::Page 코드

참고 Jekyll::StaticFile 코드

예를 들어, 생성기는 빌드 시 템플릿 변수에 계산된 값을 저장할 수 있다.

아래 예제의 템플릿 reading.html에서 생성자를 채우는 ongoingdone 변수를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
module Reading
class Generator < Jekyll::Generator
def generate(site)
ongoing, done = Book.all.partition(&:ongoing?)

reading = site.pages.detect {|page| page.name == 'reading.html'}
reading.data['ongoing'] = ongoing
reading.data['done'] = done
end
end
end

아래 예제는 새로운 페이지를 생성하는 다소 복잡한 생성기 코드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module Jekyll

class CategoryPage < Page
def initialize(site, base, dir, category)
@site = site
@base = base
@dir = dir
@name = 'index.html'

self.process(@name)
self.read_yaml(File.join(base, '_layouts'), 'category_index.html')
self.data['category'] = category

category_title_prefix = site.config['category_title_prefix'] || 'Category: '
self.data['title'] = "#{category_title_prefix}#{category}"
end
end

class CategoryPageGenerator < Generator
safe true

def generate(site)
if site.layouts.key? 'category_index'
dir = site.config['category_dir'] || 'categories'
site.categories.each_key do |category|
site.pages << CategoryPage.new(site, site.source, File.join(dir, category), category)
end
end
end
end

end

위의 생성기는 categories_index.html 파일의 레이아웃을 사용하여 각 카테고리의 게시물을 나열한 뒤, 각 카테고리의 폴더 아래 포스트를 생성한다.

생성기는 하나의 메소드만 구현하면 된다.

메소드명 설명
generate 컨텐츠를 생성

변환기(Converters)

특정 Markup 언어를 사용하려면 변환기를 구현하여 사이트에 포함시킬 수 있다.

Jekyll이 제공하는 Markdown과 Textile Markup 언어도 변환기를 사용하여 구현되었다.

참고 YAML 머리말에 관하여
Jekyll은 YAML 머리말이 존재하는 파일만 변환한다는 것을 기억하자.

아래는 .upcase로 끝나는 모든 포스트를 가져와 UpcaseConverter로 변환하는 변환기 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Jekyll
class UpcaseConverter < Converter
safe true
priority :low

def matches(ext)
ext =~ /^\.upcase$/i
end

def output_ext(ext)
".html"
end

def convert(content)
content.upcase
end
end
end

변환기는 세 가지 메소드를 구현해야한다.

메소드명 설명
matches 확장자가 변환기가 처리할 수 있는 확장자인지 체크
일치하면 true, 일치하지 않으면 false 반환
output_ext 출력되는 결과물에 대한 확장자를 지정
.html이 일반적인 경우
convert 컨텐츠를 변환하는 프로세스
YAML 머리말이 아닌 나머지 파일의 내용을 변환하여 문자열을 반환
위의 UpcaseConverter 예제를 참고

명령어(Commands)

Jekyll의 2.5.0 버전 이상부터 추가된 기능으로 jekyll로 시작하는 하위 명령어를 추가할 수 있는 플러그인이다.

아래와 같이 Gemfilejekyll_plugins 그룹에 명령어를 포함시키면 된다.

1
2
3
group :jekyll_plugins do
gem "my_fancy_jekyll_plugin"
end

각 명령어는 Jekyll:Command 클래스를 상속해야만 하며, 하나의 클래스 메소드인 init_with_program을 구현하면 된다.

아래의 예제를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyNewCommand < Jekyll::Command
class << self
def init_with_program(prog)
prog.command(:new) do |c|
c.syntax "new [options]"
c.description 'Create a new Jekyll site.'

c.option 'dest', '-d DEST', 'Where the site should go.'

c.action do |args, options|
Jekyll::Site.new_site_at(options['dest'])
end
end
end
end
end

명령어는 한 가지 메소드를 구현하면 된다.

메소드명 설명
init_with_program Mercenary::Program 객체를 파라미터로 받아 동작

참고 Mercenary::Program Repository

태그(Tags)

사용자 정의 Liquid 태그를 사용하고 싶다면 태그 시스템에 연결하면 된다.

Jekyll에 포함되어 있는 예제에서는 highlightinclude 태그가 대표적인 예시다.

아래의 예제는 페이지가 렌더링 된 시간을 출력하는 태그 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Jekyll
class RenderTimeTag < Liquid::Tag

def initialize(tag_name, text, tokens)
super
@text = text
end

def render(context)
"#{@text} #{Time.now}"
end
end
end

Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)

Liquid 태그는 아래의 한 가지 메소드를 구현하면 된다.

메소드명 설명
render 태그 컨텐츠를 출력

다음으로 사용자 정의 태그를 Liquid 템플릿 엔진에 꼭 등록해야 한다.

1
Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)

Liquid 템플릿 엔진에 등록을 마치고 나면 아래 처럼 사용자 정의 태그를 사용할 수 있다.

1
<p>{% render_time page rendered at: %}</p>

위 태그가 렌더링하는 결과물은 아래와 같다.

1
<p>page rendered at: Tue June 22 23:38:47 –0500 2010</p>

Liquid 필터

위에서 Liquid 템플릿 엔진에 태그를 추가하듯 Liquid 템플릿 시스템에 필터를 추가할 수 있다.

필터는 간단하게 메소드는 Liquid로 내보내는 모듈로, 모든 메소드는 필터의 입력을 나타내는 한 개 이상의 파라미터를 가져야 한다.

메소드의 반환값이 필터의 출력이 된다.

1
2
3
4
5
6
7
8
9
module Jekyll
module AssetFilter
def asset_url(input)
"http://www.example.com/#{input}?#{Time.now.to_i}"
end
end
end

Liquid::Template.register_filter(Jekyll::AssetFilter)

참고 Liquid를 이용한 site 객체 접근
Jekyll은 context.registers[:site]에서 Liquid의 context.registers 기능으로 site 객체에 접근할 수 있게 해준다.
예를 들어 context.registers[:site].config를 통해 _config.yml 환경설정 파일에 접근할 수 있다.

플래그(Flags)

사용자 정의 플러그인을 구현할 땐 아래 두 플래그를 주의해야 한다.

플래그명 설명
safe Jekyll에게 플러그인이 임의의 코드 실행이 불가능한 환경에서 안전하게 실행 가능한지 알리는 플래그
Github pages에서 특정 핵심(Core) 플러그인을 사용할 수 있는지 판단하는 데 사용
true : 임의 코드 실행 허용
‘false’ : 임의 코드 실행 미허용
priority 플러그인을 읽어들이는 우선순위를 결정
:lowest, :low, :normal, :high, :highest로 정의

위에서 설명한 플러그인 예제를 사용할 때 플래그는 아래와 같이 지정한다.

1
2
3
4
5
6
7
module Jekyll
class UpcaseConverter < Converter
safe true
priority :low
...
end
end

후크(Hooks)

후크를 사용하면, 플러그인이 빌드 프로세스에서 다양하고 세분화된 제어를 수행할 수 있다.

플러그인에 후크를 지정하면 Jekyll은 후크로 지정된 시점에서 플러그인을 호출한다.

후크는 컨테이너와 이벤트명에 등록되며, 등록하려면 Jekyll:Hooks.register를 호출하고 컨테이너, 이벤트명, 코드를 전달하고 후크가 실행될 때마다 호출하면 된다.

예를 들어 Jekyll이 포스트를 렌더링 할 때마다 사용자 정의 기능을 실행하려면 아래와 같은 후크를 등록하면 된다.

1
2
3
Jekyll::Hooks.register :posts, :post_render do |post|
# Jekyll이 포스트를 렌더링 할때마다 호출되는 코드를 추가한다
end

Jekyll은 :site, :pages, posts, :documents에 대한 후크를 제공한다.

Jekyll은 모든 경우에 컨테이너 객체를 첫 번째 파라미터로 사용하여 후크를 호출한다.

:pre_render, :site, :post_render 후크는 두 번째 파라미터로 페이로드 해시를 사용한다.

:pre_render의 경우, 렌더링을 진행하는 동안 사용할 수 있는 변수를 완벽하게 제어하고, :site, :post_render의 경우, 사이트를 전부 렌더링한 뒤의 최종 값을 포함하고 있다.

이는 사이트맵이나 피드를 사용할 때 유용하다.

사용 가능한 후크의 전체 목록은 다음과 같다.

컨테이너명 이벤트명 호출 시기
:site :after_init 사이트가 초기화되고, 렌더링되기 직전
사이트 환경설정을 수정하는 데 적합
:site :after_reset 사이트 재설정 직후
:site :post_read 사이트 데이터를 디스크에서 읽고 불러온 직후
:site :pre_render 사이트를 렌더링하기 직전
:site :post_render 사이트를 렌더링하고 파일을 작성하기 전
:site :post_write 전체 사이트를 디스크에 쓴 후
:pages :post_init 페이지가 초기화 될 때마다
:pages :pre_render 페이지 렌더링 직전
:pages :post_render 페이지 렌더링 후 디스크에 쓰기 전
:pages :post_write 디스크에 페이지를 쓴 후
:posts :post_init 포스트가 초기화될 때마다
:posts :pre_render 포스트 렌더링 직전
:posts :post_render 포스트 렌더링 후 디스크에 쓰기 전
:posts :post_write 디스크에 포스트를 쓴 후
:documents :post_init 문서가 초기화 될 때마다
:documents :pre_render 문서 렌더링 직전
:documents :post_render 문서 렌더링 후 디스크에 쓰기 전
:documents :post_write 디스크에 문서를 쓴 후

사용가능한 플러그인 목록

Jekyll 사이트에 작성된 몇가지 유용한 플러그인 목록

생성기(Generators) 관련 플러그인

변환기(Converters) 관련 플러그인

필터(Filters) 관련 플러그인

태그(Tags) 관련 플러그인

컬렉션(collections) 관련 플러그인

기타 플러그인

편집기(Editors) 관련 플러그인