Node.js
grunt
grunt.js

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

More than 1 year has passed since last update.

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

インストール

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

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

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

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

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

Gruntfile

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

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をコンパイルするなど)際に、

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

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

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コマンドを実行して、

Terminal
$ grunt

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

つまずいたところ

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

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

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

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

MacVimの場合は

set nobackup
set nowritebackup

で良いみたいです。

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

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

以下は調べた方法など。

watch.js
require('fs').watch(process.argv[2], function(event, filename) {
  console.log(Date.now(), arguments);
});
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では発生しなかったと思います。

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

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

Terminal
$ ulimit -n
256

でした。