おっさんエンジニアの備忘録

ここは主におっさんエンジニアが技術的な事を調べたり、試してみたりしたことを備忘録的に書いていくものです。忘れっぽいからね。誰かの参考になったら嬉しい。

angular-generatorで作られたGruntfile.jsの中身を調べる。

前回angular-generatorで作られたファイルについてざっくり調べた結果を書き連ねましたが、今回はその中でGruntfile.jsの中身を調べつつ、Gruntの使い方を見てみたいと思います。

Gruntってなんぞ?

Gruntは「The JavaScript Task Runner(公証)」です。
「minifyとかcompileとかunit testとか簡単にしたいでしょ?Gruntは日々成長していて、誰かが君に必要なプラグインをもう作ってくれてるはずだから使ったら良いよ」みたいなことが書いてありました。

http://gruntjs.com/

Grunt本体および各種プラグインnpmで公開されていますので、インストールも簡単です。

$ npm install -g grunt-cli
※「cli」は「Command Line Interface」の略称である模様

$ npm install grunt-some-plugin --save-dev
※プラグインは公式(https://www.npmjs.org/)で検索可能

MavenやAntみたいなものかなぁと思ってます。

Gruntfileの基本的な書き方

Gruntの動作を定義する設定ファイルはGruntfile.jsGruntfile.coffeeになります。(もちろん実行時に別ファイル指定は可能)
おっさんはCoffeeScriptが良くわかっていないので.jsで書いていきたいと思います。

Gruntfileの基本的な書き方については公式にも載っています。基本的には以下のような構成になるみたいですね。

module.exports = function(grunt) {
  grunt.initConfig({
    // 1.Init config
  });
  // 2.Load the plugin
  grunt.loadNpmTasks('grunt-xxx');
  // 3.Task definition
  grunt.registerTask('default', ['xxx']);
};

もうこれがテンプレみたいですね。
ちなみに設定ファイルがJavaScriptのコードになっています。pom.xmlやらbuild.xmlは単純にテキストファイルなんですが、公式によると「JSONじゃなくてJavaScriptのコードだから、必要ならプログラムで設定を作っても良いよ!」ということでした。Gradleなんかもそうなんですよね、これが最近のデファクトなのかぁ・・・

公式の例をいじって動かしてみたものは以下の通りです。

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: {
      name: 'sample'
    },
    uglify: {
      options: {
        banner: '/*! sample banner <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        files: {
          'build/<%= pkg.name %>.min.js' : ['src/*.js']
        }
      },
      build2: {
        options: {
          banner: '/* custom banner */\n'
        },
        files: {
          'build/<%= pkg.name %>2.min.js' : ['src/<%= pkg.name %>2.js']
        }
      },
      build3: {
        files: {
          'build/<%= pkg.name %>3.min.js' : ['src/<%= pkg.name %>.js']
        }
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.registerTask('default', ['uglify']);
};

それぞれ見ていきます。

Init config

各種設定を書く場所ですね。initConfigに渡した連想配列のルート階層(task-levelというらしい)がタスクの定義と紐づいて設定として渡されます。プラグインに対応する設定を書く必要があります。今回は定数定義として使ってるpkgと、grunt-contrib-uglifyというプラグインのための設定であるuglifyを書いています。※pkgに対応するタスクは登録されていないのでgrunt pkgとかやってもエラーです。

optionsは文字通り設定を記述するためのもので、タスクやターゲットではないようです。(たぶん予約語なのかな?)
buildbuild2build3がそれぞれターゲットになり、それぞれの設定を表しています。
ターゲット毎にoptionsを書くこともでき(Target-levelというらしい)、task-levelのオプションはTarget-levelのオプションで上書きされるようです。

grunt-contrib-uglifyではuglifyというタスクが設定されていますので、通常の実行は以下のような指定になります。

$ grunt uglify

この場合はoptionsを除くすべてのターゲットが実行されます。個々のターゲットを指定して実行するには、以下のような指定を行います。

$ grunt uglify:build

この場合はbuildのターゲットのみ実行されます。このようにGruntでは:を使ってターゲット指定するみたいですね。

途中に出てくる<%= pkg.name %>という何ともJSPな記述(おっさんはUnderscoreよりJSPにピンときてしまいます・・・)は、値の参照を行うときに使う記法のようです。この例ですと、uglifyの上に書かれたpkgのname属性を参照している、という意味ですね。ここで展開される変数は、定義されたプロパティとgruntオブジェクトになるようです。bannerにある<%= grunt.template.today("yyyy-mm-dd") %>はgruntオブジェクトのメソッドを使っていますね。 この辺は公式ページのAPIドキュメントにいろいろ書いてありますので、そちらを参照。http://gruntjs.com/api/grunt
※加えて<% %>で囲まれた箇所では、任意のJavaScriptコードも実行できるみたいです。

また、filesの部分に入力元ファイルと出力先ファイルを指定しています。このファイルの指定にはいくつか方法があり、そこに対してのオプションも指定することができるみたいです。詳しくはコチラ。http://gruntjs.com/configuring-tasks#files

あと、ファイルの指定にsrc/*.jsのような書き方ができます。
これはglobbingと呼ばれる、Unixライクなファイル指定の方法のようです。実際の中身はnode-globminimatchというライブラリが使われているみたいです。詳しい使い方はやはりコチラ。http://gruntjs.com/configuring-tasks#globbing-patterns
※加えてファイルリストを動的に作る仕組みもあるみたいです。詳しくはコチラ。http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically

と、このようにgrunt#initConfigにドデカい連想配列で変数から、設定からを記述するブロックのようですね。

※その他grunt-contrib-uglifyの詳しい設定はコチラ。https://github.com/gruntjs/grunt-contrib-uglify

Load the plugin

実際に利用するプラグインを読み込みます。これによって、それぞれが持つタスクが使えるようになります。今回はnpmでインストールしたgrunt-contrib-uglifyを使うためにgrunt.loadNpmTasks('grunt-contrib-uglify')と書いてあるわけですね。
ですが、この例ですと、プラグインを追加するごとにGruntfileを修正するので、メンドーなのでload-grunt-tasksというプラグインを使うのが良いようです。

https://github.com/sindresorhus/load-grunt-tasks

これさえ入れておけば、require('load-grunt-tasks')(grunt);と書くだけでタスクが自動的に登録されます。便利ですね。

Task definition

タスクは、プラグインとかを読み込むと自動的に登録されるみたいですが、自分で作ったタスクを登録する場合はこちらに記述するみたいです。例ではgrunt.registerTask('default', ['uglify']);と書いてありますが、これはdefaultというタスク(つまり引数なしで実行した場合)はuglifyのタスクを実行するよ、といった意味になります。なので、この場合次の実行結果はいずれも同じものになります。

$ grunt
$ grunt default
$ grunt uglify

また、タスクにロジックを挟むことも可能のようです。その場合はgrunt.task.registerTask(taskName, description, taskFunction)という形で呼び出し、taskFunctionに処理を書きます。taskFunctionは可変長引数で、与えられたターゲットを引数に受け取ります。taskFunctionからfalseを返すとタスクが失敗します。詳しくは以下の公式を参照してください。

http://gruntjs.com/api/grunt.task#grunt.task.registertask

以上で、簡単にですがGruntfileの書き方がわかった気がします。これで処理は追える・・・ハズ!

angular-generatorで作られたGruntfile.jsの中身を見てみる

では、実際にangular-generatorで作られたGruntfile.jsを見てみて、どんな処理が入っているかを追ってみたいと思います。

ファイル冒頭

まず、コメントが書いてあります。

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

「パフォーマンスを考えて、1階層にしかマッチしないようにしているぜ!」ってことですね。{,*/}は先ほど出てきたglobbingの書き方です。モジュール構造が2階層以上になる場合は注意が必要そうです。その場合は上述の通り**/*.jsといった形に書き換える必要があります。

// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);

先ほど紹介したload-grunt-tasksを使ってますね。 加えてtime-gruntというプラグインも使っています。これはタスクの実行時間を表示してくれるものです。両方ともgrunt.loadNpmTasksじゃないのは、それぞれがタスク登録される手合いのものではないからですかね?

定義されたタスク

angular-generatorが定義したタスクについて、それぞれ見ていきます。
また、それぞれのプラグインがどんな設定で、どんな動きをするのかも調べてみたいと思います。

default

まぁまずはここからでしょうか。

grunt.registerTask('default', [
  'newer:jshint',
  'test',
  'build'
]);

少ないですね。testbuildは別に定義されたタスクなので、ここでは掘り下げません。

newer

grunt-newerのタスクです。これは別タスクに定義されたファイルに対して新しいもののみを対象としてくれるものです。つまり、変更のあった個所だけ処理してくれるってことですね。

https://github.com/tschaub/grunt-newer

newer:jshintは「jshintタスクに対して新しいものだけで処理する」という意味です。実際newerは後続タスクに設定されているfilesをハックして、更新のあった個所だけを返すように設定を書き換えているみたいですね。ちなみにタイムスタンプについてはnode_modules/grunt-newer/.cache配下で管理されているみたいです。

jshint

grunt-contrib-jshintのタスクです。これは以前ちょっと出てきたのですが、JavaScriptの構文チェックをしてくれるJSHintを呼び出してくれるものですね。

http://www.jshint.com/

定義されているターゲットを見てみます。

jshint: {
  options: {
    jshintrc: '.jshintrc',
    reporter: require('jshint-stylish')
  },
  all: [
    'Gruntfile.js',
    '<%= yeoman.app %>/scripts/{,*/}*.js'
  ],
  test: {
    options: {
      jshintrc: 'test/.jshintrc'
    },
    src: ['test/spec/{,*/}*.js']
  }
},

シンプルですね。各所にGruntfiles指定があります。「all」とある割にはテストが含まれていないのが、個人的に違和感ありますが・・・とりあえず実行する場合は以下のような感じです。

$ grunt jshint

※ためしに書いてソースを食べさせたのですが、Comma warnings can be turned off with 'laxcomma'.なんて怒られてしまいました。これ、配列とかで先頭に,書くの禁止って意味らしいです。

{
    a: 111
  , b: 222
}

これではダメです。

{
  a: 111,
  b: 222
}

なのでこう書きます。なんと、そういうものなんですね・・・むむむ~・・・
(まぁ.jshintrcで設定変えれば良いっぽいんですが・・・)

serve/server

次は開発時にサーバを起動するためのものです。まずserverですが、以下のようになっています。

grunt.registerTask('server', function (target) {
  grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
  grunt.task.run(['serve:' + target]);
});

serverタスクは非推奨だからserveを使えよ」と言っていますが、ちゃんとserveを呼び出してくれてるあたり、優しさにあふれていますね。

で、本命のserveは以下の通りです。

grunt.registerTask('serve', function (target) {
  if (target === 'dist') {
    return grunt.task.run(['build', 'connect:dist:keepalive']);
  }

  grunt.task.run([
    'clean:server',
    'bowerInstall',
    'concurrent:server',
    'autoprefixer',
    'connect:livereload',
    'watch'
  ]);
});

処理ロジックが定義されていますね。
ターゲットとしてdistが渡された場合は本番用の挙動を示していますね。grunt.task.runでタスクを実行しています。そうでない場合はその下の長々したタスクが実行されています。buildは別途定義されているのでここではスキップ。

connect

grunt-contrib-connectのタスクです。これは、アプリを動かすためのサーバを起動してくれるものですね。

https://github.com/gruntjs/grunt-contrib-connect

設定はこんな感じになっています。

// The actual grunt server settings
connect: {
  options: {
    port: 9000,
    // Change this to '0.0.0.0' to access the server from outside.
    hostname: 'localhost',
    livereload: 35729
  },
  livereload: {
    options: {
      open: true,
      base: [
        '.tmp',
        '<%= yeoman.app %>'
      ]
    }
  },
  test: {
    options: {
      port: 9001,
      base: [
        '.tmp',
        'test',
        '<%= yeoman.app %>'
      ]
    }
  },
  dist: {
    options: {
      base: '<%= yeoman.dist %>'
    }
  }
}

まぁportとかhostnameは良いとして・・・baseはdocumentRootみたいなものです。配列で指定することで、複数のルートを持つことができるのが個人的に面白いですね。ちなみに.tmpというディレクトリは別のタスクで使われているディレクトリで、この後いろいろと出てきます。
livereloadは変更があった場合に、自動で再読み込みをしてくれる設定です。これはその待ち受けポートを指定しているようですね。
またlivereloadは単体では動かないらしく「grunt-contrib-watchとかとタンデムしてね」と公式に書かれていました。

ちなみに、livereloadの設定をした場合、以下のスクリプトが自動的に埋め込まれていました。

<!-- livereload script -->
<script type="text/javascript">document.write('<script src="http://'
+ (location.host || 'localhost').split(':')[0]
+ ':35729/livereload.js?snipver=1" type="text/javascript"><\/script>')
</script>

これはgrunt-contrib-connectで使っているconnect-livereloadがリクエストをフックして、挿入しているようです。
ここで気になるのが、connect:distとかで動かすときはlivereloadが悪影響とかしないかなぁというところです。
Gruntfileの設定では、task-levelのオプションでlivereloadが有効になっていますので、このままだと挿入されてしまうかなぁと思ったのですが、実際は挿入されていません・・・なんでじゃ?

$ grunt connect:dist:keepalive

connect-livereloadのソースとにらめっこしたり、実際のHTMLを見ながら試行錯誤した結果わかったことは以下の通りです。

  • grunt-contrib-connectではlivereloadの設定が有効になっている。
    • Gruntfileの記述の理解は正しかった。
  • connect-livereloadではスクリプトを挿入する条件として</body>を見つけて、その直前に入れるようにしている。
  • 何故かminifyされたHTMLは</body>が存在していない。
    • 何ででしょうか?なくても良いものだから省略したんですかね?それともバグ?
  • したがってスクリプトが挿入されない。
  • grunt-contrib-connectは内部でキャッシュを積んでいるのか、リクエストのフック処理はリクエスト毎に実行されるわけではない。
  • あと同一ターゲットで起動されたgrunt-contrib-connectは前回のキャッシュを引き継いでいるっぽい。
    • connect-livereloadのソースにデバッグ文を仕込んで実行させてもそのロジックを通らずに苦労しました・・・

distターゲットで動かす状況を考えると、livereloadfalseにしておくのが正しい気もしますが、そもそもそんな状況って何かあるのだろうか・・・ん~、まぁサービス影響はなさそうだし、心の片隅に置いておきましょう。

だいぶ脱線しました・・・

clean

grunt-contrib-cleanのタスクで、ファイルを消してくれるものですね。そこまで複雑じゃないんだから、ver1.0で良い気がするけど・・・(内部で使ってるrimrafはver2.2.8なのに・・・

// Empties folders to start fresh
clean: {
  dist: {
    files: [{
      dot: true,
      src: [
        '.tmp',
        '<%= yeoman.dist %>/*',
        '!<%= yeoman.dist %>/.git*'
      ]
    }]
  },
  server: '.tmp'
}

すべてGruntfilsesを使ってますが、特に難しいところはなさそうですね。詳しくはGruntの公式に情報があります。

bowerInstall

grunt-wiredepのタスクですね。grunt-bower-installという名前でインストールされましたが、内部的にはこちらを向いているようです。(リダイレクトされているようです)

https://github.com/stephenplusplus/grunt-wiredep

ジャンプ先のソースを見ると、タスク登録がwiredepになっている(bowerInstallが見つからない・・・)ので、そのうちgeneratorの方も変わるかもしれませんね。(今手元のpackage.jsonではgrunt-bower-install~1.0.0ですが、grunt-wiredepの方は1.7.0になってますね・・・)

このタスクは何をしているかというと、Bowerを使ってインストールしたJSやCSSを自動的にHTMLソースに反映してくれる便利なものです。

// Automatically inject Bower components into the app
bowerInstall: {
  app: {
    src: ['<%= yeoman.app %>/index.html'],
    ignorePath: '<%= yeoman.app %>/'
  }
}

上記設定ではindex.htmlに対して自動注入を行ってくれます。ファイルの中身を見てみます。(抜粋)

<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<!-- endbower -->

<!-- 中略 -->

<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<!-- endbower -->

HTMLコメントに書かれている<!-- bower:css-->から<!-- endbower -->までが、CSSを展開してくれるブロックです。同様に<!-- bower:js -->から<!-- endbower -->までが、JSを展開してくれるブロックです。既にいくつか展開されていますね。

ためしにおっさんの大好きなFont Awesomeを入れてみましょう。

$ bower install font-awesome --save
$ grunt bowerInstall

実行結果はこちら。

<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />
<!-- endbower -->

追加されていますね。Great!!

concurrent

grunt-concurrentのタスクです。重たい処理を並列実行してくれるみたいですね。

https://github.com/sindresorhus/grunt-concurrent

設定を見てみます。

// Run some tasks in parallel to speed up the build process
concurrent: {
  server: [
    'copy:styles'
  ],
  test: [
    'copy:styles'
  ],
  dist: [
    'copy:styles',
    'imagemin',
    'svgmin'
  ]
}

copyやらminifyやらが設定されています。ちなみに今回はSassを使っていないので、CSSファイルのコピーになっていますが、Sassを使うよう設定すると、copyではなくcompassのタスクに置き換わります。
ついでにcopyimageminsvgminの設定も見てみます。

// Copies remaining files to places other tasks can use
copy: {
  dist: {
    files: [{
      expand: true,
      dot: true,
      cwd: '<%= yeoman.app %>',
      dest: '<%= yeoman.dist %>',
      src: [
        '*.{ico,png,txt}',
        '.htaccess',
        '*.html',
        'views/{,*/}*.html',
        'images/{,*/}*.{webp}',
        'fonts/*'
      ]
    }, {
      expand: true,
      cwd: '.tmp/images',
      dest: '<%= yeoman.dist %>/images',
      src: ['generated/*']
    }]
  },
  styles: {
    expand: true,
    cwd: '<%= yeoman.app %>/styles',
    dest: '.tmp/styles/',
    src: '{,*/}*.css'
  }
}

こちらはcopyです。filesのオンパレードですね。
ちなみに、.tmpディレクトリを経由してたりするのは別のタスク(compassなど)との兼ね合いであったりしますので、今回みたいにCSSオンリーの場合は不要なものも含まれているようですね。まぁ害がありそうではないので無視しましょう。

imagemin: {
  dist: {
    files: [{
      expand: true,
      cwd: '<%= yeoman.app %>/images',
      src: '{,*/}*.{png,jpg,jpeg,gif}',
      dest: '<%= yeoman.dist %>/images'
    }]
  }
}

こちらはimgeminの設定です。
これはgrunt-contrib-imageminのタスクになります。

https://github.com/gruntjs/grunt-contrib-imagemin

内部的にimageminを使っており、READMEにはsvgファイルもいけるっぽいことが書かれているのですが、何故かsvgファイルだけは後述のsvgminを別に使っています。謎ですね・・・
細かい仕組みはよくわかっていないのですが、何やら画像を圧縮してくれるものみたいです。機会があったら調べてみたいと思います。

あと上述のcopyタスクに*.pngと書いてありますが、あれは直下のpngファイルのみ単純コピーしています。favicon用なのかな?

svgmin: {
  dist: {
    files: [{
      expand: true,
      cwd: '<%= yeoman.app %>/images',
      src: '{,*/}*.svg',
      dest: '<%= yeoman.dist %>/images'
    }]
  }
}

最後はsvgminの設定です。
こちらはgrunt-svgminのタスクで、こちらはsvgファイルを圧縮してくれるもののようです。

https://github.com/sindresorhus/grunt-svgmin

内部ではsvgoが使われているようです。

autoprefixer

grunt-autoprefixerのタスクです。これはCSSのベンダープレフィックスを自動的につけてくれる便利なものです。

https://github.com/nDmitry/grunt-autoprefixer

設定を見てみます。

// Add vendor prefixed styles
autoprefixer: {
  options: {
    browsers: ['last 1 version']
  },
  dist: {
    files: [{
      expand: true,
      cwd: '.tmp/styles/',
      src: '{,*/}*.css',
      dest: '.tmp/styles/'
    }]
  }
}

.tmpディレクトリのCSSファイルに対して処理を行っていますね。このディレクトリは開発時にCSSファイルを配置する場所(Sassのコンパイル結果であったり単純コピーであったり)なので、そういうことなのでしょう。また、buildタスクでもここからCSSファイルを持って行っていますので、必ず通るよう設定されていることになります。とりあえず試しに動かしてみますね。

.foo {
  box-sizing: border-box;
  columns: 3;
}

って感じのCSSファイルが

$ grunt copy:styles autoprefixer

ってコマンドをたたくと

.foo {
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-columns: 3;
  -moz-columns: 3;
  columns: 3;
}

こんな感じ。素敵ですね。

ベンダープレフィックスの対応状況などは、以下のサイトが参考になるかなぁと思います。
https://w3g.jp/blog/tools/vendor_prefix_cleaning

watch

grunt-contrib-watchのタスクです。これはファイルの変更を監視し、定義されたタスクを実行してくれるようです。先ほど出てきたlivereloadとタンデムすることが主な使い方でしょうか。

// Watches files for changes and runs tasks based on the changed files
watch: {
  bower: {
    files: ['bower.json'],
    tasks: ['bowerInstall']
  },
  js: {
    files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
    tasks: ['newer:jshint:all'],
    options: {
      livereload: true
    }
  },
  jsTest: {
    files: ['test/spec/{,*/}*.js'],
    tasks: ['newer:jshint:test', 'karma']
  },
  styles: {
    files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
    tasks: ['newer:copy:styles', 'autoprefixer']
  },
  gruntfile: {
    files: ['Gruntfile.js']
  },
  livereload: {
    options: {
      livereload: '<%= connect.options.livereload %>'
    },
    files: [
      '<%= yeoman.app %>/{,*/}*.html',
      '.tmp/styles/{,*/}*.css',
      '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
    ]
  }
}

基本的にはJSHintしたりlivereloadしたりしていますね。Sassを使っていると、自動的にコンパイルのタスクが走るようになると思われます。bower.jsonの監視は正直やりすぎな気もしますが・・・

test

続いて、テストタスクです。

grunt.registerTask('test', [
  'clean:server',
  'concurrent:test',
  'autoprefixer',
  'connect:test',
  'karma'
]);

ファイルをきれいにして、コピー(Sassのコンパイル)し直して、ベンダープレフィックスを付けて、サーバを立ち上げて、テストをする。という感じですね。ちゃんと流れを追えばシンプルですね。

karma

grunt-karmaのタスクです。内部的にはKarmaというテスティングフレームワークの呼び出しを行っています。

https://github.com/karma-runner/grunt-karma
http://karma-runner.github.io/

AngularJSでは公式にKarmaが推奨されていますので、ゆくゆくは深堀したいところですが、今回は簡単にさわりだけ。

// Test settings
karma: {
  unit: {
    configFile: 'karma.conf.js',
    singleRun: true
  }
}

unitターゲットでkarma.conf.jsを設定ファイルにしてタスクを実行しています。
他にも色々と設定は可能なのですが、Karma側の設定ファイルにいろいろと記述されているようです。

build

最後のタスクです。これは本番用にモジュールをビルドするタスクです。できたモジュール群はdistディレクトリに出力されています。

grunt.registerTask('build', [
  'clean:dist',
  'bowerInstall',
  'useminPrepare',
  'concurrent:dist',
  'autoprefixer',
  'concat',
  'ngmin',
  'copy:dist',
  'cdnify',
  'cssmin',
  'uglify',
  'rev',
  'usemin',
  'htmlmin'
]);

長いですが、半分くらいは見たことのあるものです。まだ調べられていないものを調査してみます。

useminPrepare

grunt-useminのタスクです。後述のuseminとペアになっているようです。

https://github.com/yeoman/grunt-usemin

useminPrepareは一部後続タスクの設定を変更します。後続タスクとしては、JS/CSSファイルの結合や難読化などです。何でそんなことしてるかというと、こいつは指定されたHTMLファイルで読み込まれているJS/CSSファイルを解釈して、自動的にそれらを集めてくれます。つまり、使われているファイルだけを集めて、後続タスクで結合や難読化を行ってくれるわけです。すごいですね。

とりあえず生成されたindex.htmlを見てみましょう。(抜粋)

<!-- build:css styles/vendor.css -->
<!-- bower:css -->
    <!-- bowerInstallで追加される外部モジュールの読み込み -->
<!-- endbower -->
<!-- endbuild -->
<!-- build:css({.tmp,app}) styles/main.css -->
    <!-- 自分で定義したCSSファイルの読み込み -->
<!-- endbuild -->

<!-- 中略 -->

<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
    <!-- bowerInstallで追加される外部モジュールの読み込み -->
<!-- endbower -->
<!-- endbuild -->

<!-- build:js({.tmp,app}) scripts/scripts.js -->
    <!-- generator-angularで追加する自作モジュールの読み込み -->
<!-- endbuild -->

さらに抜粋。

<!-- build:css styles/vendor.css -->
<!-- bower:css -->
    <!-- bowerInstallで追加される外部モジュールの読み込み -->
<!-- endbower -->
<!-- endbuild -->

このbuild:cssCSSファイルをまとめますよ、という意味です。JSファイルにしたければbuild:jsですね。
その後ろの()は省略可能です。省略した場合は、対象となっているファイルを起点にファイルを探します。CSSファイルはコンパイルされることが想定されていますので、.tmpディレクトリを検索パスに含める必要がありますね。
その後ろがまとめられるファイル名です。
<!-- endbuild -->までの間に読み込まれているファイルをまとめようとしているのですが、ここで先ほど出てきた<!-- bower:css -->が入れ子になっているところがCoolですね。
つまり、bowerInstallで自動的に追加されたファイル群をuserminが更に自動的に食べてくれるという作りになっています。Great!!

useminPrepareの設定は以下のとおりです。

// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
  html: '<%= yeoman.app %>/index.html',
  options: {
    dest: '<%= yeoman.dist %>',
    flow: {
      html: {
        steps: {
          js: ['concat', 'uglifyjs'],
          css: ['cssmin']
        },
        post: {}
      }
    }
  }
}

index.htmlを読み込んで、flowにあるステップで処理をしますよ、ということのようです。でも実際はこいつが処理を行ってくれるわけではなく、「後続処理としてこういうのがあるから、そこの設定を書き換えてね」という意味です。実際に動かしてみると、どのように設定が変わっているかわかります。

$ grunt useminPrepare

出力結果抜粋です。

Configuration is now:

  concat:
  { generated: 
   { files: 
      [ { dest: '.tmp/concat/scripts/vendor.js',
          src: 
           [ 'app/bower_components/jquery/dist/jquery.js',
             'app/bower_components/bootstrap/dist/js/bootstrap.js',
             'app/bower_components/angular/angular.js',
             'app/bower_components/angular-resource/angular-resource.js',
             'app/bower_components/angular-sanitize/angular-sanitize.js' ] },
        { dest: '.tmp/concat/scripts/scripts.js',
          src: 
           [ '{.tmp,app}/scripts/app.js',
             '{.tmp,app}/scripts/controllers/main.js' ] } ] } }

  uglify:
  { generated: 
   { files: 
      [ { dest: 'dist/scripts/vendor.js',
          src: [ '.tmp/concat/scripts/vendor.js' ] },
        { dest: 'dist/scripts/scripts.js',
          src: [ '.tmp/concat/scripts/scripts.js' ] } ] } }

  cssmin:
  { options: { root: 'app' },
  generated: 
   { files: 
      [ { dest: 'dist/styles/vendor.css',
          src: [ 'app/bower_components/bootstrap/dist/css/bootstrap.css' ] },
        { dest: 'dist/styles/main.css',
          src: [ '{.tmp,app}/styles/main.css' ] } ] } }

flowに記述された、後続タスクの設定が書き換わっていることがわかりますね。ちなみにCSSファイルはconcatしないのかなぁと思ったのですが、複数のCSSが存在している場合はcssminタスクのsrcに複数ファイルが渡されるようです。この辺は後で詳しく調べます。
※ちなみにGruntfileにはuglifyjsと書いてありますが、実際のタスクとしてはuglifyに変わっています。何でだろう?と思ってGruntfileを書き換えたらエラーでした・・・誰が置き換えているんだろう・・・

最終的な出力先はdistになっていますが、途中結果を.tmpディレクトリでつなげているところが面白いですね。

concat

grunt-contrib-concatのタスクです。単純にファイルをつなげるだけのものですね。

https://github.com/gruntjs/grunt-contrib-concat

ちなみにGruntfileにはconcatのオプションはありません。useminPrepareで設定が作られることを想定しているのでしょう。
※useminを使わない場合は書く必要がありますが、その辺はGruntfileのコメントに記述されていますので、そちらを参照してください。

uglify

grunt-contrib-uglifyのタスクです。内部ではUglifyJSを使っているようです。JSファイルの難読化をしてくれるライブラリですね。

https://github.com/gruntjs/grunt-contrib-uglify

まぁ特に難しいものではなさそうなので、ここでは省略します。
ちなみにAngularJSを使うときに、依存性注入の定義を間違えると、圧縮時に変数が変わったりして動かなくなってしまうのでは?と心配してしまいそうですが、実は後述のgrunt-ngminが良い働きをしてくれているみたいです。

なお、やはりこちらもGruntfileには設定の記述はありません。
ugilifyjsの処理を探すのに苦労しました・・・内部から更にnode_modulesをたどりました・・・

cssmin

grunt-contrib-cssminのタスクですね。CSSファイルを圧縮してくれるもののようです。

https://github.com/gruntjs/grunt-contrib-cssmin

ちなみに何故かここだけはGruntfileに設定が書かれています。試しにコメントアウトして動かしましたが、設定の展開は同じ結果になるようです。ん〜、何でだろう・・・
先ほど、CSSのconcatがかまされていなかったのは、こちらのモジュールは複数のsrcを受け取ることができるからみたいですね。別にconcatした後でも良い気がしますが、Sassがあったりするとタスク制御が面倒だからかな。まぁ結果が同じならどっちでも良いのですが・・・ ※ちなみにuseminPrepareのデフォルト設定のflowにはconcatcssminと続いています。そっちとの連携がうまく出来ていないのかなぁと推測します。

ngmin

grunt-ngminのタスクです。AngularJSでの依存性注入がminifyでもキチンと動いてくれるよう処理をしてくれるものです。

https://github.com/btford/grunt-ngmin

例えばこんなソースが・・・・

angular.module('testApp').controller('MainCtrl',
  function ($scope) {
    $scope.awesomeThings = [
      'HTML5 Boilerplate',
      'AngularJS',
      'Karma'
    ];
  }
);

こんな感じに・・・

angular.module('testApp').controller('MainCtrl', [
  '$scope',
  function ($scope) {
    $scope.awesomeThings = [
      'HTML5 Boilerplate',
      'AngularJS',
      'Karma'
    ];
  }
]);

上手にできました〜! ちなみにngminの設定を見ると以下のとおりです。

// ngmin tries to make the code safe for minification automatically by
// using the Angular long form for dependency injection. It doesn't work on
// things like resolve or inject so those have to be done manually.
ngmin: {
  dist: {
    files: [{
      expand: true,
      cwd: '.tmp/concat/scripts',
      src: '*.js',
      dest: '.tmp/concat/scripts'
    }]
  }
}

外部ライブラリにも処理を行ってくれてます。何者も信じていない感じがありますね・・・
これは便利!

cdnify

grunt-google-cdnのタスクのようです。スクリプトなどの読み込みについて、自動的にCDNを指すよう変更してくれるものみたいですね。

https://github.com/btford/grunt-google-cdn

利用されるライブラリについてはbower.jsonを参照するようです。
設定は以下のとおりです。

// Replace Google CDN references
cdnify: {
  dist: {
    html: ['<%= yeoman.dist %>/*.html']
  }
}

ただし、実際は上述のタスクにより、Bowerを使って読み込まれたライブラリは全てconcatされていますね。さすがにそこまでインテリジェンスに処理をしてくれるものではないようですので、ちょっと細工して動かしてみます。

$ cp app/index.html dist/index.html
$ grunt cdnify

あり?反映されていない・・・
スクリプト言語はソースが転がってるので、すぐソースを見たくなっちゃうおっさんにはキツイ誘惑です・・・まぁ見ちゃいますが・・・

$ vi node_modules/grunt-google-cdn/node_modules/google-cdn/lib/data.js
※中略
// AngularJS

var angularFiles = [ 
  'angular',
  'angular-cookies',
  'angular-loader',
  'angular-resource',
  'angular-sanitize'
];
var angularVersions = ['1.0.7', '1.0.6','1.0.5', '1.0.4', '1.0.3', '1.0.2', '1.0.1', '1.1.5', '1.1.4', '1.1.3'];

嫌な予感しますね・・・試しにbower.jsonを書き換えて再実行してみます・・・

# bower.jsonの書き換え
"angular": "1.0.7",
# 処理結果
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

あちゃ〜。どうやら、モジュール側で想定しているVerしかCDN化してくれないようです。
ん〜、ちょっとこれは使いづらいですね・・・幸い指定されたVerが高かった場合は無害なのが救いでしょうか・・・消しちゃっても良いかなぁ・・・
もしこちらを使う場合は都度利用されているVerを追記するなどの対応が必要そうです。
※もしくはソースコピって、BowerのVerで問答無用にパスを組み立てても良いかも?

※CDN化についての議論
googleなどのCDNを利用することのメリットは、世界中の人が共通のファイルを参照することにより、ブラウザのキャッシュヒット率が高くなることが期待されています。同じjQueryファイルを再度ダウンロードしなくて済むのが大きなメリットですね。
ただし、この場合厳密には単一障害点が増えてしまうことになり、これはサイト運営のリスクになるのかなぁと思います。(極めて低いリスクですが・・・)
なので、おっさん的にはここにあるように自前でminifyして読み込んだ方が良いかなぁと思います。
あ、でもこれって厳密にはソース改変になるのかしら?その場合ライセンスとの兼ね合いはどうなるんでしょうか・・・ん〜・・・難しい問題ですね・・・

rev

grunt-revのタスクです。

https://github.com/cbas/grunt-rev

これはブラウザのキャッシュ対策として、grunt-useminと一緒に動かすことを想定しているタスクのようです。設定を見てみます。

rev: {
  dist: {
    files: {
      src: [
        '<%= yeoman.dist %>/scripts/{,*/}*.js',
        '<%= yeoman.dist %>/styles/{,*/}*.css',
        '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
        '<%= yeoman.dist %>/styles/fonts/*'
      ]
    }
  }
}

これだけだとちょっと良くわかりませんが、デフォルトの設定を合わせてみてみるとわかりやすいです。

var options = this.options({
  encoding: 'utf8',
  algorithm: 'md5',
  length: 8
});

md5の8文字のキャッシュを先頭に付けてくれるというものですね。
ここではファイルにプレフィックスをつけるだけで、実際に読み込み処理を記述するのは後述のuseminタスクということになります。

※特に開発中のモジュールの挙動を確認してもらう時とか、キャッシュが効いてて「直ってませんよ!」とお叱りを頂いた苦い思い出が蘇りました・・・便利な世の中になったものです。

usemin

上述のuseminPrepareの相方ですね。useminPrepareが設定を書き換えて、他のタスクが処理をしてくれたファイルを最終的にまとめてくれるタスクのようです。

// Performs rewrites based on rev and the useminPrepare configuration
usemin: {
  html: ['<%= yeoman.dist %>/{,*/}*.html'],
  css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
  options: {
    assetsDirs: ['<%= yeoman.dist %>']
  }
}

対象となるHTMLファイルのブロック(<!-- build:css styles/vendor.css -->とか)を解釈して、実際に指定されたファイルを読み込むよう展開してくれます。(CSSファイルも同じかな?調べてないけど・・・)

<!-- build:css styles/vendor.css -->
<!-- bower:css -->
<!-- 中略 -->
<!-- endbower -->
<!-- endbuild -->

というソースが

<link rel="stylesheet" href="styles/6004ddbf.vendor.css"/>

になりました。
revタスクで付けられたプレフィックスも見事に解釈してくれていますね。ちなみにプレフィックス付きのファイルがなかった場合はそのまま展開されるみたいです。

ちょっとイジメてみましょう。

dist/styles/111.vendor.css
dist/styles/11111111.vendor.css
dist/styles/6004ddbf.vendor.css
dist/styles/99999999.vendor.css
dist/styles/vendor.css

という感じで複数の候補があった場合どうなるか?

<link rel="stylesheet" href="styles/111.vendor.css"/>

こうなりました。まぁこんなもんですよね。同一ファイルがあったりすると予期せぬ挙動になりそうなので、buildタスクバラバラで実行してはいけません。(それやっちゃったらGrunt使う意味ないか・・・)

htmlmin

grunt-contrib-htmlminのタスクです。HTMLファイルをminifyしてくれるものですね。内部ではhtml-minifierを使っているようです。

https://github.com/gruntjs/grunt-contrib-htmlmin

設定は以下のとおりです。

htmlmin: {
  dist: {
    options: {
      collapseWhitespace: true,
      collapseBooleanAttributes: true,
      removeCommentsFromCDATA: true,
      removeOptionalTags: true
    },
    files: [{
      expand: true,
      cwd: '<%= yeoman.dist %>',
      src: ['*.html', 'views/{,*/}*.html'],
      dest: '<%= yeoman.dist %>'
    }]
  }
}

実際に動かしてみましょう。サンプルとして用意したファイルは以下の通りです。

<!DOCTYPE html>
<html>
  <head>
    <title>hoge</title>
  </head>
  <body>
    <!-- comment exists -->
    <p readonly="readonly">  has space  </p>
    <p>&nbsp;has space&nbsp;</p>
    <p> 全角スペースが 入ってます </p>
    <script type="text/javascript">
      // script comment exists
      alert('hello!');
    </script>
    <script type="text/javascript">
    <!-- 
      alert('good-bye!');
    // -->
    </script>
  </body>
</html>

結果は以下のとおりです。
※実際は改行やインデントはありません、見やすくしているだけですのであしからず。

<!DOCTYPE html>
<html>
  <head>
    <title>hoge</title>
  <body>
    <!-- comment exists -->
    <p readonly>has space</p>
    <p>&nbsp;has space&nbsp;</p>
    <p>全角スペースが 入ってます</p>
    <script type="text/javascript">
      // script comment exists
      alert('hello!');
    </script>
    <script type="text/javascript">
      alert('good-bye!');
    </script>

htmlとかbodyの閉じタグが消えました。(これは上述のlivereloadでも触れましたね。)
前後のスペース(全角も!)が消えましたが、あいだのスペースは残っています。当然&nbsp;も。
readonlyが省略されてたり、scriptタグのHTMLコメントも消えてますね。この辺は全てオプションの設定によるものです。

ちなみに本文のHTMLコメントが消えていません。これは恐らくIEの条件付きコメントへの対応だと思われます。
IEなんて知らねぇぜ!」という時はremoveComments: trueを設定してあげればキレイになります。(ちなみにJavaScriptのコメントはそれでも消えませんが・・・)

長くなりすぎましたね・・・

ということで、非常に長くなりましたが、以上です。
※途中で分割も考えたのですが、お互いにリンクした内容だったので一つにまとめたほうが良いかなぁと思ったのであえて長くしました・・・

AngularJSを調べていたつもりが、気が付くとそれ以外のところばかり深堀りしていますね。細かいことが気になるおっさんの悪いクセです。

さて、これで気分もスッキリしましたので、いよいよAngularJSでのアプリ開発に取り組むことができます。次からは実際にソースを書きながら気になったトピックをちょこちょこ書いていきたいと思います。

ここまで読んでくださった方に感謝。m( )m