リファクタリング grunt

gruntで自動化しているタスクが多くなり，いわゆる `fat Gruntfile.js` になったのでリファクタリングした．

# はじめに

プロジェクト開始初期のGrunfile.coffeeは[【Grunt + Karma + Jasmine】CoffeeScriptの開発環境を構築する](http://qiita.com/tanakaworld/items/cefeaf5c122210d362c9)で書いたくらいの量だったが，約1, 2ヶ月で膨大になった．

```coffeescript:Gruntfile.coffee
grunt.loadNpmTasks 'grunt-contrib-coffee'		
grunt.loadNpmTasks 'grunt-contrib-watch'		
grunt.loadNpmTasks 'grunt-contrib-connect'		
grunt.loadNpmTasks 'grunt-contrib-concat'		
grunt.loadNpmTasks 'grunt-contrib-clean'		
grunt.loadNpmTasks 'grunt-env'		
grunt.loadNpmTasks 'grunt-open'		
grunt.loadNpmTasks 'grunt-s3'		
grunt.loadNpmTasks 'grunt-closure-compiler'		
grunt.loadNpmTasks 'grunt-replace'		
grunt.loadNpmTasks 'grunt-jsonlint'		
grunt.loadNpmTasks 'grunt-jsbeautifier'		
grunt.loadNpmTasks 'grunt-karma'		
grunt.loadNpmTasks 'grunt-http'
```

読み込んでいるgruntライブラリは10数種類あり，それぞれの設定連想配列が列挙されている状態で，当時のGruntfile.coffeeは約300行弱あった．


# リファクタに使用するライブラリ

[load-grunt-config](https://github.com/firstandthird/load-grunt-config)と[jit-grunt](https://github.com/shootaroo/jit-grunt)を使用する．

> npm install load-grunt-config --save-dev
> npm install jit-grunt --save-dev


# リファクタ前のGruntfile.coffee

Grunfile.coffeeに記述しているtask毎のconfig定義とtask alias定義を別ファイルに抽出する．

```coffeescript:Gruntfile.coffee
module.exports = (grunt)->
  grunt.initConfig
    watch:
      coffee:
        files: ['src/**/*.coffee']
        tasks: 'coffee:app'
    coffee:
      app:
        files: [
          expand: true
          cwd: 'src/'
          src: ['**/*.coffee']
          dest: 'src/js'
          ext: '.js'
        ]
      dist:
        files: [
          expand: true
          cwd: 'src/'
          src: ['**/_all.coffee']
          dest: 'dist/'
          ext: '.js'
        ]
    concat:
      dist:
        src: ['src/hello.coffee', 'src/user.coffee', 'src/main.coffee']
        dest: 'src/_all.coffee'

  grunt.loadNpmTasks 'grunt-contrib-coffee'
  grunt.loadNpmTasks 'grunt-contrib-watch'
  grunt.loadNpmTasks 'grunt-contrib-concat'

  grunt.registerTask 'default', ['watch']
  grunt.registerTask 'publish', ['concat', 'coffee:dist']
```

# task毎のconfig定義
task毎のconfig定義ファイルを`/grunt/config/TASK-NAME.coffee`に分離する．

設定のトップレベルのキー名だった `watch`, `coffee`, `concat` はファイル名に含め，`module.exports =` 以下には記述不要．

```coffeescript:grunt/config/watch.coffee
module.exports =
    coffee:
        files: ['src/**/*.coffee']
        tasks: 'coffee:app'
```
```coffeescript:grunt/config/coffee.coffee
module.exports =
    app:
        files: [
          expand: true
          cwd: 'src/'
          src: ['**/*.coffee']
          dest: 'src/js'
          ext: '.js'
        ]
      dist:
        files: [
          expand: true
          cwd: 'src/'
          src: ['**/_all.coffee']
          dest: 'dist/'
          ext: '.js'
        ]
```
```coffeescript:grunt/config/concat.coffee
module.exports =
    dist:
        src: ['src/hello.coffee', 'src/user.coffee', 'src/main.coffee']
        dest: 'src/_all.coffee'
```

# task alias毎の定義

task alias毎の定義ファイルを`grunt/tasks/ALIAS-NAME.coffee`を個別に作成する．
`Gruntfile.coffee`に記述していたaliasの定義をほぼそのまま記述する．

```coffeescript:grunt/tasks/default.coffee
module.exports = (grunt) ->
  grunt.registerTask 'default',
    [
      'replace:cx-const'
      'replace:media-html'
      'coffee:display'
      'coffee:redirect'
      'cx_append:dispatcherExe'
      'connect:server'
      'open:server'
      'watch:coffee'
    ]
```

# リファクタ後のGrunfile.coffee

最後に元のGrunfile.coffeeの設定を書き換え，load-grunt-configとjitGruntの設定を記述する．

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

  require('load-grunt-config') grunt,
    configPath: [
      path.join(process.cwd(), 'grunt/config')
    ]
    jitGrunt:
      customTasksDir: 'grunt/tasks'
    data:
      KEY_NAME: 'Sample Data :)'
```

各configファイルでGrunfile.coffee内の値は，`data.KEY_NAME`に指定すると，`options.KEY_NAME`か`<%= KEY_NAME %>`で参照できる．

```coffeescript:grunt/config/TASK-NAME.coffee
module.exports = (grunt, options) ->
    console.log(options.KEY_NAME) # Sample Data :)
```

# その他やったこと
設定をローカル環境・開発環境・本番環境用に分けるために タスク x 3環境数分のJSONファイルを作成していた．
ファイルが分割されることで可読性が非常に悪くファイルを行ったり来たりでやりづらかった．
タスクごとに設定ファイルを分割したので，環境の分岐も含めて `grunt/config/TAKS-NAME.coffee`に集約するようにしている．

replaceタスクはgruntの複雑さを増強していた．
値を差し替える箇所が複数ファイルにまたがっており，こちらもコードを追うのが困難だったので，置換される値を管理するクラスをつくり一箇所にまとめた．

# まとめ
最終的に`Gruntfile.coffee`は30, 40行程度になり，タスク毎の設定も集約されたことで，gruntのメンテナンスもしやすくなった．
ただ設定量が多いことには代わりないので，別の方法も考えないと先がつらそう．