Jekyll で --watch の代わりに Grunt を使ってみるテスト
このブログでは Jekyll を使ってることは何度か書いたのだけど、いままで記事を書くときには jekyll --auto
を実行した状態で書いていた。このようにしておくと、ファイルを書き換えたら自動的にサイトをビルドしてくれるようになる。ただ、このコマンドを実行してると CPU がグオーンと音を上げ始め、クアッドコアで CPU 使用率 25% に達するという地球に優しくない状態であった。
原因を調べてみると directory_watcher
モジュールが犯人だった。このモジュールは、監視対象のディレクトリー配下の全ファイルに対して、毎秒、File::Stat()
を実行する、という富豪的実装になっている。もちろん、ファイルの数が少ないときには問題なく動くんだけども、ファイルの数が増えると CPU を浪費してしまう。
たとえば、このサイトの場合、600 個以上の記事があって、Jekyll が 900 個のファイルを生成する。しかも、.git
フォルダーの下には 5,000 個程度のファイルがある。ひどいことに、Jekyll 0.12 までのバージョンは、これらのファイルすべてを監視対象にする。毎秒 6,500 個のファイルを stat するわけだから、当然、CPU は振り切る。
Jekyll 1.0 になって、コマンドも jekyll build --watch
に変わって、_site
(サイトの出力先) や .git
が監視対象から外れたので、パフォーマンスはだいぶ改善した。それでも自分の環境で常時 10% ぐらい CPU を消費し続けている。
ということで、--watch
(もしくは --auto
) オプションの代わりに、流行の Grunt を使ってみることにした。
Grunt の設定ファイル
使っているバージョンやプラグインは次の通り。
- Jekyll 1.0.3
- Grunt 0.4.1
grunt-shell-spawn
プラグインgrunt-contrib-watch
プラグイン
最初は、grunt-shell-spawn
ではなく grunt-jekyll
を使ってたんだけど、grunt-jekyll
は Jekyll を実行中の途中経過を表示してくれないので使うのをやめた。
Grunt の使い方については Getting started - Grunt を見たほうが早いだろうから、ここでは package.json
と Gruntfile.js
を紹介する。
package.json
{
"name": "tech-ni",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-shell-spawn": "~0.2.4",
"grunt-contrib-watch": "~0.4.4"
}
}
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
shell: {
jekyll: {
command: 'jekyll build',
options: {
async: false
}
}
},
watch: {
jekyll: {
files: ['_posts/**/*.md', '_layout/*.html', '_includes/*.html'],
tasks: ['shell:jekyll']
}
}
});
grunt.loadNpmTasks('grunt-shell-spawn');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['shell:jekyll']);
};
(Jekyll 0.12 までを使用している場合は、jekyll build
を jekyll
で置き換えるべし)
使い方
grunt watch
を実行すると、ファイルの監視を始める。記事や HTML が編集されたら、jekyll build
が実行されるようになっている。
しかし、Jekyll で監視するよりも CPU 消費量は小さいが・・・0 ではない・・・!? と、ここで調べてみて気づいたんだけども、Grunt の watch は、ネイティブ API の fs.watch()
を使ってファイルを監視していない!!! gaze
モジュールを使って定期的にファイルを stat しているだけだった。
これでは Ruby の directory_watcher
と同じである。
どうやら、Node.js の fs.watch()
は Mac OS でファイル名を取れなかった過去があるなど、歴史的に不安定であり、結局、stat で独自に監視する、という手順が一般的になったようだ。なんという罠。