grunt-bower-installとgrunt-useminが出力するファイルパスを調整


現在あるWebアプリを作っているのですが、そこではyeoman-angular-fullstackを使っています。これはgrunt-bower-installgrunt-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)だとコケるとか様々な試練が待っていました・・・。

次からもっと早く解決したいです。