gruntで更新されたファイルを対象にタスクを動かす方法

JavaScript のビルドツール grunt が熱いということで使い始めました。

Sencha Touchの場合は senchaコマンドでビルドできるのですが、それ以外のプロジェクトでリリースビルドを作るのに、sassのコンパイル、cssのminify、jsの結合とminify、そしてリリースディレクトリへのコピーなどを一括で行うために使っています。

便利なんだけど、毎回全ビルドが走るのがどうにも非効率だなぁ、と感じていました。更新されたファイルだけ対象にしてくれればいいのに。というかantとかrakeとか他のビルドツールには普通あるでしょ!

同じような事を思っている人は当然いるわけで、gruntに提案されています。しかしまだ取り込まれていないようです。→ Provide conditional support for file inclusion

ここでも言われてますが、gruntのタスクに定義するファイルには filter オプションというのがあって、タスク実行時にsrcファイルを任意の関数でフィルタリングすることができます。これを使えば、更新されたファイルだけ対象することができそうです。

と、いうわけで作ってみた。dsuket/Gruntfile.js

3つのフィルタ関数を作りました。

  • newer
    • srcがdestよりも新しければ true を返す。
  • expandNewer
    • destにディレクトリを指定して、srcの各ファイルと比較する。
  • newerAny
    • srcの何れかがdescよりも新しければ true を返す。

grunt のカスタムフィルタ関数は、タスク実行前に srcファイルを引数に呼ばれます。true を返せばそのファイルを対象とし、falseならば対象としません。Custom Filter Function

フィルタ関数の引数には srcしか渡されないため、destファイルがわかりません。そのため、フィルタ関数を返す関数を作成し、その引数に dest ファイルを指定するようにしました。やや冗長ですが仕方ない。

悩ましいのが、newerAny。フィルタ関数は1つのsrcファイル毎に呼び出されるため、予め全てのsrcがわかりません。そこで第2引数にsrcの定義を取るようにしました。配列でも定義できますが、文字列で与えた場合、その変数の値を取ります。(あんまりいけてない)

大体使えるようになりましたが、まだ問題はいくつかあります。

テンプレートが使えない。

テンプレートの展開は grunt.initConfig() の中で行われるので、newerのターゲットでテンプレートを使っても展開されません。

watch で newerAny が更新されない

最初のsrcファイルのフィルタ実行時にしか判定しないため、watchで起動したときは更新されません。毎回チェックするようにすればいいんですが、今のところwatchはあまり使っていないのでペンディング。

newerAny で expand が使えない。

srcファイルの展開は実行時行われるため、定義時にsrcの一覧が取得できません。


と、まぁ幾つか細かい問題がありますが、このフィルタの最大の問題点は、uglify で失敗するということです。

grunt-contrib-uglify タスクが、srcファイルが無い場合エラーで止まる問題があります。filterの結果、更新ファイルがなければ src は空になるのですが、その場合以降のタスクが止まってしまいます。他のタスクはsrcが空でも動くようになっているので、grunt-contrib-uglifyのバグだと思います。一応修正版を github にアップしました。(pull requestも出しておいた)
https://github.com/dsuket/grunt-contrib-uglify


参考: