Emacsを使って既存のファイルを編集していると，gulp.watch内でクラッシュが発生する問題の回避方法

# 要約

Emacsを使って既存のファイルを編集していると，`gulp.watch`内でクラッシュが発生する．問題を回避するため，`gulp.watch`を[glob-chokidar]で置き換えた．

この問題自体は既知の問題であり，各モジュールのGitHub IssueやStack Overflowでも記述を見かける．同じ問題で悩んでいる人の参考になれば幸いである．

もし，他の回避方法を知っている人がいれば，ぜひ教えてほしい．

本記事の内容の確認は，以下の環境で行っている．他の環境では，ここに書かれてあるように動作しないかもしれない．

* Xubuntu 14.04 on VirtualBox 4.3.14 with Guest Additions
* GNU Emacs 24.3.1

# 事の発端

Emacsを使って既存のファイルを編集していると，`gulp.watch`内でクラッシュする．

実行している`gulp.watch`は以下のようなものだ．

```coffeescript
gulp.watch ['path/to/**/*.coffee'], ['compile']
```

Emacsですでに存在するCoffeeScriptファイルを編集し，そのバッファを未保存とすると，Emacsのインターロック機能により，編集したファイルの先頭に`.#`を付与したシンボリックリンクが作成される．例えば，編集したファイルが`path/to/file.coffee`だとすると，`path/to/.#file.coffee`が作成される．

この状態で`gulp.watch`が反応すると，以下のような例外が発生し，例外を捕捉していなければプログラムは停止する．

````
[20:35:04] Error: ENOENT, no such file or directory 'path/to/.#file.coffee'
````

この問題は，`gulp.watch`に渡すglobパターンに除外パターンを含めても回避できない．

* https://github.com/gruntjs/grunt-contrib-watch/issues/239

# 問題の原因

原因は，[gulp]が間接的に依存している[gaze]というモジュールの既知の問題であり，現時点のgazeの最新版である0.6.4では修正されている（0.6.4以外は未確認だが，0.6系列で修正されたのかもしれない）．

* https://github.com/shama/gaze/issues/45

しかし，gulp 3.8.7は古いgaze 0.5.1を使用しているため問題が発生している．

この問題はgulp 3.8.7だけでなく，古いgazeを使っているモジュールであればあれば発生する可能性がある．再現を確認してはいないが，恐らく以下のモジュールでも発生するだろう．

* [gulp-watch]
* [grunt-contrib-watch]

# 回避方法１

glob-chokidarを使って，`gulp.watch`を置き換える．

元ソースだと複数のglobパターンを指定できないため，すこし改造を加えた．

* https://github.com/masnagam/glob-chokidar/commit/587f3aef6d8df92cc5cedb25cbf2097d071baf87

これをインストールする．

```
$ npm install git+https://github.com/masnagam/glob-chokidar.git --save-dev
```

その後，`gulp.watch`を置き換える．

```coffeescript
watch = require 'glob-chokidar'

gulp.task 'watch', ->
  watch ['path/to/**/*.coffee'], -> gulp.start ['compile']
```

ここで，`gulp.run`ではなく，`gulp.start`を使っている理由は以下の通り．

* `gulp.run`は廃止予定
* `gulp.watch`は`gulp.start`を呼び出してる
    * https://github.com/gulpjs/gulp/blob/v3.8.8/index.js#L36

なお，`gulp.watch`と同様に`on`も使える．

```coffeescript
watch ['path/to/compiled/assets/**']
  .on 'change', (path) ->
    ...
```

発生するイベントの種類については，[Chokidar]を参照．

最後に，glob-chokidarの引数について補足しておく．

`gulp.wath`では，以下のように記述することで除外パターンを指定できる．

```coffeescript
gulp.watch ['path/to/*.include', '!path/to/*.exclude'], ['task']
```

一方，glob-chokidarはChokidarの流儀に従い，除外パターンをオプションで指定する．

```coffeescript
watch ['path/to/*.include'], ignored: ['path/to/*.exclude'], ->
  ...
```

## 怪我の功名？

例えば，以下のようなパターンで`gulp.watch`を行ったとする．

```coffeescript
gulp.watch ['path/to/**'], ['task']
```

この場合，監視しているフォルダ配下に

```
touch path/to/new.coffee
```

新しいファイルを作成しても`task`タスクは実行されない．つまり，`new.coffee`は監視対象とはみなされない．

`new.coffee`を監視対象にしたい場合には，`gulpfile.coffee`を再読み込みする必要がある．

なお，監視中のファイルの削除は検出し，`task`タスクが実行される．

まとめると，以下のように振る舞うようだ．

* `gulp.watch`実行時のパターンでファイルを集めて，これを監視対象とする
* 新しくファイルを追加しても監視対象にならない
  * 監視対象に含めるためには，`gulpfile.coffee`を再読み込みする必要がある

一方，Chokidarは，ディレクトリを指定した場合，その配下が監視対象となる．そのため，`gulp.watch`のように`gulpfile.coffee`の再読み込みが必要ない．

# 回避方法２

一応，上記以外の回避方法もある．`npm-shrinkwrap.json`を使う方法だ．

`npm shrinkwrap`もしくは`npm shrinkwrap --dev`を実行し，`npm-shrinkwrap.json`のgazeのバージョンを書き換える．このとき，gazeが依存しているモジュールのエントリはすべて消してしまったほうが良いだろう．

gulp 3.8.7に対して上記を行ったところ，問題は発生しなくなった．

ただ，この方法は依存関係を無視して強引にバージョンを書き換えるため，別の問題が発生する可能性を否定できない．また，`npm shrinkwrap`のたびに該当箇所を書き換える必要があるため面倒だ（書き換えを自動がすることは可能だろう）．そのため，おすすめはできない．

[glob-chokidar]: https://github.com/masayukinagamachi/glob-chokidar
[gulp]: https://github.com/gulpjs/gulp/
[gaze]: https://github.com/shama/gaze
[gulp-watch]: https://www.npmjs.org/package/gulp-watch
[grunt-contrib-watch]: https://github.com/gruntjs/grunt-contrib-watch
[Chokidar]: https://github.com/paulmillr/chokidar