angular-generatorで作られたGruntfile.jsの中身を調べる。
前回はangular-generator
で作られたファイルについてざっくり調べた結果を書き連ねましたが、今回はその中でGruntfile.js
の中身を調べつつ、Grunt
の使い方を見てみたいと思います。
Gruntってなんぞ?
Grunt
は「The JavaScript Task Runner(公証)」です。
「minifyとかcompileとかunit testとか簡単にしたいでしょ?Gruntは日々成長していて、誰かが君に必要なプラグインをもう作ってくれてるはずだから使ったら良いよ」みたいなことが書いてありました。
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.js
かGruntfile.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
は文字通り設定を記述するためのもので、タスクやターゲットではないようです。(たぶん予約語なのかな?)
build
、build2
、build3
がそれぞれターゲットになり、それぞれの設定を表しています。
ターゲット毎に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-glob
とminimatch
というライブラリが使われているみたいです。詳しい使い方はやはりコチラ。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'
]);
少ないですね。test
とbuild
は別に定義されたタスクなので、ここでは掘り下げません。
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
を呼び出してくれるものですね。
定義されているターゲットを見てみます。
jshint: {
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/scripts/{,*/}*.js'
],
test: {
options: {
jshintrc: 'test/.jshintrc'
},
src: ['test/spec/{,*/}*.js']
}
},
シンプルですね。各所にGrunt
のfiles
指定があります。「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
ターゲットで動かす状況を考えると、livereload
をfalse
にしておくのが正しい気もしますが、そもそもそんな状況って何かあるのだろうか・・・ん~、まぁサービス影響はなさそうだし、心の片隅に置いておきましょう。
だいぶ脱線しました・・・
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'
}
すべてGrunt
のfilses
を使ってますが、特に難しいところはなさそうですね。詳しくは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
のタスクに置き換わります。
ついでにcopy
、imagemin
、svgmin
の設定も見てみます。
// 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:css
はCSSファイルをまとめますよ、という意味です。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
にはconcat
、cssmin
と続いています。そっちとの連携がうまく出来ていないのかなぁと推測します。
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> has space </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> has space </p>
<p>全角スペースが 入ってます</p>
<script type="text/javascript">
// script comment exists
alert('hello!');
</script>
<script type="text/javascript">
alert('good-bye!');
</script>
html
とかbody
の閉じタグが消えました。(これは上述のlivereload
でも触れましたね。)
前後のスペース(全角も!)が消えましたが、あいだのスペースは残っています。当然
も。
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
この中で、開発する際に注目したいのがdependencies
とdevDependencies
かなぁと思います。
これは文字通り、外部依存のライブラリの一覧を定義するもので、例えば他の開発者がリポジトリを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
ower
はTwitterが作ってるパッケージマネージャです。yeoman
をインストールしたときについでにグローバルインストールされていました。bower.json
はその設定ファイルですね。
ファイルの書き方はpackage.json
によく似ています。ついでに使い方もnpm
によく似ています。なのでbower.json
も基本的には手で触ることはなさそうです。
$ bower install XXX --save
# 取得と同時にdependenciesに追記
$ bower install XXX --save-dev
# 取得と同時にdevDependenciesに追記
これで自動的にDLされます。その後は以下のコマンドを発行すると、アプリの読み込みの設定が行われます。
$ grunt bowerInstall
この辺の細かい動きは次回以降にさらに深堀したいと思います。
ちなみに、Bower
でインストールしたいモジュールは以下のサイトで探すことができます。
もしくは以下のコマンドです。
$ 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
って感じでしょうか・・・ってあれ?
npm
はどちらかというと開発環境を構築するためにツールをインストールするものであり、Grunt
は実際にそれらのツールを走らせる、という印象です。文字通りタスクランナーですね。(実際npm
を使ってGrunt
はインストールされているようです。正確にはプラグインですが・・・)
先ほどのBower
やKarma
といったツールもGrunt
から呼び出して使うみたいですね。
実際の中身はそれなりのボリュームなので、次回以降に詳しく見ていきたいと思います。
※実際にどんなプラグインが入っていて、それぞれがどんな動きをしているのか・・・ん~、大変そうです・・・
.gitignore
git
の設定ファイルですね。
テンポラリディレクトリなどがignoreされています。
.gitattributes
同じくgit
の設定ファイルですね。
ちなみに何故かファイルの終端に改行がありません・・・何故だ?
.editorconfig
おっさん初めてみたファイルです。どうやら以下の規格に準拠するためのものみたいですね。
プロジェクト内で標準化されたコードスタイルを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.js
にvar 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をゴリゴリしていた時以来かも・・・
今回はここまでです。
次回は実際のアプリケーションソース(index.html)を読みながら、もう少しGruntの仕事を深堀しようと思います。
AngularJSでの開発環境を作る(yeomanで環境構築)
本格的にAngularJSで開発をしていくための環境と整えていきたいと思います。
開発環境の作成には、yeoman
というツールが便利みたいです。
早速インストールです。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
==========================================================================:
よりより方法を探しているから、匿名でレポートしてね?って感じですね。
この後インストールしようと思っていたgrunt
とbower
もインストールされていました。
$ 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-resource
とangular-sanitize
は利用用途が高いのでYesです。angular-route
はangular-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.json
にgrunt-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
にあるautoWatch
がtrue
になっているからですね。デフォルトの設定ということなのできっと推奨なんでしょうね。
これでチュートリアルはひと通り動きましたかね?細かいところを見てみるとまだまだ掘り下げたいところがたくさんあったのですが、そのへんは徐々に調べていきたいと思います。
AngularJSの開発環境を作る(Homebrew、npmのインストール)
引き続き、AngularJSを楽しんでみたいと思います。
AngularJSでは、開発のためのツールも色々と紹介されています。特にテストの方式については、かなり厚めに紹介されている印象ですね。やはりテストは大事ですね。 特に、Viewに関する部分って、ビジネスロジックと違って自動テストがしづらいなぁとおっさんは感じていたのですが、AngularJSでは、テストがうまくいくように色々と考えられているように感じます。流石ですね。
ということで、今日はAngularJSで開発するために使えるツール群のインストールをしたいと思います。まだ開発できなさそうですね・・・
開発環境はMacです。おっさんはWindowsメインで使っているのですが、どうも相性が悪いみたいですので・・・良い機会なので、新しくMacを買いました!早速いろいろインストールしたいと思います!
Homebrew
Macでいろいろとインストールするためのパッケージマネージャーです。
以下のコマンドを発行すればインストール完了です。
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なんでしょうか?
ネットとかを調べたら、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の場合は「MVW」といっているようです。「Model View Whatever」ということで、「MVなんとか」といった感じでしょうか?
おっさんはいつもクラサバのアプリばっかり作ってるので、よく知らなかったのですが、「SPA(Single-page Application)」って言われてるんですね。ん〜、このへんがどうなっているかも徐々に勉強していきたいですね。
さて、AngularJSの公式ページにはチュートリアルがありますので、まずはそちらを見てみます。英語だけど・・・(頑張って目を通しました・・・) ※日本語の翻訳サイトもあるみたいですね・・・後から知りました・・・
あとはみんな大好きオライリーから書籍も出ているようです。
ちなみに、今回おっさんは初めてオライリーの電子書籍を購入してみました。結構快適ですね、楽しい!
ということで、まずはこの辺の情報を整理して行きたいと思います。