Padrinoをインストールしたよ
Padrinoを動かしてみるよ。
前回はRubyをインストールしたところまででしたが、今回はRubyのFWを入れて、Web開発を進めていきたいと思います。
今回選んだFWはPadrino
です。
http://jp.padrinorb.com/
PadrinoはSinatraをベースにしたFWで、ActiveRecordとか使えて便利みたいです。上述の公式(翻訳)サイトのドキュメントは途中まで日本語になっているのですが、まだまだ英語の領分が大きい・・・頑張ります。
インストール自体は簡単です。
$ mkdir ${work}
$ cd ${work}
$ bunlde init
$ echo 'gem "padrino"' >> Gemfile
$ bundle install --path vendor/bundle
Gemを使うと基本的にグローバルにインストールされてしまうのですが、--path
を指定することで、ローカルに閉じた状態でインストールすることができるようです。
ただ、この場合、グローバルに入っていないので、インストールしたモジュールへのアクセスがbundler
経由じゃないと呼び出せないので注意が必要です。
Padrinoには便利なGeneratorがあるよ。
次に、サンプルのアプリを作成してみます。
今回はRestfulなAPIを作りたかったので、必要そうなものを最小限で構成してみます。
その他、いろんなモジュールを組み合わせてアプリを作ることができたります。詳しくは公式を見ていただけると大丈夫かと。
$ bundle exec padrino g project study_project -d activerecord -t rspec -e none
これでアプリのひな形が出来ました。
ソース管理するときはこちらをルートにしてpushしてあげると良い感じです。
さて、実際に動かす準備を進めます。
まずは依存モジュールを落としてきます。こちらもグローバルが何となく気持ち悪かったので、ローカルに配置してあります。
$ cd study_project
$ bundle install --path vendor/bundle
無事に配置が終わったら、コントローラを作ります。
この辺もGeneratorがたくさんあるので、うまく活用していきます。
$ bundle exec padrino g controller Hello
先程も触れましたが、padrino
をグローバルに入れていないので、bundle exec
から呼び出します。これによりapp/controllers/hello.rb
が出来ます。何やらコメントが書いてあるので、何となく書き方はわかるかと思いますが、一応以下のような記述を追記します。
get :world do
"Hello World!"
end
これで準備は万端です。早速動作確認してみましょう。
RubyではWEBrick
というサーバが同梱されているらしいので、そちらで動作確認できます。
$ bundle exec padrino s
「s」は「start」の意味です。
無事立ち上がったら、http://localhost:3000/hello/worldで動作確認してみます。
文字列が表示されたら成功です!
通常のWeb開発であれば、erbなどテンプレートエンジンを組み合わせて、画面描画を行ったりするのでしょうけど、今回はRestfulAPIを作りたいので、その辺は不要ですね。
APIにしたいのだけど・・・
RestfulなAPIを作るときにGrape
というものがあるのですが、どうもRailsを基準にしているのか、Padrinoではうまく動かないっぽいです・・・
Padrinoとのつなぎ込みのモジュールもあったのですが、PadrinoのVerが「0.11」に準拠しているらしく、おっさんの落としてきた「0.12」だとエラーになってしまいました・・・
次回はこの辺をしっかり調査して、何とか動かせるようにしたいです。
※もしくは妥協案を探したい・・・
Jenkins入れようとしてCentOSに新しいgitが入ってくれなくてハマった話
改めてJenkinsなんぞを入れてみようとしたのですが、Jenkins内部で想定しているgitのバージョンと、CentOSのyumで入れられるgitのバージョンに差異があってエラーで落ちまくってしまいました・・・
男は黙ってソースコンパイル!かとも思ったのですが、yumで新しいのを入れる方法をネットで拾ったのでメモメモです。
$ sudo rpm --upgrade http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm
$ sudo yum install --enablerepo=rpmforge-extras git
以上、小ネタでした。
Rubyの環境をMacで作ってみるよ
Rubyを触ってみるよ。
すっかりAngularJS
にご執心のおっさんですが、業務でRuby
を使うことになったので、自分で触ってみるか、ということになってしまいました。
まぁフロントから受けるAPI部分を作ると思えば、Ruby
も楽しいはずですよね。
Rubyをインストールする。
手元のMacには、Rubyの2.0.xが標準で入っているようですが、既存のライブラリととの相性がまだよくないみたいなので、人気の高い1.9.3系を入れたいと思います。
Ruby自体のVerを管理してくれるrbenv
というものがあるみたいなので、まずはそちらをインストールするところから始めます。
インストール自体はHomebrew
でサクッとインストールできます。
$ brew update
$ brew install rbenv ruby-build
インストールできたら、~/.bash_profile
に以下の記述を足します。
eval "$(rbenv init -)"
~/.rbenv/shims
がPATHに追記されるようになります。
※ちょっと不思議だったのは、Macの場合の.bash_profileってLinux系でいうところの.bashrcと同じ意味なんですね・・・bash bashするとパスがどんどん追加されていきます・・・
次にお目当てのRubyのVerを探します。
$ rbenv install -l
1.9.3系の新しいものをインストールしてみましょう。
$ rbenv install 1.9.3-p547
インストールできたら、切り替えを行います。
$ rbenv versions
* system (set by /Users/xxx/.rbenv/version)
1.9.3-p547
$ rbenv global 1.9.3-p547
$ ruby -v
ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0]
おぉ、できた!
どうやらrbenv
は~/.rbenv/shims/
配下のRuby系モジュールをスイッチしてくれるものみたいですね。
$ gem install bundler
Fetching: bundler-1.6.4.gem (100%)
Successfully installed bundler-1.6.4
1 gem installed
Installing ri documentation for bundler-1.6.4...
Installing RDoc documentation for bundler-1.6.4...
あ、入りました。
ちなみに今回はrbenv
経由でのgem
でインストールされているようなので、以下のコマンドを発行しないと使えるようになりません。
$ rbenv rehash
あと、rbenv
経由であった場合は以下のコマンドでも同じことが起こるようです。(こっちが本線かな?)
$ rbenv exec gem install bundler
まぁどっちでも大丈夫っぽいです。
本日はとりあえずここまで
とりあえずインストールできましたが、「そもそもRubyってなんぞ?」っていうレベルのおっさんはきっとこれからたくさん落とし穴にはまっていくんだろうなぁ・・・
次回以降はフレームワークの選定やら、イケてる開発フローなんかを調べていけたらなぁと思っています。
ng-includeなどについてリクエストがどのくらい発生するのか?
ふと疑問に思ったこと
directiveやng-includeなどで、外部ファイルを読み込むようなことがあると思います。
$templateCache
では、スクリプトタグで書くか、ロジックで設定するようにすると良いよ、と書かれています。
https://docs.angularjs.org/api/ng/service/$templateCache
でも実際、パーツとしてのHTMLを開発するにしても、追加するたびに1つのファイルに編集が集中してしまうのはなんかイヤな気がします。パーツとなるHTMLは開発中のモジュールと合わせて独立して追加ができたほうが効率は良さそうだなぁと感じるわけです。また、スクリプトタグでHTMLを書くと、IDEとかのハイライトが効かないんじゃないかとか思ってしまいます。
ちなみに別ファイルとして作成したパーツを自動でキャッシュに乗せてくれるgrunt-angular-templates
というものがあります。
https://github.com/ericclemmons/grunt-angular-templates
これを使っても良いのですが、livereloadに対応してくれるの?とかminifyさせるにはどこにタスクを差し込むの?とかいろいろ考えることが多そうです。
で、そもそもパーツのHTMLを直接叩いたら何が起こるか?を一応確認しておこうと思いました。
ネットの記事などでは、「アクセスするときにリクエストが発生してヤな感じ」みたいなことが書かれていましたが、実際はどう動いているんでしょうか?
とりあえず、ng-includeを使った簡単なサンプルを作ってみました。chromeの開発者ツールなのでリクエストの発生を見てみたいと思います。
テストのソースコード(同一階層に適当なa.htmlとb.htmlを置いてください)
<!DOCTYPE html>
<html lang="ja" ng-app>
<head>
<meta charset="utf-8">
<title>テスト</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
</head>
<body>
<h3>ng-includeのテスト</h3>
<div>
<label>
表示する
<input type="checkbox" ng-model="visible" ng-init="visible=true"></input>
</label>
</div>
<div>
<label>
a.html<input type="radio" ng-model="incTemplate" ng-value="'a.html'"></input>
</label>
<label>
b.html<input type="radio" ng-model="incTemplate" ng-value="'b.html'"></input>
</label>
</div>
<hr />
<div ng-show="visible" ng-init="visible = true">
Show Area!
<div ng-include="incTemplate" ng-init="incTemplate = 'a.html'"></div>
</div>
</body>
</html>
確かにb.htmlに切り替えたタイミングで通信は発生しているようですが、以降は通信は発生していませんでした。最初の通信時にキャッシュに乗ったので、通信はこれ以上発生しないみたいですね。
動作としては、遅延初期化のようなイメージでしょうか。すべてのテンプレートを読み込む前提であれば、1ファイルにまとまったテンプレートを一括で読み込んだほうがトータルの処理時間は短くなるかもしれませんが、最初のローディング処理が長くなってしまう気がしますね。個人的にはこの方法も大幅に間違っていない気もします。ケースによって使い分けても良いかもしれませんね。
protractorでのE2Eテスト観点でng-bindを使うべきかどうか。
APIマニュアルを見ていて気になりました。
こちらの記述を見ていてちょっと気になったことがあったので少し調べてみたメモです。
サンプルとして以下のようなソースがありました。
<span>{{person.name}}</span>
<span ng-bind="person.email"></span>
<input type="text" ng-model="person.name"/>
ていうソースにおいて。
// Find element with {{scopeVar}} syntax.
element(by.binding('person.name')).getText().then(function(name) {
expect(name).toBe('Foo');
});
// Find element with ng-bind="scopeVar" syntax.
expect(element(by.binding('person.email')).getText()).toBe('foo@bar.com');
// Find by model.
var input = element(by.model('person.name'));
input.sendKeys('123');
expect(input.getAttribute('value')).toBe('Foo123');
と書きましょうとありますが、ちょっと「ん?」って感じ
expect(element(by.binding('person.name')).getText()).toBe('Foo');
じゃダメ?
と思ったので、試しに実行。ちゃんと動きますね。
ただし、{{person.name}}
をspan
タグで囲っておかないと取得されるテキストデータがページ全体に広がってしまい大変なことになるので注意が必要でした。
{{person.name}}
<span ng-bind="person.email"></span>
<input type="text" ng-model="person.name"/>
これだと、{{person.name}}
の一つ上のタグが対象範囲として取得されてしまうので、同階層のテキストが全部取れちゃいます。
どうもng-bindの利用が推奨っぽいのでそっちで書いておくのが安全そうですね。
※どのみちSPANで囲むのであればそっちの方がキレイかな?
generator-angular環境に手を加えて、protractorでE2Eテストを動かしてみる。
e2eテストってカッコ良いですよね
AngularJSの公式チュートリアルにはE2Eテストのチュートリアルも含まれています。
これを動かすと、ブラウザが立ち上がって、文字入力や画面遷移が自動的に実行され、テストされていきます。
個人的にはユニットテストだけでテストを担保するのに若干の不安がありましたので、ここまで外のレイヤーでのテストが継続的に行われていると安心できるかなぁと思っています。(もっともテストを書く手間が大変そうではありますが・・・)
是非ともこのテストも標準で行っていきたいのですが、generator-angular
では標準で動かす準備がないようです。
ng-scenario
を使ったやりかけの部分はあるようですが、AngularJSの公式では「ng-scenario
じゃなくてprotractor
使いなよ」って言われていますので、そちらを使えるようにいろいろ調べて行きたいと思います。
protractorを動かすためのサンプルページを作る
今回はprotractor
を動かすだけなので簡単なページです。
yo angular
でできたアプリを簡単に改造しました。
なおデータの取得はソースベタになっていますのであしからず。
<div class="container" ng-controller="MainCtrl">
<div class="row">
<div class="col-xs-3">
<form class="well" role="form">
<div class="form-group">
<label for="searchText">Test Search</label>
<input id="searchText" type="text" class="form-control" ng-model="query.$" placeholder="input search text">
</div>
<div class="form-group">
<label for="sortKey">Sort Key</label>
<select id="sortKey" class="form-control" ng-model="sort.key">
<option value="id">Default</option>
<option value="title">Title</option>
</select>
</div>
<div class="form-group">
<label for="sortOrder">Sort Revers</label>
<input id="sortOrder" type="checkbox" ng-model="sort.revers"></input>
</div>
</form>
</div>
<div class="col-xs-9" class="items-panel">
<div class="panel panel-default" ng-repeat="e in items | filter:query | orderBy:sort.key:sort.revers">
<div class="panel-heading">
<div class="item-title" ng-bind="e.title"></div>
</div>
<div class="panel-body">
<div class="item-description" ng-bind="e.description"></div>
</div>
</div>
</div>
</div>
</div>
以下はJSのコード。
'use strict';
angular.module('testApp')
.controller('MainCtrl', function ($scope) {
$scope.items = [
{
id:1,
title:'HTML5',
description: '説明文は省略'
},
{
id:2,
title:'AngularJS',
description: '説明文は省略'
}
];
$scope.query = {
$: ''
};
$scope.sort = {
key:'id',
revers:false
};
});
とりあえず、サンプルで動かすテストコードです。
AngularJSのサンプルから拾ってきたものです。(画面の挙動とは一切関係ありませんね・・・)
describe('angularjs homepage', function() {
it('should greet the named user', function() {
browser.get('http://www.angularjs.org');
element(by.model('yourName')).sendKeys('Julie');
var greeting = element(by.binding('yourName'));
expect(greeting.getText()).toEqual('Hello Julie!');
});
});
protractorを動かす
Grunt
でprotcator
を動かすモジュールを探したら、grunt-protractor-runner
なるものがヒットしたので、そちらを使ってみます。
https://github.com/teerapap/grunt-protractor-runner
インストールはいつも通りコマンド一発。
$ npm install grunt-protractor-runner --save-dev
Gruntfile
にタスクを追記します。
protractor: {
options: {
configFile: "node_modules/protractor/referenceConf.js",
keepAlive: true,
noColor: false,
args: {
}
},
sample: {
options: {
configFile: "e2e.conf.js",
args: {
browser: 'chrome'
}
}
},
},
// 中略
grunt.registerTask('e2e', function(){
grunt.task.run([
'clean:server',
'concurrent:test',
'autoprefixer',
'connect:test',
'protractor'
]);
});
protractorの設定ファイルは以下のとおりです。
exports.config = {
allScriptsTimeout: 11000,
specs: [
'test/e2e/{,*/}*.js'
],
capabilities: {
// 'browserName': 'chrome'
// 'browserName': 'firefox'
// 'browserName': 'safari'
},
chromeOnly: false,
baseUrl: 'http://localhost:9001/',
framework: 'jasmine',
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};
※注意!
chromeOnly
をfalse
に設定している場合、WebDriverを使うことになりますが、その場合はJDKのインストールが必須になります。事前に入れておいてください。
あと、立ち上げるブラウザの種類ですが、Gruntfile
で指定する場合はbrowser
でe2e.conf.jsで指定する場合はbrowserName
になります。(キーが違ってハマった・・・)
指定箇所を明示するためにあえてコメントで残してあります。
さて、準備万端、意気揚々とコマンド実行です。
$ grunt e2e
が、ダメ!動かない・・・
どうやらprotractor
に同梱のwebdriver-manager
を走らせる必要がありそうです。いろいろ調べたのですが、どうもgrunt-protractor-runner
は自身のnode_modules
ディレクトリ配下のprotractor
を使っているらしく、そちらのwebdriver-manager
を実行してねって書いてありました。で、タスクは?うん、ないですね・・・えぇ〜!?
ちなみに変更履歴に以下のような記述がありました。
Temporarily remove automatically download/update webdriver-manager because it fails in some environment such as Windows (#41)
諦めんなよ!
しかたがないので、手実行、と言いたいところですが、これだと環境構築でめんどくさいことになりそうなので別の方法を探してみますが・・・見つからないです・・・
※途中grunt-protractor-webdriver
というプラグインが見つかったのですが、これは違った・・・
幸い、protractor
が個別にインストールされている場合はそちらを参照してくれるような作りになっているぽいので、何とかそちらで管理をしてみようと思います。
※グローバルインストールも考えたのですが、環境が個別にまとまっている感じが良かったので・・・
少々強引ではありますがgrunt-shell
を使ってテスト前にwebdriver-manager
のupdateを走らせることにします。
まずは必要なプラグインをインストール。
$ npm install protractor --save-dev
$ npm install grunt-shell --save-dev
続いてコマンドを登録します。
shell: {
webdriver_update: {
command: 'node_modules/.bin/webdriver-manager update'
}
}
最後にタスクを修正します。
grunt.registerTask('e2e', function(){
grunt.task.run([
'clean:server',
'concurrent:test',
'autoprefixer',
'connect:test',
'shell:webdriver_update',
'protractor'
]);
});
いざ実行。
$ grunt e2e
できた!けどイケてねぇ〜!
とりあえず作者の今後の活動に期待しときます・・・
※ここまで書いてnpm
のpostinstallもアリかなぁとちょっと思いました・・・
実際のテストを書いてみる。
実際にサンプルに対してE2Eテストを書いてみました。
'use strict';
describe('sample e2e tests.', function() {
var searchText,sortKey,sortRevers,itemsPanel;
beforeEach(function() {
browser.get('/index.html');
searchText = $('#searchText');
sortKey = $('#sortKey');
sortRevers = $('#sortOrder');
itemsPanel = $('.items-panel');
});
it('default search setting.', function(){
expect(searchText.getText()).toBe('');
expect(sortKey.getAttribute('value')).toBe('id');
expect(sortRevers.isSelected()).toBeFalsy();
itemsPanel.$$('.item-title').then(function(items){
expect(items.length).toBe(2);
expect(items[0].getText()).toBe('HTML5');
expect(items[1].getText()).toBe('AngularJS');
});
});
it('search "ang" result.', function(){
searchText.sendKeys('ang');
itemsPanel.$$('.item-title').then(function(items){
expect(items.length).toBe(1);
expect(items[0].getText()).toBe('AngularJS');
});
});
it('search sort by title.', function(){
$('#sortKey > option[value=title]').click();
itemsPanel.$$('.item-title').then(function(items){
expect(items.length).toBe(2);
expect(items[0].getText()).toBe('AngularJS');
expect(items[1].getText()).toBe('HTML5');
});
});
it('search sort desc.', function(){
sortRevers.click();
itemsPanel.$$('.item-title').then(function(items){
expect(items.length).toBe(2);
expect(items[0].getText()).toBe('AngularJS');
expect(items[1].getText()).toBe('HTML5');
});
});
});
今回はあえてAngularJSに特化しない形でテストケースを書いています。
具体的には、DOMの検証にはCSSセレクターのみを利用し、その要素に対してイベントを発行している、といった具合です。
基本的な書き方は恐らくJasmineに準拠するのですが、protractor用に幾つか便利なメソッドが提供されています。詳しい使い方は、公式のマニュアルが詳しいのでそちらを参照にすると良いです。
https://github.com/angular/protractor/blob/master/docs/api.md
以下、トピックを抜粋して紹介します。
ElementFinder.prototype.$
jQuery
使えるのかな?と思いきや、実は全く違います。ElementFinder
というオブジェクトをCSSセレクターで返すエイリアスの関数です。
ElementFinder
は単一のDOMオブジェクトというイメージです。複数のオブジェクトを同一視することはなく、複数オブジェクトを取り扱う場合はElementArrayFinder
というオブジェクトを使います。(ちょっと不便な気がしますね・・・今後改善されるのでしょうか?)
余程のことがなければjQuery
ライクに書けるとは思いますが、より厳密に書いてあげたほうが良さそうですね。
ElementFinder.prototype.$$
こちらはElementArrayFinder
を返します。上述の通り、こちらは複数のオブジェクトというイメージですね。
複数のDOMが帰ってきますので、実際の値を取得するget
やらcount
やらが用意されています。get
で帰ってくるのはElementFinder
になります。
ここで非常に注意したいのが、ElementFinder
やElementArrayFinder
から値を取得するメソッドとして提供されているgetText
やcount
といったメソッドの戻り値はPromise
になっているということです。
Promise
の詳細については割愛しますが、ざっくり言うと、「非同期通信の結果を取得するためのエンドポイント」ということです。(Java
でいうところのFutureみたいなものでしょうか)
Promise
を通じて取得される値は非同期で順序が保証されていませんので、今回のテストのようにthen
などを使って、コールバック経由でないと実際の値は取得できません。したがって、以下の様な書き方はできません。
if ($('#hogehoge').getText() == 'inputValue') {
// logic
}
代わりに以下のように書きます。
$('#hogehoge').getText().then(function(text){
if (text == 'inputValue') {
// logic
}
});
もうこうなってくるとわけわからなくなります。
コールバックの呼び出しより先に後続ロジックが動いてしまいますので、処理をシリアルに実行しようとするとthen
で返ってくるPromise
を更にチェインして処理を書いて・・・なんて事になります。実際に、セレクトボックスの値を取得しようとループで回して選択値を取得して検証、なんてテストを書こうとしてえらく大変でした。なので、DOMの取得は極力CSSセレクタで一発で取れるよう記述しないと話になりませんね・・・
「値の比較ができないってことはexpect
はちゃんと動くの?」と思ったのですが、こちらは正常に動作しているようです。中身までは確認していませんが、expect
による検証は内部的にPromise
を解釈して値検証をしていると思われます。expect
自体はそれぞれが独立した検証であり、検証順序の厳密性は問われないからでしょう。なるほど、頭良いですね。
恐らく、WebDriverとの値のやり取りはプロセス間通信が発生するため、即時に値を取得してロジックに反映させることができないのではないかと推測しています。まぁこればかりは仕方ないですね・・・
webdriver.WebElement.prototype.click
DOM要素をクリックする処理です。「テストコードではElementFinder
のclick
を呼んでいるけど大丈夫?」と思ったのですが、こちらは大丈夫なようです。
The ElementFinder can be treated as a WebElement for most purposes, in particular, you may perform actions (i.e. click, getText) on them as you would a WebElement.
webdriver.WebElement.prototype.sendKeys
こちらはキー入力を行うものです。
基本的な使い方は上述のclick
と同じです。
まとめ
今回はあえてAngularJSに依存しないE2Eテストを書いてみましたが、AngularJSを使う場合はby.model
などAnuglarJSの変数に直接アクセスできるようなコードも書けるみたいですので、よりテストはし易いかなぁと感じました。
しかしながら、今回のようにCSSセレクターを利用したDOMの取得と、クリックとキー入力というイベントの発火を行うだけであれば、非常に汎用的ですし、AngularJSを使っていないアプリに対してもテストが書けます。protractor
はこの辺のサポート実装を行っていますのでテストのコードの記述量を抑える意味でも導入するメリットは大いにあります。
また、今回は試していませんが、distで本番用にコンパイルしたコードをベースにしたテストなどもできそうです。変なミスをなくす上では重要なアプローチですよね。
テスト自体が自動化できたということでJenkins
などのCIツールと組み合わせたりすると、テストが捗りそうですので、機会があればその辺も調査していきたいと思います。
ただ、実際のアプリとなると、連携させるAPIの準備であったり、APIが返すデータをDBなどに初期投入するなど、まだまだやることは多そうです。今後の課題ですね。
とりあえず、開発するにあたって必要な環境構築はひと通りできたかなぁと思います。これでやっとAngularJSを使ったアプリ開発に入れますね。(長かった・・・)
今後は、実際のAnuglarJSを触りながら、気になったトピックなどを書いていきたいですね。(まぁあくまで備忘録なのであしからず)
GruntのbowerInstallタスクで差し込まれるJavaScriptファイルの順番
bowerInstallって便利ですよね
grunt-bower-install
を使うと、bower.json
で定義されたモジュールを自動的にHTMLファイルに反映させてくれます。すごく便利ですね。
で、ちょっと気になったのが、HTMLファイルに内容が反映される時に記述される順番です。ライブラリによっては依存関係があったりして、読み込み順序を間違えると正しく動かない可能性ってありますよね。そこで、ちょっと調べてみました。
https://github.com/stephenplusplus/grunt-wiredep
grunt-bower-install
の依存先のwiredep
の中(detect-dependencies.js)で優先度を決定しているっぽいです。(ソースを見た感じ)
優先度は、お互いがお互いの依存関係として登録されているかどうかを基準に判断されているようです。つまり依存されているライブラリは優先度が高くなり、上位に記述され、先に読み込まれます。なるほど、流石にインテリジェンスですね。
さらに、いくつかのライブラリ(jQueryなど)の優先度はすごく高いと決めて、それらを上位に持ってきているようですね。
それ以外については、bower.json
の記述順序に読み込まれるようです。
ちょっと実験してみました。
"angular-sanitize": "1.2.15",
"angular-resource": "1.2.15",
"angular": "1.2.15",
だと
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
になります。
"angular-resource": "1.2.15",
"angular-sanitize": "1.2.15",
"angular": "1.2.15",
だと
<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>
になります。
なるほど、普通に使う分には困ることはなさそうです。ちなみにgrunt bowerInstall
を実行するたびにHTMLファイルは強制的に書き換えられてしまいますので、ちょっとだけHTMLで順序を書き換えてもすぐに上書きされてしまいます。bower.json
を書き換えましょう。