My first Jekyll GeneratorのためにJekyllを読む
先日Jekyll と Middleman の比較をして、Jekyll の方が拡張はやりやすそうという結論を出したわけだけど、実際に書くとなったら公式のドキュメントを読んでるだけだとピンとこない部分があって、それが Generator.
Generators | Jekyll • Simple, blog-aware, static sites
いきなり Generator に site オブジェクトを突っ込んで、そこから Page を生成する例が出てくるんだけど、なんでこれでうまくいくのかがよく分からない。
この理由はそもそも全体の流れが説明されていないから。ということでコードを読んでみることにする。
対象バージョンは現行の Jekyll 3.8.5
まずはcommandから
jekyll の操作は
$ jekyll <command>
から始まる。これを担うのが commands/ 以下のコマンド群だ。
exe/jekyll
を読むと command 群は init_with_program が呼ばれるのが分かるので、例えば build.rb で追っていくと
def init_with_program(prog)
prog.command(:build) do |c|
c.syntax "build [options]"
c.description "Build your site"
c.alias :b
add_build_options(c)
c.action do |_, options|
options["serving"] = false
Jekyll::Commands::Build.process(options)
end
end
end
中で process を呼び、process では
def process(options)
..
site = Jekyll::Site.new(options)
..
build(site, options)
build を呼んでて、
def build(site, options)
..
process_site(site)
process_site は superclass の
def process_site(site)
site.process
で、Jekyll::Site オブジェクトにたどりつく。
すべてを司るJekyll::SiteオブジェクトからGenerator#generateが呼ばれる
Jekyll::Site#process はこれだけ。
def process
reset
read
generate
render
cleanup
write
print_stats if config["profile"]
end
これを見るとピンとくるのが
Hooks | Jekyll • Simple, blog-aware, static sites
ですね。ここでようやく全体の流れが分かる。これを見ると Generator は Reader と Renderer の間で仕事をしていることが分かる。
generate の中身は
def generate
generators.each do |generator|
start = Time.now
generator.generate(self)
Jekyll.logger.debug "Generating:",
"#{generator.class} finished in #{Time.now - start} seconds."
end
end
登録された Generator それぞれの generate メソッドに Jekyll::Site そのものが渡され、site を書き換えることでコンテンツを増やし、その後 render, write が仕事をすることで実際のファイルが生成される、という流れになる。
なるほど。
Generator間の依存関係はpriorityで調整
generator の初期化は site.rb の #setup の中の
self.generators = instantiate_subclasses(Jekyll::Generator)
からの
def instantiate_subclasses(klass)
klass.descendants.select { |c| !safe || c.safe }.sort.map do |c|
c.new(config)
end
end
で、ここの sort で plugin.rb の
def self.<=>(other)
PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
end
が呼ばれて、priority の高いものから並ぶようになっている。
※ ちなみにこの setup が実行され終わる時に after_init に register した hook が走るようになっている。
したがって、例えばカテゴリやタグといった情報をもとにイマドキのリッチなスライドっぽいリンクを並べた一覧ページを作りたいということになったら、
- Page をなめて content から data ( Front Matter ) の中に最初の heading や paragraph, img など、リンク生成時に使いたいデータをまとめ直す処理を行う
- 1 のデータをもとにカテゴリやタグの Page を追加する
この際に 1, 2 の順になるように priority を調整する、という感じで利用することができる。
※ これくらいならひとまとめでもよさそうだけど、他の generator で生成したコンテンツも利用できるようになっていた方が都合がよいかもしれない。分かってくると Ganerator は応用範囲が広い感じがする。
MiddlemanのproxyのようなものもGeneratorで解決できる
考え方は以下のような感じ。
- site.data, site.pages などから必要なデータを取得して
- 取得したデータをもとに Jekyll::PageWithoutAFile を継承した Page を生成
- この際、Page#initialize とはリスコフの置換原則を破るようにしないと page 独自の data は渡せないことに注意が必要。
def initialize(site, base, dir, name)
適当に keyword args 足せばいいと思う :p
layout はいちいち指定してもいいけど、_config.yml の defaults で一気に指定するようにした方がたぶん他のものと整合性が確保できてよい感じになる。
Front Matter Defaults | Jekyll • Simple, blog-aware, static sites