Bower入門(応用編)

Bower入門(応用編)

さて、応用編を書いていきます。
基礎編ではBowerのインストールとライブラリ管理する上での基本的なコマンドを紹介しました。
応用編ではBowerのライブラリを管理する上で利用するべきツールやライブラリを公開する上で心がけるべきことについて書いていきます。

少し長いのでサマリ

  • Bowerを管理する上で利用すると良いツール:grunt-bower-taskがオススメです
  • ライブラリを公開する上で心がけること、その1:mainとignoreをちゃんと書きましょう
  • ライブラリを公開する上で心がけること、その2:ちゃんとgit tagを使ってバージョン管理しましょう

Bowerからインストールしたライブラリを利用する場合

前回の基礎編で少し書きましたが、おさらいすると、Bowerはあくまでパッケージマネージャなので、インストールしてもフォルダ構造までは変えてくれません。

なのでフォルダの構造を意識して、

<script src="/components/jquery/jquery.js" ></script> // jqueryの直下にjquery.jsがある
<script src="/components/impress.js/js/impress.js"></script> // impress.jsの下にjsフォルダがあって、更にその下に目的のimpress.jsがある

みたいに記述しないといけません。

少し前に Uzulla さんもブログ にこんな内容を書いてました。

じゃあどうやって実際にDLしたファイルを使うのか

これはクーールなやり方は特になくて、普通にそのはいったファイルを

  <script src="/bower_components/jquery/index.js"></script>

しましょうって書いてある、えっ、オシャレじゃないよ!っておもったけど、
まあ統一的なものがないし、どうしようもないのかな。
(オシャレなやり方あるならだれか教えて下さい…)

オシャレなやり方かどうかは分かりませんが、RequireJSを使ったり、 Grunt を併用することで、ある程度フォルダ構造を意識しないで利用することが可能です。
僕はGruntが好きなのでgrunt-bower-taskというタスクを併用すれば、レイアウトマネージャーとしての機能が活用ができるのでお勧めです。

grunt-bower-taskを使う

grunt-bower-taskを使うと、レイアウトを変えることが可能です。

利用するにはまず、gruntを入れて、その後にgrunt-bower-taskをインストールしてください。

$ npm install -g grunt-cli
$ npm install grunt --save-dev
$ npm install grunt-bower-task --save-dev // grunt-bower-task のインストール

終わったら、Gruntfile.jsを以下のように書きましょう。

module.exports = function (grunt) {
  grunt.initConfig({
    bower: {
      install: {
        options: {
          targetDir: './lib', //ライブラリの配置先のディレクトリ
          layout: 'byType', // レイアウト、説明は後述します
          install: true, //grunt実行時にbower installを実行するかどうか
          verbose: false, // ログの詳細を出すかどうか
          cleanTargetDir: true, // targetDirを削除するかどうか
          cleanBowerDir: false // bowerのcomponentsディレクトリを削除するかどうか
        }
      }
    },
  });
  grunt.loadNpmTasks('grunt-bower-task');
};

実行する時は、

$ grunt bower:install

とすると、 bower.json の main プロパティに記されたファイルだけを targetDir以下に配置してくれます。

例えば jQueryの bower.json を読むと、mainプロパティに jquery.js と記述されているのがわかります。
grunt-bower-taskにより、jquery.jsがlib以下に配置されます。

lib
├── jquery
│   └── jquery.js

前回の基礎編で main ignore がライブラリ登録時に重要だと言った理由は grunt-bower-task等のbower拡張ツールがこれらの情報を利用して配置するからです。

ただし、mainが書かれていないライブラリはまだまだたくさんあります。 そうなると grunt-bower-task でも何を配置して良いか分からず、結果全てを配置することになってしまいます。


例えばunderscoreはmainが記述されていないので、入れるとこんな感じになります。。。

lib
├── jquery
│   └── jquery.js
└── underscore // 不要なファイルまで取得される
    ├── CNAME
    ├── CONTRIBUTING.md
    ├── LICENSE
    ├── README.md
    ├── Rakefile
    ├── bower.json
    ├── docs
    │   ├── docco.css
    │   ├── favicon.ico
    │   ├── images
    │   │   ├── background.png
    │   │   └── underscore.png
    │   └── underscore.html
...

なので、 bowerのライブラリを登録する際には、必ずmainとignoreを書くようにしましょう。

これはgrunt-bower-task以外のツールでも同じです。bower-installerやgrunt-bowerful等、色んなbower拡張ツールがありますが全てbowerのmainを読み込みます。

Bowerの仕様にもmainはbowerが直接利用するものではなく、ビルドツールから利用されるものである、と明記されています。

どんなツールが使われるか分かりませんが、作法として書くようにしたほうが良いと思います。

mainがないライブラリの扱い方

先程述べた通り、mainに何も記述されていないライブラリはデフォルトのままだとgithubに登録されているフォルダ構成でダウンロードされてしまいます。ただし、grunt-bower-taskでは、bower.jsonに記述を加える事で、これを解決できます。

■ bower.json

{
  "name": "My application",
  "version": "0.0.1",
  "main": index.js
  "ignore": [
    "**/.*",
    "node_modules",
    "components"
  ],
  "dependencies": {
    "jquery": "~2.0.0",
    "underscore": "~1.4.4"
  },
  "exportsOverride": { //ここが肝。exportsOverrideを加える事でタイプを定義し、レイアウトを変えられる。
    "underscore": { //キーがtypeとして扱われる、
      "js": "**/*.js" // css:"**/*.css"とかimg:"**/*.png"とかも併記できる。
    }
  }
}

exportsOverrideを記述した上で Gruntfile.jsの layout オプションに以下のように設定します。

■ Gruntfile.js

module.exports = function (grunt) {
  grunt.initConfig({
    bower: {
      install: {
        options: {
          targetDir: './lib', 
          layout: 'byType', // byTypeかbyComponentか任意のレイアウト関数を登録できます。
          install: true,
          verbose: false,
          cleanTargetDir: true,
          cleanBowerDir: false
        }
      }
    },
  });
  grunt.loadNpmTasks('grunt-bower-task');
};

この状態で

$ grunt bower:install

と実行すると、以下のようにmainが設定されていないライブラリでもjsだけをフォルダに集約することができます。

lib
├── jquery
│   └── jquery.js
└── js
    └── underscore
        ├── arrays.js
        ├── chaining.js
        ├── collections.js
        ├── functions.js
        ├── index.js
        ├── jquery.js
        ├── jslitmus.js
        ├── objects.js
        ├── qunit.js
        ├── runner.js
        ├── speed.js
        ├── underscore-min.js
        ├── underscore.js
        └── utility.js

byComponentにすると以下のようになります。

├── jquery
│   └── jquery.js
└── underscore
    └── js
        ├── arrays.js
        ├── chaining.js
        ├── collections.js
        ├── functions.js
        ├── index.js
        ├── jquery.js
        ├── jslitmus.js
        ├── objects.js
        ├── qunit.js
        ├── runner.js
        ├── speed.js
        ├── underscore-min.js
        ├── underscore.js
        └── utility.js

せっかくなのでjqueryにも同じ定義を加えましょう。

■bower.jsonを以下のように書き換えましょう。

{
  "name": "My application",
  "version": "0.0.1",
  "main": index.js
  "ignore": [
    "**/.*",
    "node_modules",
    "components"
  ],
  "dependencies": {
    "jquery": "~2.0.0",
    "underscore": "~1.4.4"
  },
  "exportsOverride": { //ここが肝。exportsOverrideを加える事でタイプを定義し、レイアウトを変えられる。
    "underscore": {
      "js": "**/*.js"
    },
    "jquery": { // jqueryの定義を追加、この定義がなければmainが読み込まれる
      "js": "**/*.js"
    }
  }
}

再度gruntを実行すると、以下の様なフォルダ構造になります。

lib
└── js
    ├── jquery
    │   ├── jquery-migrate.js
    │   ├── jquery-migrate.min.js
    │   ├── jquery.js
    │   └── jquery.min.js
    └── underscore
        ├── arrays.js
        ├── chaining.js
        ├── collections.js
        ├── functions.js
        ├── index.js
        ├── jquery.js
        ├── jslitmus.js
        ├── objects.js
        ├── qunit.js
        ├── runner.js
        ├── speed.js
        ├── underscore-min.js
        ├── underscore.js
        └── utility.js

指定する時はこうなりますね、

<script src="/lib/js/jquery/jquery.js" ></script> // lib/js/<ライブラリ名>/<ファイル名>と良い感じにまとめられる
<script src="/lib/js/underscore/underscore.js"></script>

良い感じにjsファイルがまとめられて階層がフラットになったのでフォルダ構造を意識する必要が減ったと思います。

覚えてほしいこと
  • bowerのライブラリを登録する場合は mainとignoreを記述してください
  • grunt-bower-taskを使えばmainを取得したり、レイアウトを変える事ができます。
  • grunt-bower-taskを併用すればフォルダ構造がフラットになるので構造を意識する必要が若干減ります。

ついでなのでUzullaさんのブログに書かれていたハマりどころ全部答えてみる。

どうでもいいけど

  "dependencies": {
    "jquery": "~2.0.0",
    "blockui": "git://github.com/malsup/blockui", ←ここの最後のカンマでエラーになる
  }

このIE的挙動がイラッ♪とします。

なんかJSONのパーサーが違うんですかね(node初心者)

わりと(.bowerrcとか)設定ファイルの文法まちがってると、エラーなく死ぬ事も多いので、辛いですね

JSONのパーサーについて

まず、JSONのパーサーはどれも同じだと思います。手元でChromeSafariでやった感じだとケツカンマがあるとSyntax Errorになります。

Javascriptオブジェクトはケツカンマを許すものもありますが、JSONは仕様としてケツカンマを許しません。

JavaScriptとJSONとECMAScript - gomoh
JSONのRFC

なので、この問題はbower.jsonという名前を付けてしまった時点で発生する問題ですね。
ちなみにnpmもpackage.jsonなので同じ問題が発生します。

dependenciesを編集するだけなら直接bower.jsonを編集するのではなく、以下のようにsaveオプションを使いましょう。

$ bower install xxxx --save
$ bower install xxxx --save-dev

ただbowerrcはJSONである必要があったのか疑問ですね。npmrcはJSONじゃないので(確か)。

文法間違いでエラーを吐かないこと

これは本当に致命的だと思ったので、Issue(+ PullReq)を投げたら自分の修正方法じゃないエレガントな方法で解決してくれたので、最新版ではエラーをちゃんと吐くようになりました。なので、いますぐ

$ npm install -g bower

で、最新にすれば直ります。

bowerはコミットが盛んなので、常に最新にするように心がけたほうが良いかもしれません。




最後にbower のライブラリ公開方法

bower register
$ bower register <ライブラリ名> <github URL(git://から始まるReadonlyのURLにしましょう。)>

例:

$ bower register tmlib.js git://github.com/phi1618/tmlib.js.git


で登録できます、くどいようですが、登録の際には必ずmainとignoreを付けるようにしましょう。

後、git tagでちゃんとバージョンを設定する必要があります。

その際はsemverと呼ばれるバージョン管理体系に沿ってtagを設定してください。

gitのtagとbower.jsonのバージョンの間でミスマッチがあると下記のようなwarningが出ます。

mismatch The version specified in the bower.json of package <package_name> matches the tag
mismatch You should report this problem to the package author

このwarningが出たら、大抵の場合はbower.jsonのバージョン番号が古い時だと思うのでライブラリ管理者に伝えて適切なgit tagを打ってもらうかbower.jsonを書き換えてpullreqしましょう。

bower で登録したライブラリを解除したい時

特にコマンドはありません、

Unregister package requests · Issue #120 · bower/bower · GitHub

上記のissueに登録すると解除してくれるようです。

まとめ

  • Bowerを使うとフロントエンドのライブラリを管理することができます。
  • Bower自体の機能はかなりミニマルなのでgrunt-bower-task等のBower拡張ツールと併用したほうが実際に使う上では良いでしょう。
  • Bowerは常に最新に。
  • 公開するならmainやignoreはちゃんと書きましょう。
  • 公開するならgit tagでバージョン管理しましょう。

と長くなりましたが、応用編終わりです。疑問があれば答えます!