音の鳴るブログ

鳴らないこともある

Grunt で変更のあったファイルだけを処理する

僕のやり方が悪いのかもしれないけど、Gruntでファイルを watch して変更があったら任意のタスクを走らせるとき、関係ないファイルの分まで処理される。たとえば grunt-contrib-jshint とか、変更のあったファイル以外もチェックするのは意味がないし無駄な時間がかかるのでイライラする。そういうくだらないことで心の安寧を乱されたくないときの方法です。

概要

  • 実行したいタスクを引数の取れるタスクでラッピングする
  • 引数に応じてタスクの設定を変更して実行する

サンプル

説明

サンプルでは CoffeeScriptコンパイルするタスクがあって、Gruntfile.coffee では以下のようなカスタムタスクを設定している。→ 参照

grunt.registerTask(
  'coffee'
  'Compile CoffeeScript files into JavaScript'
  -> grunt.task.run "-coffee:#{toTaskArgs(arguments)}"
)

ここの toTaskArgs というのは与えられた引数を : で結合する関数。Grunt ではタスク名に続けて :引数 をつけることができるので、それを受け取って -coffee というプライベートタスクに渡している。(プライベートタスクというのは適当につけた名称)

で、この -coffee というのが 実行したいタスクを引数の取れるタスクでラッピングした タスクで、この中で 引数に応じて grunt-contrib-coffee の設定を変更して実行する。 → 参照

grunt.registerTask '-coffee', (filter)->
  src = grunt.file.expand 'src/**/*.coffee'  # 対象の全ファイル
  src = filterFiles src, filter              # filter にマッチするものだけを残す
  return if src.length is 0                  # マッチするものがなければ終了
 
  # 初回のときだけ loadNpmTasks 'grunt-contrib-coffee' する
  loadNpmTasksIfNeeded 'grunt-contrib-coffee'

  # coffee タスクの設定をして
  grunt.config.data.coffee =
    files:
      expand: yes
      cwd : 'src/'
      src : _.map(src, (path)-> _.rest(path.split('/')).join '/')
      dest: 'lib/'
      ext : '.js'
 
  # タスクを呼び出す
  grunt.task.run 'coffee';

ここの filterFiles というのは配列要素からフィルタ文字列に一致するものだけを抽出する関数(フィルタがなければ全部通す)で、 loadNpmTasksIfNeeded というのは最初の一回だけ loadNpmTasks する関数です。

こういう設定で grunt coffee:file1 したときは file1.coffee とか file100.coffee とか file1/*.coffee だけを対象に、引数を省略して grunt coffee したときは、フィルタが無効になり file1.coffeefile2.coffee も全部の CoffeeScript を対象にタスクを走らせることができる。ターミナルがちょっと残念な感じになるけど

Running "coffee:file" (coffee) task    # 最初に設定したカスタムタスク
Running "-coffee:file" (-coffee) task  # カスタムタスクから呼ばれるプライベートタスク
Running "coffee:files" (coffee) task   # grunt-contrib-coffee のタスク

で、変更があったファイルだけを処理したい場合は watch タスクから引数つきでプライベートタスクを呼ぶようにすれば良い。僕は este-watcher なのでこんな感じ。 → 参照

grunt.registerTask '-watch', ->
  loadNpmTasksIfNeeded 'grunt-este-watch'
 
  grunt.config.data.esteWatch =
    options:
      dirs: 'src/**/'
    coffee: (file)-> "-coffee:#{file}"
 
  grunt.task.run 'esteWatch'

一応注意すべき点は、watch タスクから呼ぶのはプライベートタスク(-で始めている方)でないといけない。なぜなら最初に設定した coffee タスクは -coffee タスク内で上書きされてしまうので 2回目以降は引数を取れなくなってしまう。

あとは grunt --help したときにプライベートタスクが表示されると鬱陶しいのでガードしたりとかすると親切。→ 参照

実際に使っている様はこんな感じです。このプロジェクトでは CoffeeScript は使っていないけど、lint とかタイポチェックとかテストやカバレッジとか数種類のタスクをターゲットを絞って実行している。ごちゃごちゃしているので閲覧注意です。