grunt-contrib-watch が重いので grunt-este-watch を試したら幸せになった
最近、Grunt と grunt-contrib-watch
を使っているのだけど、grunt-contrib-watch
が CPU を消費しがちである。
watch 対象のファイルが少ないうちは grunt-contrib-watch
は問題なく動くんだけども、ファイル数が増えてくると CPU の消費量が増えてくる。自分の環境では、1,000 個ぐらいのファイルを監視していると、常時 10% 程度 CPU を消費している。
この問題は既知であり、FAQ には次のように書いている。
たくさんのファイルを監視している場合、デフォルトの
interval
の値が小さすぎるかもしれない。options: { interval: 5007 }
のようにして増やしてみてほしい。詳しくは issues #35 と #145 を参照のこと (※日本語訳は私によるもの)Another reason if you're watching a large number of files could be the low default
interval
. Try increasing withoptions: { interval: 5007 }
. Please see issues #35 and #145 for more information.
なぜ CPU を消費するのか?
grunt-contrib-watch
はファイルの監視に gaze
というモジュールを使用している。
gaze
さんがファイルを監視するときには、ネイティブな監視 API の fs.watch()
だけでなく、fs.watchFile()
を併用している。fs.watchFile()
は定期的に fs.stat()
を実行しているだけであり、監視対象のファイルに変化がなくても CPU を消費する (詳しくは Node.js の fs.watch() と fs.watchFile() の違い をご覧あれ)。そのため、ファイルが増えるに従って、CPU の消費も激しくなるのである。
なんで gaze
はわざわざ CPU 負荷が高くなる fs.watchFile()
を利用しているのだろうか。それには歴史的な理由があるらしく、chokidar/README.md に fs.watch()
についての愚痴が書いてある。
Node.js の
fs.watch
は...
- Mac でファイル名を報告してくれない。
- Mac で TextMate2 のようなエディターで編集したときに何もイベントが発生しない
- ときどき、2 重にイベントを報告する。
- 利用価値のない
rename
イベントしかない。- 他にもたくさんの問題がある。
(※日本語訳は私によるもの)
Node.js
fs.watch
:
- Doesn't report filenames on mac.
- Doesn't report events at all when using editors like TextMate2 on mac.
- Sometimes report events twice.
- Has only one non-useful event:
rename
.- Has a lot of other issues
どうやって回避したか
CPU 負荷が耐えられなくなったので、grunt-contrib-watch
を諦めてみることにした。
検索して探したところ、grunt-este-watch
が便利そうであった。grunt-este-watch/README.md
には次のように書いてあった。
公式の grunt-contrib-watch の問題点は何か?
遅い、そして、バグがある。というのも、公式のものは歴史的な理由で fs.watchFile と fs.watch を組み合わせて使っている。 Node の 0.9.2 以降では、fs.watch には問題はない。
github.com/steida/este では最大限のパフォーマンスと安定性が必要だったので、新しい Node.js の file watcher を作る必要があった。この watcher は Mac, Linux, Windows で継続的にテストしている (※日本語訳は私によるもの)。
What's wrong with official grunt-contrib-watch?
It's slow and buggy, because it uses combination fs.fileWatch and fs.watch, for historical reason. From Node 0.9.2+, fs.watch is ok.
github.com/steida/este Needs maximum performance and stability, so that's why I had to create yet another Node.js file watcher. This watcher is continuously tested on Mac, Linux, Win platforms.
この作者が試す限り問題はないようだ。信じて試してみたところ、待機中に CPU を消費しなくなくなった。万歳。
設定例
ためしにこのブログのビルド環境に組み込んでみた。Gruntfile.js
がこちら。
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
shell: {
jekyll_build: {
command: 'jekyll build'
},
// snip
},
// snip
esteWatch: {
options: {
dirs: ['./', '_posts/*/', '_layouts', '_includes',
'javascript/**/', 'apollo/tutorial',
'_plugins/**/', 'stylesheets', 'javascripts'],
livereload: {
enabled: false
}
},
'*': function(filepath) { return 'shell:jekyll_build' }
}
});
grunt-este-watch
の設定は grunt-contrib-watch
とかなり違う点に注意。
grunt-este-watch
は監視するディレクトリの一覧を記述する。ディレクトリ配下のファイルが変更されたら、コールバック関数が呼び出される。
自分のコールバック関数では、単純に 'shell:jekyll_build'
と返しているので、ファイルが更新されたら grunt
は shell:jekyll_build
タスクを実行してくれる。その気になれば filepath
引数に応じて、返すタスク名を変えることもできる。
'*'
ってのは「すべての拡張子」を意味する。特定の拡張子を指定することもできる。grunt-este-watch/README.md のサンプルを見ると分かりやすい。
coffee: function(filepath) {
var files = [{
expand: true,
src: filepath,
ext: '.js'
}];
grunt.config(['coffee', 'app', 'files'], files);
grunt.config(['coffee2closure', 'app', 'files'], files);
return ['coffee:app', 'coffee2closure:app'];
},
// snip
css: function(filepath) {
if (grunt.option('stage')) {
return 'cssmin:app';
}
}
grunt-contrib-watch
の CPU 負荷が高くて困っている人はぜひ試してみてほしい。