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

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

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

AngularJSでの開発環境を作る(yeomanで自動生成されたファイルたちを調べる)

前回作成したAngularJSのアプリのひな形でどんなファイルができているかをいろいろ調べてみようと思います。


app/

それぞれアプリーケーションソースとテストケースが格納されています。
詳しくは次回以降に見ていきたいと思いますので、今回はスキップ。

test/

同じくテストケースが格納されているディレクトリです。
ちなみにgenerator-angularでcontrollerやdirectiveを追加すると、こちらのディレクトリにテストケースのひな形が自動的に追加されます。

$ yo angular:controller piyo
   create app/scripts/controllers/piyo.js
   create test/spec/controllers/piyo.js
$ yo angular:directive fuga
   create app/scripts/directives/fuga.js
   create test/spec/directives/fuga.js

便利ですね。

package.json

npmが読み込み設定ファイルですね。まぁMavenでいうところのpom.xmlって感じでしょうか。
ホームページとか、ライセンスとか、バグトラッキングシステムのURLまで記述することが可能みたいですね。
ですが、yeomanで作られたファイルはスカスカでした、ライブラリとして公開したりする場合はいろいろと追記してあげる必要がありそうですが、今回はとりあえず無視しておきましょう。

ドキュメント:https://www.npmjs.org/doc/json.html
日本語:http://liberty-technology.biz/PublicItems/npm/package.json.html

この中で、開発する際に注目したいのがdependenciesdevDependenciesかなぁと思います。
これは文字通り、外部依存のライブラリの一覧を定義するもので、例えば他の開発者がリポジトリをcloneしたときにnpm installとか叩いてネットから拾ってくるモジュールの一覧になるみたいです。
dependenciesはプロダクト用、devDependenciesは開発とテストのためだけに必要なものを記述するようです。
今回はAngularのアプリなので、実際dependenciesは必要ないかなぁと思います。(だってnode.js使わないし・・・)
※ちなみにJavascriptのライブラリについては後述のBowerを使います。
あと、この辺は手修正しなくても大丈夫みたいです。以下のコマンドで、実際にモジュールを取得しつつ、package.jsonにも追記を行ってくれるみたいです。

$ npm install XXX --save
# 取得と同時にdependenciesに追記
$ npm install XXX --save-dev
# 取得と同時にdevDependenciesに追記

あと、個人的に気になったのが、バージョン指定のところに^だの~だのが書いてあったことです。こちらの意味については下記サイトに詳細がありました。

英語:https://www.npmjs.org/doc/misc/semver.html#ranges
日本語:http://liberty-technology.biz/PublicItems/npm/misc/semver.html
※日本語版がもしかしたら古い気がする・・・

grunt^0.4.5って書いてあるってことは0.5.0とか1.0.0とか出たら自動的にUPDATEされちゃうってことなんじゃ・・・大丈夫なんだろうか・・・いきなり動かなくなったりしないかなぁ・・・こっそり書き換えてきたい気分・・・
※0.xをプロダクトに取り込むとか、おっさんからすると目がチカチカするくらい眩しいです・・・今の時代こんなものなのかなぁ・・・まぁサービス影響あるものじゃないから良いのかな・・・

node_modules

npmが先ほどのpackage.jsonを使ってDLしてきたモジュールを格納するディレクトリです。 特に手で触ることはないかと思います。

bower.json

B owerTwitterが作ってるパッケージマネージャです。yeomanをインストールしたときについでにグローバルインストールされていました。bower.jsonはその設定ファイルですね。

http://bower.io/

ファイルの書き方はpackage.jsonによく似ています。ついでに使い方もnpmによく似ています。なのでbower.jsonも基本的には手で触ることはなさそうです。

$ bower install XXX --save
# 取得と同時にdependenciesに追記
$ bower install XXX --save-dev
# 取得と同時にdevDependenciesに追記

これで自動的にDLされます。その後は以下のコマンドを発行すると、アプリの読み込みの設定が行われます。

$ grunt bowerInstall

この辺の細かい動きは次回以降にさらに深堀したいと思います。
ちなみに、Bowerでインストールしたいモジュールは以下のサイトで探すことができます。

http://bower.io/search/

もしくは以下のコマンドです。

$ bower search XXX

※引数なしでも動きますが、尋常じゃない量が帰ってくるのでオススメできません・・・

.bowerrc

同じくBowerの設定ファイルです。
ここではインストール先を設定しているみたいですね。デフォルトだと直下のbower_componentsというディレクトリになってしまいますが、それだと都合が悪いので設定し直していますね。

karma.conf.js

Karmaというテストツールの設定ファイルです。AngularJSの公式チュートリアルでも使われていますね。(公式の推奨ツールです)
既に基本的な設定がされていますので、直ぐに動かすことができます。
※ただし前回あったように、generator-karmaが少しバグってるようですので、必要なモジュールを追加してから実行してください。

$ grunt test

具体的なテストケースの書き方は次回以降に調べていきたいと思います。

karma-e2e.conf.js

こちらはe2eテストのための設定ファイルです。
しかし、読み込み対象となるtest/e2eディレクトリがなかったり、Gruntのタスクが存在してなかったりと何故か動かない状態っぽいです・・・何でだろ・・・
また、内容についてもKarma+ng-scenarioで記述しようとしておりますが、「新しく始めるならProtractor使いなよ!」と言われております。※参考

この辺はインストールや動かし方から調べなきゃいけなそうですね。次回以降にやりたいと思います。

Gruntfile.js

いよいよ本丸です。
こちらはGruntというタスクランナーの設定ファイルです。まぁMavenでいうところのpom.xmlって感じでしょうか・・・ってあれ?

http://gruntjs.com/

npmはどちらかというと開発環境を構築するためにツールをインストールするものであり、Gruntは実際にそれらのツールを走らせる、という印象です。文字通りタスクランナーですね。(実際npmを使ってGruntはインストールされているようです。正確にはプラグインですが・・・)
先ほどのBowerKarmaといったツールもGruntから呼び出して使うみたいですね。

実際の中身はそれなりのボリュームなので、次回以降に詳しく見ていきたいと思います。
※実際にどんなプラグインが入っていて、それぞれがどんな動きをしているのか・・・ん~、大変そうです・・・

.gitignore

gitの設定ファイルですね。 テンポラリディレクトリなどがignoreされています。

.gitattributes

同じくgitの設定ファイルですね。
ちなみに何故かファイルの終端に改行がありません・・・何故だ?

.editorconfig

おっさん初めてみたファイルです。どうやら以下の規格に準拠するためのものみたいですね。

http://editorconfig.org/

プロジェクト内で標準化されたコードスタイルをIDEなどに伝えるための規格のようですね、とGoogle先生がおっしゃっているような気がします・・・ちなみにEclipseが対応していないという・・・

dist

プロダクト用ソースを作成するため、grunt buildしたときに出力されるファイルを格納するディレクトリです。
こちらの設定はGruntfile.jsに記述されています。

yeoman: {
  // configurable paths
  app: require('./bower.json').appPath || 'app',
  dist: 'dist'
}

ちなみにこちらのディレクトリは.gitignoreされているので、名前を変えたりしたときは注意が必要ですね。

.tmp

文字通りテンポラリディレクトリです。Gruntで実行される様々なタスクで使われる、途中状態のファイルを格納するディレクトリのようです。例えば、CSSを連結した結果のファイルを置いておくとか。
尚、こちらのディレクトリはGruntfile.jsの各所にベタ書きされていますので、もはや変更しようとか思えなくなりました・・・なんでdistみたいに変数で受けなかったんだろう・・・
あと、やはりこちらも.gitignoreされています。

.jshintrc

JSHintの設定ファイルです。これはJavascriptの構文チェックを行ってくれるライブラリらしいですね。package.jsonにもインストールするよう書かれていますし、Gruntfile.jsにもタスクとして登録されていますね。
試しにapp/scripts/app.jsvar hoge = 1;なんてコードを追記して実行してみます。

$ grunt jshint:all

app/scripts/app.js
  line 2  col 9  'hoge' is defined but never used.

✖ 1 problem

Warning: Task "jshint:all" failed. Use --force to continue.

Aborted due to warnings.

なんと、変数を読み込んでないだけで怒られてしまいました・・・恐るべし・・・

ちなみに細かい設定については下記サイトを参考にするとよさそうです。

JSHint:http://jshint.com/
オプションの一覧:http://jshint.com/docs/options/

オプション多い・・・まぁいったんはデフォルトで問題ないかなぁ・・・

.travis.yml

Travis CIというCI(継続的インテグレーション)をしてくれるホスティングサービスで使う設定ファイルみたいです。
例えばGitHubとかの連携を設定しておくと、PUSHされたタイミングでTravisが変更を受け取って、このファイルに書かれた情報をもとにテストをして、結果をメールとかで返してくれるものみたいです。
なるほど、世の中便利になったものですね。オープンソースならいいけど、プロダクトコードには不向きかなぁ・・・
まぁあっても影響があるものではないけど、気になるなら消してしまってよさそうですね。
YAMLファイルは昔Symfonyをゴリゴリしていた時以来かも・・・

https://travis-ci.org/


今回はここまでです。
次回は実際のアプリケーションソース(index.html)を読みながら、もう少しGruntの仕事を深堀しようと思います。

AngularJSでの開発環境を作る(yeomanで環境構築)

本格的にAngularJSで開発をしていくための環境と整えていきたいと思います。 開発環境の作成には、yeomanというツールが便利みたいです。

http://yeoman.io/

早速インストールです。npmを使えば楽ちんですね。

$ npm install yo -g
$ which yo
/usr/local/bin/yo
$ yo --version
1.1.2

入りました。ちなみに、最初に起動した時に以下のようなメッセージが表示されました。

[?] ==========================================================================
We're constantly looking for ways to make yo better! 
May we anonymously report usage statistics to improve the tool over time? 
More info: https://github.com/yeoman/insight & http://yeoman.io
==========================================================================:

よりより方法を探しているから、匿名でレポートしてね?って感じですね。

この後インストールしようと思っていたgruntbowerもインストールされていました。

$ which grunt
/usr/local/bin/grunt
$ grunt --version
grunt-cli v0.1.13
$ which bower
/usr/local/bin/bower
$ bower --version
1.3.3

yoemanにはAngularJS用のテンプレートが用意されているようです。せっかくなのでこちらも入れます。

$ npm install -g generator-angular

テキトーなディレクトリでまずは試しに作ってみましょう。

$ yo angular

     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___\   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

Out of the box I include Bootstrap and some AngularJS recommended modules.

[?] Would you like to use Sass (with Compass)? No
[?] Would you like to include Bootstrap? Yes
[?] Which modules would you like to include? (Press <space> to select)
❯⬢ angular-resource.js
 ⬢ angular-cookies.js
 ⬢ angular-sanitize.js
 ⬢ angular-route.js

利用するモジュールを選択できるみたいですね。
とりあえずBootstrapは使いたいので、選択します。angular-resourceangular-sanitizeは利用用途が高いのでYesです。angular-routeangular-uiを使うのでこちらは使いません。angular-cookiesは・・・今のところ使うシーンが想像できません・・・

実際に動かしてみましたが、いくつかエラーが出ておりました。直下にあるnpm-debug.logというファイルを見ろよと言われましたが・・・でかい・・・
なんとなく見てみると、どうやらkarmaあたりのインストールで失敗しているみたいです。
前回グローバルインストールしたが悪さしている気がします・・・npm uninstall karma -gで削除します。あと、ネットにて「npmのキャッシュも綺麗にしてからやってみな?」という書き込みもありましたので、そちらも試してみます。コマンドはnpm cache clearです。
再度実行!

$ yo angular

     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___\   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `
 ※以下略

無事成功しました!やった!
※前回の記事の当該箇所は神速で削除・・・
※この後、別のディレクトリで実行したらやはりkarma周りの箇所で失敗したり、別のところで失敗したり、yeomanを再インストールしたらうまくいったりしました行かなかったり・・・再現性が謎なので下記メッセージが最後に出てなければ成功とします・・・

npm ERR! Additional logging details can be found in:
npm ERR!     /path/to/directory/npm-debug.log
npm ERR! not ok code 0

その他AngularJSのgeneratorについての詳細は以下に詳しく書いてあります。(英語だけど問題ないよね)

https://github.com/yeoman/generator-angular

では、動かしてみましょう。

$ grunt serve
Running "serve" task

Running "clean:server" (clean) task
Cleaning .tmp...OK

Running "bowerInstall:app" (bowerInstall) task

Running "concurrent:server" (concurrent) task

    Running "copy:styles" (copy) task
    Copied 1 files

    Done, without errors.


    Execution Time (2014-05-xx xx:xx:xx UTC)
    loading tasks   3ms  ▇▇▇▇▇▇▇▇ 16%
    copy:styles    15ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 79%
    Total 19ms

Running "autoprefixer:dist" (autoprefixer) task
Prefixed file ".tmp/styles/main.css" created.

Running "connect:livereload" (connect) task
Started connect web server on 127.0.0.1:9000.

Running "watch" task
Waiting...

ターミナルが占拠され、ブラウザが立ち上がりました。ちなみにこのページはapp/index.htmlが表示されています。
また、Running "watch" taskとあるように、この状態でファイルを編集し、保存すると自動的にページに反映がされます。便利ですね。
※これはpackage.jsongrunt-contrib-watchが入っていて、かつGruntfile.jsが書いてあるからですね。この辺の深堀りも追々していきたいと思います。

続いてテストをしてみます。

$ grunt test

結果を抜粋。

Running "karma:unit" (karma) task
Warning: No provider for "framework:jasmine"! (Resolving: framework:jasmine) Use --force to continue.

Aborted due to warnings.

失敗しています・・・
ネットであちこち調べましたが、どうやらkarmaのjasmineのモジュールが正しく入っていないことが原因みたいですね。
※generator-karmaのバグのようで、一応報告されているみたいですが、「I'll try and fix it later. thanks」で止まってます・・・修正に期待。

まぁ以下のコマンドで解決しますので、大きな問題ではないですね。

$ npm install karma-jasmine --save-dev
$ npm install karma-chrome-launcher --save-dev

karma-chrome-launcherはブラウザごとにいろいろなlauncherが用意されているみたいです。調子に乗って色々と入れてみました。(もちろんブラウザ本体を入れることもお忘れなく)

$ npm install karma-safari-launcher --save-dev
$ npm install karma-firefox-launcher --save-dev

他にもいろいろなluncherがあるみたいなので、以下のサイトを調べてみると見つかると思います。

https://github.com/karma-runner

テストで起動するブラウザを切り替えるためにはkarma.conf.jsを編集します。

// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['Chrome'],

コメントがあるので、わかりやすいですね。とりあえずFirefoxを追加してみます。

browsers: ['Chrome', 'Firefox'],

再度実行します。

Firefox 29.0.0 (Mac OS X 10.9): Executed 1 of 1 SUCCESS (0.023 secs / 0.014 secs)
Firefox 29.0.0 (Mac OS X 10.9): Executed 1 of 1 SUCCESS (0.023 secs / 0.014 secs)
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 1 of 1 SUCCESS (0.039 secs / 0.037 secs)
TOTAL: 2 SUCCESS

無事に成功しました。(なんでFirefoxの結果が2行出てるんだろ・・・)

今回はここまでです。 次は実際に作成されたファイルを見ながら、提供されたタスクとかを調査してみたいと思います。

AngularJSのチュートリアルを動かす

AngularJSの公式チュートリアルを実際に動かしてみます。 といっても、何も難しいことではないんですけどね・・・

https://docs.angularjs.org/tutorial

まずはチュートリアルをcloneします。Macにはgitは標準で入っているようですので安心。

$ git clone --depth=14 https://github.com/angular/angular-phonecat.git

とりあえず動かしていましょう。

$ cd angular-phonecat/
$ npm start

bowerがインストールされました。後々インストールしようとしていたのでまぁOKです。
Karmaとか個別に動かさずにnpmで動かしてねって言ってるのでそうします。詳しくはまだわかってないのですが、多分直下にあるpackage.jsonが適切に設定されているから正しく動いているんでしょうね。

Starting up http-server, serving ./ on port: 8000
Hit CTRL-C to stop the server

ターミナルが占拠されました。終わらせるためにはプロセスを殺す必要があるんですね。 ブラウザで以下のURLを叩くと、画面が出てきました。

http://localhost:8000/app/index.html

チュートリアルの別のステップにするためには、checkoutしなおしてあげれば良いみたいです。
ちなみにチュートリアルの各ページの先頭にcheckoutのためのコマンドが書いてありますので、コピっとペっとしてあげればOKです。

$ git checkout -f step-0
Note: checking out 'step-0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 0c189aa... step-0 bootstrap angular app

※何故かハマってしまったことなんですが、cloneした後すぐにstep-0でcheckoutするとjqueryのモジュール読み込みがうまくいかないようで、正しく動かなくなってしまいます・・・なので、最初に起動してからcheckoutして動かしたほうが良さそうです。まぁstep-0はホントにHelloWorldなのでわざわざ動かすほどのものでもない気がしますが・・・

次はテストもしてみましょう。先ほどのターミナルとは別の端末を開いて以下のコマンドを実行です。

$ npm test

ブラウザが立ち上がりました。またターミナルを占拠されています。

Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 1 of 4 SUCCESS (0 secs / 0.037 secs
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 2 of 4 SUCCESS (0 secs / 0.042 secs
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 3 of 4 SUCCESS (0 secs / 0.045 secs
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 4 of 4 SUCCESS (0 secs / 0.048 secs
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 4 of 4 SUCCESS (0.207 secs / 0.048 secs)

テストも成功しているみたいですね。
今回はtest/unit配下に4つファイルがあり、それらが実行されているようですね。テストにはKarmaが動いて、test/karma.conf.jsの設定にそって動いているようです。(という雰囲気をpackage.jsonから感じます・・・)
ちなみに、Karmaの面白いのが、テストのプロセスが上がっている間はファイルを監視しています。 テストの中身を変更すると、即座にテスト結果がログとして流れます。試しにテストを失敗させたりするとErrorがでます。

Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 1 of 4 (1 FAILED) (0 secs / 0.036 s
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 2 of 4 (1 FAILED) (0 secs / 0.04 se
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 3 of 4 (1 FAILED) (0 secs / 0.043 s
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 4 of 4 (1 FAILED) (0 secs / 0.045 s
Chrome 34.0.1847 (Mac OS X 10.9.2): Executed 4 of 4 (1 FAILED) (0.358 secs / 0.045 secs)

なるほど、この2つのプロセスを立ち上げながら開発進めていく感じなんですね。
※実際はtest/karma.conf.jsにあるautoWatchtrueになっているからですね。デフォルトの設定ということなのできっと推奨なんでしょうね。

これでチュートリアルはひと通り動きましたかね?細かいところを見てみるとまだまだ掘り下げたいところがたくさんあったのですが、そのへんは徐々に調べていきたいと思います。

AngularJSの開発環境を作る(Homebrew、npmのインストール)

引き続き、AngularJSを楽しんでみたいと思います。

AngularJSでは、開発のためのツールも色々と紹介されています。特にテストの方式については、かなり厚めに紹介されている印象ですね。やはりテストは大事ですね。 特に、Viewに関する部分って、ビジネスロジックと違って自動テストがしづらいなぁとおっさんは感じていたのですが、AngularJSでは、テストがうまくいくように色々と考えられているように感じます。流石ですね。

ということで、今日はAngularJSで開発するために使えるツール群のインストールをしたいと思います。まだ開発できなさそうですね・・・

開発環境はMacです。おっさんはWindowsメインで使っているのですが、どうも相性が悪いみたいですので・・・良い機会なので、新しくMacを買いました!早速いろいろインストールしたいと思います!

Homebrew

Macでいろいろとインストールするためのパッケージマネージャーです。

http://brew.sh/

以下のコマンドを発行すればインストール完了です。

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

インストールできたら、以下のコマンドを発行してインストールを確認します。(色々と付属のツールをインストールします。残念ながらログをとり忘れました・・・)

$ brew doctor

UPDATEしろ!と言われたので、以下のコマンドでUPDATEです。

$ brew update

brewでインストールされるモジュールを使うためのPATH変数の設定に気をつけましょう。(多分勝手に設定されているっぽいですが・・・)

npm

NodePackagedModulesだそうです。rpmとかgemのNode.js版って感じなんですかね。AngularJSはJavaScriptだからNode.jsなんでしょうか?

https://www.npmjs.org/

ネットとかを調べたら、brewでインストールできないよという記事があったのですが、とりあえずダメ元でチャレンジしてみました。

$ brew install npm
==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/node-0.10.2
######################################################################## 100.0%
==> Pouring node-0.10.26.mavericks.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
==> make install
==> /usr/local/bin/npm update npm -g
==> Summary
🍺  /usr/local/Cellar/node/0.10.26: 1523 files, 18M

あ、入ったっぽいですね。

$ which npm
/usr/local/bin/npm
$ npm -v
1.4.10

※追記:2014/05/10
もともとここにはkarmaのグローバルインストールを書いていたのですが、後日グローバルインストールが悪さしていることがわかりましたので、記事もろともnpm uninstall karma -gで削除しました・・・無理やりPATHまで通したんですが、そのへんの思想がアダになったようです・・・気を利かせずに与えられたものをキチンと使うのが良さそうです・・・

だいぶ長くなってしまったので、今日はここまでにします。 次回はもうちょっと必要そうなパッケージをインストールしたり、AngularJSのチュートリアルを動かしたりしたいと思います。

AngularJSが楽しそう

AngularJSとはGoogleが頑張ってるオープンソースJavaScriptフレームワークですね。いわゆるMVCとかってやつですね。

AngularJS

AngularJSの場合は「MVW」といっているようです。「Model View Whatever」ということで、「MVなんとか」といった感じでしょうか?

おっさんはいつもクラサバのアプリばっかり作ってるので、よく知らなかったのですが、「SPA(Single-page Application)」って言われてるんですね。ん〜、このへんがどうなっているかも徐々に勉強していきたいですね。

さて、AngularJSの公式ページにはチュートリアルがありますので、まずはそちらを見てみます。英語だけど・・・(頑張って目を通しました・・・) ※日本語の翻訳サイトもあるみたいですね・・・後から知りました・・・

AngularJS日本語リファレンス

あとはみんな大好きオライリーから書籍も出ているようです。

AngularJSアプリケーション開発ガイド

ちなみに、今回おっさんは初めてオライリー電子書籍を購入してみました。結構快適ですね、楽しい!

ということで、まずはこの辺の情報を整理して行きたいと思います。