grunt-este-watchとgrunt-contrib-connectで軽快ファイル監視とLiveReload

- [grunt-contrib-watch が重いので grunt-este-watch を試したら幸せになった - てっく煮ブログ](http://tech.nitoyon.com/ja/blog/2013/10/10/grunt-watch-slow/)

- [grunt-este-watch めっちゃ便利やんけ - 音の鳴るブログ](http://mohayonao.hatenablog.com/entry/2013/10/16/071920)

などを見て、軽いのいいなーと思って試してみました。
が、使うのは簡単だったのですけれどいろいろつまずいたので、それも含めたメモなどを。

## インストール

- grunt
- grunt-contrib-conenct
- grunt-este-watch

をインストールします。ついでに

- grunt-contrib-jade
- grunt-contrib-stylus

をインストールしてJadeとStylusのファイルを自動でコンパイル・LiveReloadしてみます。

```sh:Terminal
$ npm install grunt grunt-contrib-jade grunt-contrib-stylus \
> grunt-contrib-connect grunt-este-watch
```

## Gruntfile

`Gruntfile`を以下のように書きます。

```coffeescript:Gruntfile.coffee
module.exports = (grunt) ->

  grunt.initConfig
    connect:
      server:
        options:
          # LiveReloadに必要なスクリプトを</body>直前に挿入してくれる
          # 多分grunt-contrib-connect ver.0.4.0から
          # HTML5などでbodyタグを書いていないと挿入してくれないみたい……
          livereload: true
    esteWatch:
      options:
        # 監視対象のディレクトリ
        # ['./**']などとするとサブディレクトリを含むようになる
        dirs: ['.']
        livereload:
          enabled: true
          # 監視対象とするファイルの拡張子
          extensions: ['jade', 'styl', 'stylus']
          port: 35729
      # *.jadeのハンドラ
      jade: (filepath) ->
        # お好みでタスクのオプションなどを設定をする
        grunt.config 'jade.options.pretty', true
        # 更新されたファイルだけコンパイルするように指定する
        grunt.config 'jade.compile.files', [
          expand: true
          ext: '.html'
          src: filepath
        ]
        # 実行するタスク名を返す
        # 複数実行する場合は['jade', 'stylus']のように配列で返す
        'jade'
      # *.stylのハンドラ
      styl: '<%= esteWatch.stylus %>'
      # *.stylusのハンドラ
      stylus: (filepath) ->
        grunt.config 'stylus.options.compress', false
        grunt.config 'stylus.compile.files', [
          expand: true
          ext: '.css'
          src: filepath
        ]
        'stylus'

  grunt.loadNpmTasks 'grunt-contrib-connect'
  grunt.loadNpmTasks 'grunt-contrib-jade'
  grunt.loadNpmTasks 'grunt-contrib-stylus'
  grunt.loadNpmTasks 'grunt-este-watch'

  grunt.registerTask 'default', ['connect', 'esteWatch']

  return
```

### 二重拡張子の場合

二重拡張子のファイルをコンパイルする（grunt-contrib-coffeeで`.coffee.md`をコンパイルするなど）際に、

```coffeescript:Gruntfile.coffee
esteWatch:
  options:
    livereload:
      extensions: ['coffee.md']
```

と書いても反応しない模様……　なので

```coffeescript:Gruntfile.coffee
esteWatch:
  options:
    livereload:
      extensions: ['md']

# ...

md: (filepath) ->
  # 拡張子が.coffee.mdでなかったらタスクを実行しない
  return unless /\.coffee\.md$/.test filepath
  grunt.config 'coffee.options.bare', true
  grunt.config 'coffee.compile.files', [
    expand: true
    ext: '.js'
    src: filepath
  ]
  'coffee'
```

などど書くと良いのではないかなーと。

## 実行

あとは`grunt`コマンドを実行して、

```sh:Terminal
$ grunt
```

`localhost:8000`にブラウザでアクセスしたあとに、`.jade`や`.styl`や`.stylus`のファイルを更新すればLiveReloadで快適なコーディングが出来ると思います。面倒だなー、ただJade, Stylus/Less, CoffeeScriptのコンパイルとLiveReloadしたいんだけど、というときは[generator-prototyping](https://github.com/sasaplus1/generator-prototyping/)を使うとラクチンです。（宣伝）

## つまずいたところ

grunt-este-watchを使っていてつまずいたところなど。

### お気に入りのテキストエディタで保存しても更新が検知されない

監視対象となっているファイルに対して`touch`や`echo`などを使うと更新が検知され、LiveReloadで再読み込みされるが、テキストエディタで保存するとなぜか最初の1回目以降は更新が検知されない、という問題に直面したりしました。

SublimeText2の場合は設定ファイルの`atomic_save`を`false`にすると良いみたいです。
https://github.com/steida/grunt-este-watch/#note-about-editors-atomic-save

MacVimの場合は

```
set nobackup
set nowritebackup
```

で良いみたいです。

どうもテキストエディタが素直にファイルに対して上書き保存しているわけではないようで、別のファイル名で保存したあとに元のファイル名にリネームしていることにより発生しているようです。（たぶん）これをatomic saveというのかな？

- [fs.watch() unreliable when using vim](https://github.com/joyent/node/issues/3172)
- [Why does Vim save files with a ~ extension?](http://stackoverflow.com/questions/607435/why-does-vim-save-files-with-a-extension#answer-607475)

これによりgrunt-este-watchが内部的に使用している`fs.watch`の`event`が`change`でなく`rename`になり、それ以降検知されないのではないかなーと。

以下は調べた方法など。

```js:watch.js
require('fs').watch(process.argv[2], function(event, filename) {
  console.log(Date.now(), arguments);
});
```

```sh:Terminal
$ touch temp.txt
$ node watch.js temp.txt
1384501599596 { '0': 'change', '1': null }
1384501600478 { '0': 'change', '1': null }
1384501601110 { '0': 'change', '1': null }
# ここまで $ touch temp.txt
1384501604980 { '0': 'change', '1': null }
1384501606445 { '0': 'change', '1': null }
1384501607053 { '0': 'change', '1': null }
# ここまで $ echo > temp.txt
1384501612293 { '0': 'rename', '1': null }
1384501612295 { '0': 'rename', '1': null }
# ここまでMacVimで:wしたもの、以降反応なし
```

ただ、Mac OS X Snow Leopard + MacVim, OS X Mountain Lion + MacVimでは上記の設定をしないと更新が検知されないのだけど、OS X Marverics + MacVimだと設定をしなくてもちゃんと動作してくれています。謎だ……

### 監視ファイル数の上限

grunt-este-watchで監視するファイル数がファイルディスクリプタの最大値より多くなると、`esteWatch`のタスクが失敗し、監視ができずにタスクが失敗します。grunt-contrib-watchでは発生しなかったと思います。

- [EMFILE error when using fs.watch on many items](https://github.com/joyent/node/issues/2479)

解決策としては、なんとか監視対象とするファイルを減らすか、ファイルディスクリプタの上限を変更するか、でしょうか。

ちなみにOS Xのデフォルト値は

```sh:Terminal
$ ulimit -n
256
```

でした。