grunt-bower-installとgrunt-useminが出力するファイルパスを調整
現在あるWebアプリを作っているのですが、そこではyeoman-angular-fullstackを使っています。これはgrunt-bower-installとgrunt-useminを使うのですが、Angularのhtml5modeで少し悩んだのでメモしておきます。
前提知識
- Yeoman Grunt, Bower, HTML, CSSなどのテンプレートを作成してくれるツール。Gruntの設定ファイル作成が面倒だとかいうケースに使える。
- yeoman-angular-fullstack AngularJSやExpressなどのMEAN環境を作ってくれるYeomanのジェネレータ。
- grunt-bower-install Bowerによってインストールした依存関係のあるパッケージへのリンクをHTMLに自動で挿入してくれるツール。HTMLファイルの特定部分にscript src=""を自動で挿入してくれる。
- grunt-usemin MinifyなどでJSファイルやCSSファイルが結合されたり、リネームされたりするけど、その新しい名前に合わせてHTMLファイルを変更してくれるツール。HTML内のscript src=""などの部分を書き換えてくれる。
経緯
現在とあるWebアプリをAngularJSで作っているわけですが、Angularにはhtml5modeというものが存在します。Angularはデフォルトではhttp://localhost/#/menu/settingsというように#以下をパスとして解析して動作するようになっています。html5modeをonにするとhttp://localhost/menu/settingsというように自然なパスでアクセスできるようになります。
このModeは色々落とし穴があるのですが、今回はそのうちの1つの#の方式と比べてカレントディレクトリが変わる、という問題について扱います。例えば#の方式だと常にカレントは/、つまりルートですが、html5modeだと/だったり/menuだったり色々変わります。
AngularはSinglePageApplicationを作るものですので、index.htmlと複数のpartial(index.htmlの一部分)を使って開発することが多いと思います。
上記の状況でindex.htmlにscript src=“bower_components/jquery.js”などと相対パスで書いていると/でアクセスしたときは問題ないけど、/menu/settingsへアクセスしたときに問題が発生します。
(※作り方にもよりますが、Angularでhtml5modeだと/menu/settingsへアクセスしても/index.htmlを読み込みます。このときコンテキストは/menu/なので相対だと問題になります。)
今回はこれを解決した話です。
解決
まずはgrunt-bower-installの問題を解決します。grunt-bower-installを実行するとindex.htmlなどに自動的にscriptタグが挿入されます。
例えばこれが
<!-- bower:js -->
<!-- endbower -->
こうなります。
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
(その他沢山)
<!-- endbower -->
このままだとsrc指定が相対パスになっているので/menu/などにアクセスすると正しく動きません。
これはStackOverflowですぐに見つかりました。
Can I change what path gets rendered when using bower in an yeoman angular app?
なんかこの質問者もhtml5modeみたいですし、ドンピシャですね。
回答者に従ってgrunt-bower-installのversion 0.8.0を入れます。ここで1.4.0などの最新版を入れたのですが、失敗しました。素直に0.8.0を入れます。
npm uninstall grunt-bower-install --save-dev
npm install grunt-bower-install@0.8.0 --save-dev
—save-devはpackage.jsonのdevDependenciesも書き換えるという意味です。@はバージョン指定です。
次にGruntの設定を以下のような感じで書き換えます。
'bower-install': {
app: {
src: [
'<%= yeoman.app %>/views/index.html'
],
ignorePath: '<%= yeoman.app %>/',
fileTypes: {
html: {
replace: {
js: '<script src="/{{filePath}}"></script>',
css: '<link rel="stylesheet" href="/{{filePath}}" />'
}
}
}
}
},
replaceがキモで、{{filePath}}の前に/を入れることで絶対指定にしています。
この状態でbower-installするとscript src=“/bower_components…というように絶対指定になるので大丈夫です。
<!-- bower:js -->
<script src="/bower_components/jquery/dist/jquery.js"></script>
(※/で始まっている)
(その他沢山)
<!-- endbower -->
初めはgrunt-bower-installの1.4.0を入れてみたのですが、1.0.0以降ではなぜか../bower_componentsのようにパスが変わってしまうため諦めました。なんで../になるんだろう・・・誰か知ってたら教えてください。
次にgrunt-useminの問題を解決します。
yeoman-angular-fullstackはデフォルトで色々なGruntタスクを作成してくれますが、その中の一つにbuildがあります。grunt buildとするとdistフォルダにminifyなどをした本番環境用のリソースを作ってくれます。ここで複数のjsファイルは1つに結合されて、リネームされたりminifyされたり色々します。
例えばindex.htmlでこうだったものが
<!-- build:js(app) scripts/vendor.js -->
<!-- bower:js -->
<script src="/bower_components/jquery/dist/jquery.js"></script>
<script src="/bower_components/angular/angular.js"></script>
(その他色々)
<!-- endbower -->
<!-- endbuild -->
こうなります。
<script src="scripts/f351e3cf.vendor.js"></script>
かなり良い仕事してくれていますが、相対パスに戻っております・・・。
これは簡単に直すことができます。
以下の部分を
<!-- build:js(app) scripts/vendor.js -->
このようにして、scriptsの前に/を入れればOKです。
<!-- build:js(app) /scripts/vendor.js -->
実際にやってみると以下のようになります。
<script src="/scripts/f351e3cf.vendor.js"></script>
ツールの使い方が悪かったという単純な原因ですが、ここまで来る間にAngularのInjectionエラーだとか謎のSyntaxエラーだとか開発モード実行(grunt serve)だと上手く行くのに本番モード実行(grunt serve:dist)だとコケるとか様々な試練が待っていました・・・。
次からもっと早く解決したいです。