grunt.jsを使って、frontendのビルドを行う

このTopicはWeb ApplicationのfrontendのProjectを管理するTopicの3つ目です。

1. [node.jsのversionを管理するためにnodebrewを利用する](http://qiita.com/sinmetal/items/154e81823f386279b33c "node.jsのversionを管理するためにnodebrewを利用する")
1. [npmでnode.jsのpackageを管理する](http://qiita.com/sinmetal/items/395edf1d195382cfd8bc "npmでnode.jsのpackageを管理する")
1. [grunt.jsを使って、frontendのビルドを行う](http://qiita.com/sinmetal/items/ab34c02fe26e994876a6 "grunt.jsを使って、frontendのビルドを行う")

前提条件として、僕の環境はMac OSです。

## grunt.jsとは？
[grunt.js](http://gruntjs.com/ "grunt.js")は[node.js](http://nodejs.org/ "node.js")上で動くTask Runnerです。grunt.jsそのものがビルドなどをしてくれるわけではなく、あくまでgrunt.jsは指定されたTaskを実行するだけです。
実際にビルドを行うためには、gruntを取り巻くたくさんのpackageを利用して、自分が望む内容を実行する環境を構築していく必要があります。
このTopicでは、その入り口として、grunt.jsでいくつかのpackageを実行できるようなサンプルを作って行きたいと思います。

## 作業用ディレクトリ作成
まずは作業用のdirを作成します。
このdirを中にサンプルを作っていきます。

```bash:command
$ mkdir sample
```

## grunt-cliインストール
[grunt-cli](https://npmjs.org/package/grunt-cli "grunt-cli")をインストールします。

```bash:command
$ npm install -g grunt-cli
```

grunt-cliはプロジェクト毎に違うバージョンのgruntを利用できるようにするためのフロントエンドとなるパッケージです。grunt 0.3系とgrunt 0.4系では設定ファイルの形式が大きく変更されたため、プロジェクト毎に違うバージョンのgruntが利用できないと困る場合があるためです。
grunt-cli自身にgruntは含まれていません。
あくまで、本命のgruntを間接的に実行するためのtoolです。
そのため、projectごとに変える必要がないので、-gでinstallしています。

## gruntをinstallする

gruntもnpmでinstallします。
これで、project固有のgruntを利用することができます。

```bash:command
$ npm install --save-dev grunt
```

```json:package.json
{
  "name": "sample",
  "version": "0.0.0",
  "description": "",
  "devDependencies": {
    "grunt": "~0.4.1"
  }
}
```

gruntは現時点での最新versionの0.4.1が入りました。

## gruntを実行する

いよいよgruntを実行します。
しかし、gruntはtask runnerですので、taskが無いと実行出来ません。
まずは、実行するtaskを書きます。

```js:Gruntfile.js
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/<%= pkg.name %>.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    }
  });

  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Default task(s).
  grunt.registerTask('default', ['uglify']);

};
```

公式の[Getting Started](http://gruntjs.com/getting-started "Getting Started")から拾ってきたGruntfile.jsを動かしてみたいと思います。
この中に書いてあるTaskは1つ。uglifyです。
uglifyはその名の通り、難読化するためのTaskです。
uglifyは以下のlibraryを利用しているようです。

```js:Gruntfile.js
grunt.loadNpmTasks('grunt-contrib-uglify');
```

まずは、これをinstallしましょう。

```bash:command
$ npm install --save-dev grunt-contrib-uglify
```

node modules/grunt-contrib-uglifyが作られていることを確認してください。
また、package.jsonにも変更が加わっているはずです。

```json:package.json
{
  "name": "sample",
  "version": "0.0.0",
  "description": "",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-uglify": "~0.2.2"
  }
}
```

これでuglifyが動く環境にはなりました。
それでは、もう一度Gruntfile.jsを見てみましょう。

```js:Gruntfile.js
pkg: grunt.file.readJSON('package.json'),
uglify: {
  options: {
   banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
  },
  build: {
    src: 'src/<%= pkg.name %>.js',
    dest: 'build/<%= pkg.name %>.min.js'
  }
}
```

uglify taskの中にoptions, buildという項目があります。
optionsの中にはbanner、buildの中にはsrc, destがそれぞれあります。
optionsは名前の通り、optionなのでしょう。
buildのsrc, destを見ると、pathらしきものが書いてあります。
このpathに従って、uglifyするfileを適当に作りましょう。

```js:src/sample.js
function hoge() {
    var hoge = " hoge  fuga";



    console.log(hoge);
}
```

uglifyされたことを確認するために、余分な改行を入れています。
では、gruntを実行しましょう。
Gruntfile.jsがあるpathでgruntを実行します。

```bash:command
$ grunt
Running "uglify:build" (uglify) task
File "build/sample.min.js" created.

Done, without errors.
```

uglifyされたsample.min.jsが出来ました。

```js:build/sample.min.js
/*! sample 2013-06-23 */
function hoge(){var a=" hoge  fuga";console.log(a)}
```

options bannerは、uglifyされたファイルの先頭にコメントを入れるためのものだったようですね。
無駄な改行なども無くなっています。
これでgruntの実行が出来ました！

今回、実行したcommandはgruntだけでしたが、これはGruntfile.jsにdefaultが指定されているからです。

```js:Gruntfile.js
grunt.registerTask('default', ['uglify']);
```

taskを指定して実行する場合は、gruntの後にtask名を入れます。

```bash:command
$ grunt uglify
```

これでugilifyのTaskが実行されます。

## taskを追加する

次はgruntのtaskを追加してみましょう。
uglifyする前にjsファイルを連結させてみたいと思います。
まずはjsを連結してくれるgruntのlibraryを探します。
[npm](https://npmjs.org/ "npm")でgrunt concatで検索してみます。
いっぱい出てきますが、今回は[grunt-contrib-concat](https://npmjs.org/package/grunt-contrib-concat, "grunt-contrib-concat")を使います。
どのlibraryを使うかの判断材料ですが、documentが全部書いてあったりとか、githubのリポジトリを見てみるとかでしょうか。
後、contribと付いているものは、なんか公式っぽいものらしいです。

まず、npmでgrunt-contrib-concatをinstallします。

```bash:command
$ npm install --save-dev grunt-contrib-concat
```

そして、Gruntfile.jsにgrunt-contrib-concat用の設定を追加します。

```js:Gruntfile.js
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'dist/built.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    },
    concat: {
      options: {
        separator: ';'
      },
      dist: {
        src: ['src/<%= pkg.name %>.js', 'src/fuga.js'],
        dest: 'dist/built.js'
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');

  // Default task(s).
  grunt.registerTask('default', ['concat','uglify']);

};
```

書いた内容は、grunt-contrib-concatのdocument通りです。
concat用のtaskを追加したのと、loadNpmTasksにgrunt-contrib-concatを追加したのと、default taskでuglifyをする前にconcatするようにしました。

まず、concatの設定ですが、2つのファイルを連結させて、dist/built.jsに出力するようになっています。

```js:Gruntfile.js
concat: {
  options: {
    separator: ';'
  },
  dist: {
    src: ['src/<%= pkg.name %>.js', 'src/fuga.js'],
    dest: 'dist/built.js'
  }
}
```

`<%= pkg.name %>.js`は前からあるファイルで、`src/fuga.js`が追加したファイルです。
fuga.jsは連結させるために適当に書いたファイルなどで、特に意味は無いです。

```js:fuga.js
function fuga() {
    var fuga = "fuga.com";


    console.log(fuga);
}
```

そして、concatが作成した成果物がbuilt.jsです。
見事に無意味な2つのjsファイルが連結されていますね。
options separator:';'を指定しているので、ファイルの区切りには';'が入っています。

```js:dist/built.js
function hoge() {
    var hoge = " hoge  fuga";



    console.log(hoge);
};function fuga() {
	var fuga = "fuga.com";


	console.log(fuga);
}
```

dist/built.jsをuglifyが難読化して、最終的なファイルを作ります。
uglify buildもsrcを修正して、dist/built.jsを指定しています。

```js:Gruntfile.js
uglify: {
  options: {
    banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
  },
  build: {
    src: 'dist/built.js',
    dest: 'build/<%= pkg.name %>.min.js'
  }
},
```

これで最終結果のファイルが期待した通り、2つのファイルを連結して難読化したものになりました。

```js:sample.min.js
/*! sample 2013-06-24 */
function hoge(){var a=" hoge  fuga";console.log(a)}function fuga(){var a="fuga.com";console.log(a)}
```

こんな感じで、どんどんtaskを追加して、やりたいことを実現します。

## おまけ
gitを利用している場合、自動生成されるファイルは管理下におく必要が無いので、.gitignoreに入れておきます。

```.gitignore
node_modules/
build/
dist/
```