rcmdnk's blog
Last update

ジキルとハイド (子どものための世界文学の森 31)

Octopress周りをちょっと整理でもしてみようかと思い、 gemを全般的に新しいのにアップデートしてみました。

特にJekyllが2.5.3から3.3.1と、3系になったのでちょっと色々といじる必要があって 大変だったのでそのまとめ。

Octopressでなくても素のJekyllでの2から3への移行の参考にもなると思います。

Octopressのバージョン

Octopressは2.5のバージョンを使っています。

3.0の開発もありますが、こちらはOctopress自体がgemとして配布されていたり 大分構造が違います。

ただ、どちらも最終コミットが1年前。。。

3.0の方が活発に開発されていくならいずれ移ろうかとも 思ってましたが、どうも停滞中なので 今のところ苦労してまで敢えてOctopress 3.0に移行する必要はないかな、と思っています。

octogray

Octopress用のテーマです。

変更されたGemfile/Rakefile/_config.yml等も含んでいてsetupスクリプトを走らせると必要な設定を一通りしてくれる様にもなっています。

このテーマをJekyll 3用にアップデートしました。

Jekyll 3

Jekyll 3に変更するにあたって一番意味がありそうなのは 生成速度が上がっている、という点。

Jekyll 3 - The Road Ahead - By Parker

このサイトのビルドはwerkerで行ってますが、レンダリング時間は Jekyll 2のときと3のときとでそれ程変わらず2分前後になっています。 若干3の方が速い様にも見えますが、一回一回結構差が大きいのと 3の方はまだビルド数が少ないので、まあ今のところ同じくらいかちょっと速いかな、といった感じ。

ビルドスピードに関しては下のようにちょっと工夫してたりしますが、 まだ改善の余地がありそうなのとJekyll 3でまた出来る事もあるかもしれないので また見てみたい所。

他にもシンタックスハイライトなどがデフォルトで使えるようになったり色々アップデートがあるみたいで、 GitHub Pagesのジェネレーターも今年の始めの頃にすでにJekyll 3になっています。

GitHub Pages now faster and simpler with Jekyll 3.0

仕様のアップデートに関しては、おそらく今までに作ったプラグイン等、 もっと簡単にうまく書けたりパフォーマンスも良く出来たりするものもあると思いますが、 取り敢えず今回は動く様に、ということでアップデートをしています。

以下、アップデートをする際にしたメモなど。

JEKYLL_ENV=production

jekyll buildを呼ぶ際に使われるJEKYLL_ENVという環境変数があり、 3ではこれにdevelopmentという値が入っています。

これだといくつかの要素を無視したり、またシンボリックリンクがsource内にあると そのままシンボリックリンクとしてコピーします。

勿論そのままでは出来たものを他に持っていって使う事も出来ません。

さらに2回目以降もシンボリックリンクをコピーしようとするので、既にあるシンボリックリンクとバッティングして エラーになります。

実際に公開するものを作るには

$ JEKYLL_ENV=production jekyll build

とこの値をproductionにします。(環境変数として設定してしまっても勿論OK。)

ctopressではgenerateというタスクの中でjekyll buildを呼んでますが、 developmentにするメリットはないのと、シンボリックリンクを含むようなsourceを使ってて developmentだと使いものにならないので Rakefileの該当箇所にJEKYLL_ENV=production を追加しました。

Configuration - Jekyll • Simple, blog-aware, static sites

Plugin中のsite.posts

プラグインの中でブログポストの一覧を呼ぶ時に、2ではsite.postsと呼んでいましたが 3ではsite.posts.docsがこれにあたります。

一方site.pagesの方はそのまま同じくsite.pagesです。

この辺はビルド時にwarningが出る(エラーにはならないで一応2の時の様に良しなにしてくれる)ので発見しやすいです。

また、ポストやページを書く際にLiquidタグの中でsite.posts等を使う事もありますが、 この場合はsite.postsのままです。プラグインの中だけ変更する必要があります。 さらに、このpostsの順番が前と違う?みたいで、 前は古い方から入っていたため、 アーカイブを作る時に以前は並びを逆順にしてたんですが(新しいものを上に書くため)、 Jekyll 3ではどうも逆順に入ってるらしく この逆順にする部分をはずしました。

source/blog/archives/index.html
1
2
3
4
5
 {% assign index = true %}
-{% for post in site.posts reversed %}
+{% for post in site.posts %}
 {% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
 {% unless year == this_year %}

こんな感じ。

Block/TagのPlugin中のcontext['page']について

BlockやTagのプラグインを作る際、継承してる関数の引数にあるcontextから context['page']を持ってくることでページの情報を取ってこれます。

このオブジェクトはハッシュの様な機能も持っていて、どの様keyを持ってるかチェックすることがあるのですが、 Jekyll 2では

context['page'].has_key?("xxx")

としていましたが、Jekyll 3ではこのメソッドは常にtrueを返します。 実際にチェックしたい時には

context['page'].key?("xxx")

を使います。 has_keyの方は親クラスが持っていてkeyはこのオブジェクトのクラスで定義してる感じですが、 以前はhas_keyを再定義していたのが何故か違うメソッドを導入したようです。 まあ、なんか便利なことがあるんでしょうが、ここだけ見ると結構混乱のもとなので注意しないといけません。 (trueを返すだけなのでこの場ではエラーにならず、その後実際呼ぼうとしてkeywordエラーが起こる。)

_config.yml/Gemfileのアップデート

ビルド時に出るwarningやerrorに従って

_config.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-plugins: plugins
+plugins_dir: plugins
...
markdown: kramdown
kramdown:
use_coderay: true
-  coderay:
-    coderay_line_number: table
-    coderay_css: class
+  syntax_highlighter: true
+  syntax_highlighter_opts:
+    line_number: table
+    css: class
...
+gems: [jekyll-paginate]

こんな感じの変更。

またこれらのwarning等と一緒に出てきたgemを追加せよ、というものがあって、 Gemfileにjekyll-pagenatepygments.rbを追加。

gemojiは古いバージョンのまま

Jekyll 3とは関係無いですが、ついでにgemを全て最新のにしてみたところ、 gemojiというgemで問題が起こりました。

octopress-gemojiというプラグインを作って この中で使っているんですが、gemoji 2.1の時にはimages/emoji/の中に 絵文字の画像が全て入っていました。

これが最新の3.0では無くなってimages直下に、もともとimages/emoji直下に 置かれていたunicodeに登録されてない一部の絵文字だけが残っています。

実際にサイトとかで絵文字を使いたい場合には macOS Sierra or lateを用いて抜き出してください、とあります。。。

手元にSierraはあるので一回抜き出してプラグインのレポジトリに入れてしまう、と言う手も無いでも無いですが、 ちょっとあれなのでこのgemに関しては古い2.1を使います。

Extracting emojis · Issue #102 · github/gemoji

Jekyll 2の時、_config.yml

permalink: /blog/:year/:month/:day/:title/

という設定をしていました。これでブログポストのURLは http://rcmdnk.github.io/blog/2016/12/28/computer-git/みたいになります。 (blog/2016/12/28/computer-git/index.htmlが実際のページのファイル)

一方、sourceディレクトリ直下に適当なMarkdownやHTMLを置いておくと、 たとえばsource/testpage.mdというファイルを作れば そのままhttp://rcmdnk.github.io/testpage.htmlと言ったHTMLが出来ていました。

同じ設定のまま3でビルドしてみるとこのtestpageに関して /testpage/index.htmlというファイルが出来る様になっていました。

ただし、直接HTMLを置いてYAMLの無いものだと何もせずにそのままコピーされるのでディレクトリを作ったりはしません。

結論から言うと、2と同じ様な動作にさせたい場合、_config.ymlの設定を

collections:
  posts:
    permalink: /blog/:year/:month/:day/:title/

と変更します。 これでsource/_posts/以下にあるブログポストのみにこのpermalinkが適用され 他にはデフォルトのdateという定義が使われ、この定義だと ディレクトリを作らずにHTMLのファイル名にもとの名前が使われます。

以下ちょっと細かい話ですが、ちょっとこの辺きちんと見ないと理解できなかったので 見てみました。

Jekyll 2.5のソース

2.5の方ではpost.rbの中で 

post.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def template
  case site.permalink_style
  when :pretty
    "/:categories/:year/:month/:day/:title/"
  when :none
    "/:categories/:title.html"
  when :date
    "/:categories/:year/:month/:day/:title.html"
  when :ordinal
    "/:categories/:year/:y_day/:title.html"
  else
    site.permalink_style.to_s
  end
end

という関数が定義されていて、ここでsite.permalink_styleという値が使われてますが、 これは_config.ymlpermalinkで指定したものになります。

prettyとかdate(これがデフォルト)とかを指定すると指定のフォーマットを使い、 それ以外が指定されているとそのまま入力をフォーマットとして使う様になっています。

一方、page.rbの中では

page.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
def template
  if site.permalink_style == :pretty
    if index? && html?
      "/:path/"
    elsif html?
      "/:path/:basename/"
    else
      "/:path/:basename:output_ext"
    end
  else
    "/:path/:basename:output_ext"
  end
end

となっていて、prettyかつファイルがHTMLファイル(もしくはMarkdownから変更されたHTML)で かつindex.htmlでないと気に限りファイル名のディレクトリを作って その中にindex.htmlを作る様になっています。

つまり、上の様に/blog...みたいな定義だと一番下に行って そのファイル名のままのHTMLが出来ます。

Jekyll 3.3の現在のソース

一方、3.3の場合だとpost.rbではなくdocument.rbになりますが、この中で

document.rb
1
2
3
def url_template
  collection.url_template
end

となっていて、このcollection.url_template

collection.rb
1
2
3
4
5
def url_template
  @url_template ||= metadata.fetch("permalink") do
    Utils.add_permalink_suffix("/:collection/:path", site.permalink_style)
  end
end

で定義されています。

さらに

configuration.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def add_default_collections
...

  config["collections"] = Utils.deep_merge_hashes(
    { "posts" => {} }, config["collections"]
  ).tap do |collections|
    collections["posts"]["output"] = true
    if config["permalink"]
      collections["posts"]["permalink"] ||= style_to_permalink(config["permalink"])
    end
  end

  config
end

という定義もあります。

簡単に解釈すると、documentの中で呼ばれるurl_templatecollectionurl_templateなのですが、 postscollectionに関しては直接

collections:
  posts:
    permalink: /blog/:year/:month/:day/:title/

と指定しても

permalink: /blog/:year/:month/:day/:title/

とグローバルに指定してもこの値が入ってきます。 なのでどちらで指定しても同じになります。 (prettyなどを変換するメソッドもconfiguration.rbの中にあって2と変わらない定義です。)

一方、page.rbの方は

page.rb
1
2
3
4
5
6
7
8
9
def template
  if !html?
    "/:path/:basename:output_ext"
  elsif index?
    "/:path/"
  else
    Utils.add_permalink_suffix("/:path/:basename", site.permalink_style)
  end
end

となっていて、このadd_permalink_suffix

utils.rb
1
2
3
4
5
6
7
8
9
10
11
12
def add_permalink_suffix(template, permalink_style)
  case permalink_style
  when :pretty
    template << "/"
  when :date, :ordinal, :none
    template << ":output_ext"
  else
    template << "/" if permalink_style.to_s.end_with?("/")
    template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
  end
  template
end

となっています。

通常の名前付きのHTMLファイルを置いた時にはこのadd_permalink_suffixが呼ばれるわけですが、 これを見ると自分でpermalinkを設定した場合は一番下に行き、 URLの末尾の構造だけその設定したものと同じ様になる様にしています。

つまり、permalinkでタイトルをHTMLのファイル名に残すような設定にしていれば ページの方でもファイル名を残す様なURLになります。

一方、上で設定したように最後が/で終わっていればページの方でも ファイル名のディレクトリを作ってその中にindex.htmlを作る様になります。

2ではprettyかそうでないか、だけで分けていたものが 3では独自設定の場合それに沿うような形になるようになっています。

ということで、例えば404.mdの様なファイルも sourceの下に置いていてそれを404.htmlとして変換したかったりもするわけで、 ページに関しては勝手にディレクトリを作る様なことはしたくないので 上に書いたようにcollectionsを使って指定する様にしています。

もし404.mdとか特定のページだけをそうしたいなら そのページのYAMLブロックにpermalinkを書いても良いわけですが。

で、一番の問題はこの辺のドキュメントがきちんとまだ整理されてないことです。

Permalinks - Jekyll • Simple, blog-aware, static sites

Creating pages - Jekyll • Simple, blog-aware, static sites

これらを見ると ページに関しては完全に独立で基本dateのフォーマットで出力する、という感じに見れ、 変えたいなら各ページのYAMLブロックでの指定、という感じがします。

実際、stackoverflowにも同じ様な話があって、 中の人?っぽい人がサポートしようとしてますが結局良くわからずじまいで終わっています。

html - Jekyll - How do I create pages in the root directory? - Stack Overflow

おそらくこのアップデートは意図的にやってるんだと思いますが、 ドキュメントがドキュメントなのでパット見良くわからないわけです。

また、上のcollectionsを使った書き方は別のところにあります。

Collections - Jekyll • Simple, blog-aware, static sites

この辺の話どうなってるのかな、と思ってレポジトリを見てみたら、 2日前にまさにそんな感じのドキュメントのアップデートのPull Requestが出てました。

Improve permalinks docs by tomjohnson1492 · Pull Request #5693 · jekyll/jekyll

まだペンディング中ですが、これにcollectionsによる書き方と、 上の様な関係性を加えて、permalinkのところに全部まとめてもらえると正しく分かる様になるかな、と。

hookとか

Jekyll 3の開発者側の大きな変化として、ビルドのコマンドの中の 様々なところにHookが仕掛けられる様になったことだと思います。

サイト全体の情報をまとめて載せたかったり、 各ページのレンダリング毎に色々やりたかったりする時、 通常のプラグインを作るだけでは上手く行かないこともあります。

そういった時、Octopressでは octopress/hooks というツールを用意していて、ビルド時の色々な所にタスクを差し込める様にしていました。

Jekyll 3のjekyll/site.rb を見てみると色々な所にHooksが入ってます。 これでoctopress/hooksが無くても同じようなことが出来る様になっています。

Plugins - Jekyll • Simple, blog-aware, static sites

Octopressの作者の人もJekyll 3の機能で特にこれが待ち望んでたものだと言ってたり。 

3.0 RELEASE GAMEPLAN · Issue #3324 · jekyll/jekyll

octogray の中でもoctopress/hooksを使ったりモンキーパッチを入れたりしてますが、 そのうちこれもちゃんと整理していこうかと。

Filters

追記: 2017/01/24

Jekyll 2で何かしら自分でLiquid Filter({{xxx|my_filter}}の様に使えるfilter)を追加したい時、

1
2
3
4
5
6
7
module Jekyll
  module Filters
    def my_filter(input)
      "After Filter: #{input}"
    end
  end
end

みたいにしておくとAfter Filter: xxxの様に変換するフィルターが自作できました。

この様なFilterの仕組みをplugins/category_generator.rbの中で使っています。

octograyでは同じようなplugins/tag_generator.rb を作ってタグの管理も行っています。

この中でカテゴリーやタグをリンク化するフィルターを登録していましたが これが動いていませんでした。(ポストの下にあるPosted: …の部分。)

Jekyll 3ではFiltersというモジュールも特別なものではなくなり Filterを定義した後にきちんと登録の作業をしないといけないようです。

jekyll/filters.rb
1
2
3
4
5
6
7
8
9
10
...
module Jekyll
  module Filters
    ...
  end
end

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

という、最後のregister_filterがJekyll 2では無かったのが3ではあります。

ということで、ただ書くだけではダメなので敢えてFiltersの中に入れずに別途 モジュールを作って登録する様にしました。

plugins/category_generator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
module CategoryFilters

  def category_links(categories)
    categories.sort.map { |c| category_link c }.join(', ')
  end

  def category_link(category)
    dir = @context.registers[:site].config['category_dir']
    "<a class='category' href='/#{dir}/#{category.to_url}/'><span itemprop='articleSection'>#{category}</span></a>"
  end

end
Liquid::Template.register_filter CategoryFilters

こんな感じで登録してあげればcategory_links等が使える様になります。

追記ここまで

まとめ

大概の所はビルド時にwarningやerrorが出てそこから追えば すぐわかるところが大体でしたが、 permalinksの所はドキュメントを見ると内容がアップデートされてないので 逆に混乱してしまったりして、最終的にちゃんとコードを追わないとわからなかったので面倒でした。

でも一通り出来たと思うのでアップデートしてこれも作っています。

ということで、もし何か変なところとかあったら指摘してもらえるとありがたいです。

Sponsored Links
Sponsored Links

« GitのSubmoduleのプロトコルをsshからhttpsに変更する方法(またはその逆) rcmdnk.com: 独自ドメインになりました »

}