ぼくがかんがえたさいきょうのGruntfileを晒してみる

1. 開発中とリリースビルドをわけて考える。
* 開発中はとにかく速度重視。テスト重視。
* リリースビルドのほうに、自動化できる便利なタスクを出来るだけ突っ込んでおく。
* 開発中に使用するソースファイルから、リリースビルドに余計なファイルを混ぜない。

こんな考え方でGruntfileを書いてみました。

プロジェクトごとに最適化したGruntfileを作るということも魅力的な挑戦なのですが、私は開発者一人で短納期な案件をいくつもこなさないといけないので、そのために導入したタスクランナーのはずなのにGruntfileの開発やメンテに時間を取られるのは本末転倒になってしまうので、出来る限り汎用的に使えるように気をつけて書きました。

さらに何か特定の開発スタイルやフレームワークに依存しないように、特殊なディレクトリ構成などを必要としない点も気を付けました。
普段は開発は私一人ですが、デザイナーやコーダーや開発者などスキルの異なる複数人でコーディングを同時進行する場合も多々あるからです。
Gruntを導入することによって自分や誰かのスキルや開発スタイルに依存したり変更することがないよう、こころがけています。

CoffeeScript、Sass対応。
Lessは対応しているはずだけど自分はまだ使ってません。
次の段階でTypeScriptにも対応させるつもり。
Grunt0.4.1対応。
プラグインは管理が面倒臭いので、grunt-contribを全部入りで入れています。

```Coffeescript:Gruntfile.coffee
#除外ファイル
exclude = [
  '!**/.DS_Store'
  '!**/Thumbs.db'
  '!**/*.coffee'
  '!**/*.map'
  '!**/*.scss'
  '!**/*.less'
  '!**/*.s.css'
  '!**/*.l.css'
  '!**/<%= dir.cssCompile %>/'
  '!**/coffee/'
  '!**/sass/'
  '!**/less/'
  '!**/_notes/'
  '!**/.idea/'
  '!**/.gitignore'
  '!**/*.mno'
  '!**/Templates/'
  '!**/Library'
  '!**/*.dwt'
  '!**/*.lbi'
  '!**/*.fla'
]

module.exports = (grunt) ->
  pkg = grunt.file.readJSON 'package.json'
  grunt.initConfig
    #ディレクトリ設定
    dir :
      src : 'src'
      dist : 'dist'
      test : '<%= dir.src %>/test'
      doc : 'docs'
      js : 'js'
      css : 'css'
      cssCompile : '<%= dir.src %>/<%= dir.css %>.compile'
    #package.jsonの読み込み
    pkg : pkg
    #クリーン
    clean:
      js:
        src : '<%= dir.src %>/<%= dir.js %>/*'
      css:
        src : '<%= dir.src %>/<%= dir.css %>/*'
      build :
        src : ['<%= dir.dist %>/**', '<%= dir.doc %>/**']
    #CoffeeScriptコンパイル
    coffee:
      options:
        sourceMap : true
      #通常はルートディレクトリのcoffeeディレクトリに置かれたスクリプトをメインとする
      main :
        src : '<%= dir.src %>/coffee/*.coffee'
        dest : '<%= dir.src %>/<%= dir.js %>/<%= pkg.name %>.js'
      #サブディレクトリのCoffeeScriptもコンパイル
      all :
        expand : true
        ext : '.js'
        src : ['<%= dir.src %>/**/*.coffee', '!<%= coffee.main.src %>']
    #Sassコンパイル
    sass:
      #通常はルートディレクトリのsassディレクトリに置かれたCSSをメインとする
      main :
        src : '<%= dir.src %>/sass/*.scss'
        dest : '<%= dir.cssCompile %>/<%= pkg.name %>.s.css'
      #サブディレクトリのSassもコンパイル
      all :
        expand : true
        ext : '.css'
        src : ['<%= dir.src %>/**/*.scss', '!<%= sass.main.src %>', '!<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css', '<%= concat.css.dest %>']
    #Lessコンパイル
    less:
      #通常はルートディレクトリのlessディレクトリに置かれたCSSをメインとする
      main :
        src : '<%= dir.src %>/less/*.less'
        dest : '<%= dir.cssCompile %>/<%= pkg.name %>.l.css'
      #サブディレクトリのlassもコンパイル
      all :
        expand : true
        ext : '.css'
        src : ['<%= dir.src %>/**/*.less', '!<%= less.main.src %>', '!<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css', '<%= concat.css.dest %>']
    #結合
    concat:
      #コンパイルされたCSSを結合
      css :
        src : '<%= dir.cssCompile %>/*.*.css'
        dest : '<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css'
    #画像最適化
    imagemin:
      dev :
        optimizationLevel: 3
        files : [
          expand: true
          src: '<%= dir.src %>/**/*.{png,jpg,jpeg}'
        ]
      dist :
        optimizationLevel: 3
        files : [
          expand: true
          src: '<%= dir.dist %>/**/*.{png,jpg,jpeg}'
        ]
    #監視ファイル
    watch:
      coffee:
        files : '<%= coffee.main.src %>'
        tasks : 'coffee:main'
      coffeeAll:
        files : '<%= coffee.all.src %>'
        tasks : 'coffee:all'
      sass:
        files : '<%= sass.main.src %>'
        tasks : 'sass:main'
      sassAll:
        files : '<%= sass.all.src %>'
        tasks : 'sass:all'
      less:
        files : '<%= less.main.src %>'
        tasks : 'less:main'
      lessAll:
        files : '<%= less.all.src %>'
        tasks : 'less:all'
      css:
        files : '<%= concat.css.src %>'
        tasks : 'concat:css'
    #コピー
    copy:
      build :
        expand : true
        filter: 'isFile'
        cwd : '<%= dir.src %>/'
        src : ['**'].concat exclude
        dest : '<%= dir.dist %>/'
    #htmlのminify
    htmlmin:
      all :
        options :
          removeComments : true
          removeCommentsFromCDATA : true
          removeCDATASectionsFromCDATA : true
          collapseWhitespace : true
          removeRedundantAttributes : true
          removeOptionalTags : true
        expand : true
        src : '<%= dir.dist %>/**/*.html'
    #JSのminify
    uglify:
      options :
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
      main :
        expand : true
        src : '<%= dir.dist %>/<%= dir.js %>/<%= pkg.name %>.js'
      all :
        expand : true
        src : ['<%= dir.dist %>/**/*.js', '!<%= uglify.main.src %>']
    #CSSのminify
    cssmin:
      main :
        expand : true
        src : '<$= dir.dist %>/<%= dir.css %>/<%= pkg.name %>.css'
      all :
        expand : true
        src : ['<%= dir.dist %>/**/*.css', '!<%= cssmin.main.src %>']
    #yuidoc
    yuidoc:
      dist:
        name : '<%= pkg.name %>'
        description : '<%= pkg.description %>'
        version : '<%= pkg.version %>'
      options :
        paths : '<%= dir.src %>/'
        outdir : '<%= dir.doc %>'
        syntaxtype : 'coffee'
        extension : '.coffee'

  for taskName of pkg.devDependencies when taskName.substring(0, 6) is 'grunt-'
    grunt.loadNpmTasks taskName

  #どういった用途で使用するか明示的にするためにも、冗長なエイリアスも指定しておく
  grunt.registerTask 'default', 'watch'
  grunt.registerTask 'w', 'watch'
  grunt.registerTask 'c', ['clean:js', 'clean:css']
  grunt.registerTask 'main', ['coffee:main', 'sass:main', 'less:main']
  grunt.registerTask 'compile', ['coffee:all', 'sass:all', 'less:all']
  grunt.registerTask 'img', 'imagemin:dev'
  grunt.registerTask 'build', ['clean:build', 'copy', 'imagemin:dist', 'htmlmin', 'uglify', 'cssmin', 'yuidoc']
```

プロジェクトディレクトリは次のようになります。

+ src

    開発用ディレクトリ。ここでごりごり開発していきます。
+ dist

    リリースビルド用ディレクトリ。ビルドすると自動的に作られます。
+ doc

    ドキュメントが入るディレクトリ。これもビルドすると自動的に作られます。

開発用ディレクトリとリリースビルドの他にもうひとつ、開発中ビルドのディレクトリ「dev」も欲しかったのですが、LiveReloadなどリロード系監視システムでdevフォルダを監視してしまうと、開発ビルドが行われるたびに何十回、何百回とリロードしまくってしまうので、srcをソースディレクトリ兼開発用ディレクトリとして、LiveReloadなどの監視対象としています。
さらにデザイナー系コーダーの方と同時開発する場合でも、srcさえ共有しておけば相手はGruntを使っていなくても**LiveReloadやCodeKitなどで同じように**SassやCompass、LessやCoffeeScriptなども導入できるという利点もあります。


## 使い方

開発中のときとリリースビルドするときで動作が違います。
順番に説明していきます。

## 開発中

プロジェクトディレクトリで次のように`grunt`してsrcディレクトリを監視開始します。

```
$ grunt
```

srcディレクトリを監視して、ファイルの更新があった場合次のような動作をします。

### `/src/coffee/*.coffee`に変更があった場合
/src/coffee/ 内の *.coffee ファイルを全て結合して
`/src/js/<%= pkg.name %>.js`として保存します。
（<%= pkg.name %>はパッケージ名）

なのでHTML側では

```html
<script src="/js/< パッケージ名 >.js"></script>
```
このようにパッケージ名.jsファイルを読み込むようにしておけばいいことになります。
パッケージ名は、プロジェクト毎にGruntをインストールするときに使う、`package.json`の

```javascript:package.json
{
  "name": "パッケージ名"
};
```
この部分です。



/src/coffee
はリリースビルドの時にはビルドされません。
リリースビルドの動作は後でも詳細に書きます。

### その他 `*.coffee` に変更があった場合

自動でCoffeeを結合してくれるのもいいんですが、場合によっては単体のCoffeeScriptを単体のままJavaScriptとして出したいときもあります。
そこで、 `/src/coffee` 以外のどこに書いた `*.coffee` でも、同一ディレクトリに `< 同名 >.js` としてコンパイルする機能も備えています。

つまり
/src/hoge/fuga.coffee
は
/src/hoge/fuga.js
としてコンパイルされます。
fuga.coffeeのほうは、リリースビルドのときにはビルドされないので安心です。

### SassやLessも基本的には同じ

ただしちょっとだけ複雑なので順番に説明します。

### `/src/sass/*.scss`に変更があった場合

`/src/sass`内の`*.scss`ファイルに変更があった場合、一旦
`/src/css.compile/`
というディレクトリに
`<%= pkg.name %>.s.css`
というファイル名でコンパイルします。
つまりHTML側では /css.compile/< パッケージ名 >.s.css をリンクすればいいのだなと言うのはちょっと待ってください。

### `/src/less/*.less`ファイルに変更があった場合

sassのときと似たように、
`/src/css.compile/`
というディレクトリに
`<%= pkg.name %>.l.css`
というファイル名でコンパイルします。

つまり
/src/css.compile/ディレクトリには
Sassで作った
< パッケージ名 >.s.css
と、Lessで作った
< パッケージ名 >.l.css
の二つのファイルが入る可能性があるということになります。

このディレクトリ内ファイルをHTML側からリンクするのはちょっと待ってください。
**/src/css.compile/はリリースビルドに含まれません。** もう一段階Gruntが自動化処理します。

### `/src/css.compile/` 内ファイルに変更があった場合

つまりSassなりLessなりがコンパイルされた場合ですが、Gruntにさらにそれをwatchさせていて、
`/src/css/<%= pkg.name %>`として結合します。

なのでHTML側では

```html
<link rel="stylesheet" href="/css/< パッケージ名 >.css">
```
としておけばいいということになります。

### その他`*.scss`や`*.less`に変更があった場合

CoffeeScriptのときと事情が一緒ですが、全部をまとめてひとつのファイルに結合したくないとき、つまり個別にSassを書いたら個別のcssファイルとしてコンパイルしたい場合もあります。

`/src/sass/`や`src/less/`以外の場所にある`*.scss`や`*.less`ファイルはそのまま単体のcssファイルとしてコンパイルさせる機能も持っています。

つまり
/src/hoge/fuga.scss
は
/src/hoge/fuga.css
としてコンパイルされます。
もちろんfuga.scssのほうはリリースビルドには含まれないので安心です。

### 画像最適化を手動で行う

リリースビルドの時に画像最適化は自動で行いますので、ふだん開発中は気にしなくてもいいのですが、コマンドラインで

```
$ grunt img
```
と打つことで開発中の/src/ディレクトリ内画像を最適化処理することもできます。
開発中に画像のファイルサイズなどが気になった場合に使えます。

基本的な開発中の機能は以上です。
次はリリースビルド時にどういうことをするかの説明に入ります。

## リリースビルド

リリースビルドをするには、コマンドラインで

```
$ grunt build
```
と入力します。
プロジェクトディレクトリに、
/dist/
というディレクトリを自動的に作り、その中にリリースビルドが書き出されます。

## 除外ファイル

リリースビルドに含ませたくないソースファイルや無駄なファイルは、
Gruntfile.coffeeの一番最初の

```Coffeescript:Gruntfile.coffee
#除外ファイル
exclude = [
  '!**/.DS_Store'
  '!**/Thumbs.db'
  '!**/*.coffee'
  '!**/*.map'
  '!**/*.scss'
  '!**/*.less'
  '!**/*.s.css'
  '!**/*.l.css'
  '!**/<%= dir.cssCompile %>/'
  '!**/coffee/'
  '!**/sass/'
  '!**/less/'
  '!**/_notes/'
  '!**/.idea/'
  '!**/.gitignore'
  '!**/*.mno'
  '!**/Templates/'
  '!**/Library'
  '!**/*.dwt'
  '!**/*.lbi'
  '!**/*.fla'
]
```
この部分で指定できます。
.DS_StoreやThumbs.db、CoffeescriptやSassのソースファイルやソースディレクトリ、その他flaファイルやpsd素材ファイル、Dreamweaverのテンプレートやデザインノートファイルなど、リリースに含ませたくないファイルは最初から除外しています。
他に除外したいファイルがあれば追加してください。

## minify
HTML、Javascript、CSSファイルをminifyします。
さらに画像はプロジェクト内にあるpngとjpegをimageminで最適化を行っています。

## ドキュメント
さらに
/doc/ディレクトリにYUIDocのドキュメント化も行っています。
まだちょっと自分でも使っていませんが、いずれ便利に使える日が来ることを信じて。

## さいごに
既にリリースビルドが終わっていて、更新作業のときに`grunt build`し忘れて古いファイルをアップしてしまったという、Gruntを導入したことによって増えたヒューマンエラーがありましたが、リリースビルドを習慣づけるという人的対応、あと将来的に`grunt watch`のタイミングでリリースビルドを削除してしまうと言うシステム対応も考えていて、そのへんでどうにか切り抜けようと思います。

いちおう3案件ほど実戦投入してきて、だいぶこなれてきたので思い切って晒してみました。
最後に思ったんだけど、これアップグレードしていくんでGitHubに置いたほうがよかった。