npm run script, npm shrinkwrapでnpm, bowerのバージョン管理をなんとかしたい

プロジェクトで、npmとBowerを使ってフロントエンド開発のパッケージ管理をしています。

最近、フロント開発者・普段フロント開発をしないエンジニアでビルド環境を揃えるために、「[npm-run-script](https://www.npmjs.org/doc/cli/npm-run-script.html)」 を使うようにしました。「[npm shrinkwrap](https://www.npmjs.org/doc/cli/npm-shrinkwrap.html)」については検討中。

色々苦戦しながら対応したので、ビビりながらまとめます。

[Frontrend Advent Calendar 2014 - Qiita](http://qiita.com/advent-calendar/2014/frontrend) 9日目の記事です。

## 発端
### Bowerで管理しているはずのjsライブラリがGitにコミットされていた

どのタイミングからかは分からないのですが、インストール時にgit上でライブラリの更新分の差分が出るようになっていて発覚しました。
これはプロダクトが、まだ開発環境でテストされていないバージョンのライブラリを使って実行されていたことになります。（幸い影響はなかったです）

Bowerで管理しているならgitにコミットする必要はないので、git管理下から削除。


### デフォルトのバージョン指定が緩い

そもそも、gitにコミットされていても、バージョンを固定していれば問題なかったのですが、

```
$ npm install -g package_name --save-dev
```

などでインストールすると、package.jsonに自動でインストール時のバージョンを記名してくれます。
しかしデフォルトでは

```json:package.json
{
  "devDependencies": {
    "package_A": "~1.2.3",
    "package_B": "^1.2.3",
    ...
  }
}
```

のような、指定の緩いバージョン管理になります。
npmではこういった指定の場合、下記のように、時にはマイナーバージョンの変更も許容してしまいます。

* ```~1.2.3``` ： ```1.2.3```以上```1.3.0```未満
* ```~1.2``` ： ```1.2.0```以上```1.3.0 ```未満（パッチレベルの変動）
* ```~1``` ： ```1```以上```2.0.0```未満（マイナーバージョンの変動）
* ```^1.2.3``` ： ```1.2.3```以上```2.0.0```未満（マイナーバージョンの変動）
* ```^1.2.3-beta.2``` ： ```1.2.3-beta.2```以上```2.0.0```未満

参考：[npm - docs - semver](https://www.npmjs.org/doc/misc/semver.html)

ライブラリによっては、パッチバージョンでも上げると正しく動作しなくなることもあり、細かく検証する余裕がないなら固定してしまうのが一番よさそうだな〜と思い、本番環境で検証済みの現段階のバージョンを正とし、バージョンを全て固定しました。


### 開発者ごとにバージョンがバラバラ

弊社の体制的に、開発者の移動が早く、プロジェクト立ち上げ当時の開発者が何年も同じプロジェクトに関わるということは極めて稀です。

（私がそうだったように、）パッケージ管理に疎い開発者だけになってしまった場合、上記のような「いつの間にかバージョンが変わっていた」「私のPCでだけエラーが出るけどいったいなんで？」といったことが起きる度に何度も開発を止めざるを得なくなってしまいます。

新しく開発者がジョインしても動かすまでにも一苦労、これはとても非効率です。

（特に普段フロント開発に関わらないサーバサイドエンジニアのPCでは、フロントエンドでしか使わないBowerのバージョンが著しく古いままだったりしました。）


## npmで開発者によらないバージョン管理をがんばる

フロントエンドのフレームワーク、ライブラリは非常にアップデートが激しいため、最低限の知識で、全員の開発環境・ビルド環境を揃えられるよう整備する必要がありました。

そこで色々調べてみた結果、

* 「[npm で依存もタスクも一元化する](http://qiita.com/Jxck_/items/efaff21b977ddc782971)」
* 「[Front-end with Node.js - Node学園祭 2014](http://havelog.ayumusato.com/misc/e633-nodefest_2014.html)」
* [npmのあまり知られてない機能 10選](http://yosuke-furukawa.hatenablog.com/entry/2014/06/10/100410)
* [npmやbowerライブラリのバージョン管理](http://blog.koba04.com/post/2014/03/03/npm-and-bower-semantic-version/)

をふまえ、npmの「npm-run-script」を用いたパッケージ管理を自プロジェクトに導入しました。

後述する、```npm shrinkwrap```は導入しようかどうしようかなぁ。というところ。

## npm-run-scriptでプロジェクト開発環境を作る

npm-run-scriptは、package.jsonに書かれたscriptを、「コマンド」として実行できる機能です。

```zsh:zsh
$ npm run-script -h
npm run-script [<pkg>] <command>
```

scripts経由で実行すると、npmでプロジェクトディレクトリにinstallしたパッケージに、自動でパスを通してくれます。
（node_modules/以下のコマンドを探索して実行してくれる）

開発者があらかじめBowerをグローバルにインストールしていても、node_modules/以下を参照してくれるため、開発者によらず同じパッケージを使うことが可能になります。


### サンプル

package.jsonとbower.jsonがあり、gruntタスクを設定済みの例ですが、

```json:package.json（一部）
{
  // 略
  "scripts": {
    "start": "bower install",
    "grunt": "grunt build"
  },
  "devDependencies": {
    "bower": "1.3.12",
    "grunt": "0.4.5",
    "grunt-cli": "0.1.13",
    // 略
  }
}
```

とすれば、このプロジェクトのソースを手元に落としてきて

```
$ npm install
$ npm run start  // runは省略可能。 $ node_modules/bower/bin/bower install と同じ。
$ npm run grunt  // ローカルのgruntを使って grunt build を実行
```

の３行で、

フロント開発の完成品が手に入るし、
フロント開発者はすぐに作業に取りかかることができます。

たとえPCに元々bowerのv1.3.5が入っていても、package.jsonに```"bower": "1.3.12"```と書いていればそちらを使うことができます。

```zsh:version確認
$ bower -v  // globalのbowerバージョン確認
1.3.5

$ bower install  // globalのbowerが使われる


$ node_modules/bower/bin/bower -v  // localのbowerバージョン確認
1.3.12

$ node_modules/bower/bin/bower install  // localのbowerが使われる
$ npm start  // localのbowerが使われる
```

（ただし、sudoでinstallしていた場合はそちらが優先されてしまうようです）


### 登録したタスクの確認

タスクを覚えておく必要はなく、 ``` $ npm run``` で、登録したタスクを一覧で確認できます。

```zsh:タスクの確認と実行
$ npm run  // npmに登録したスクリプト一覧表示
Available scripts in the SampleApp package:
  start
    bower install
  grunt
    grunt build

$ npm start  // ローカルのbowerでbower install実行
> SampleApp@0.0.0 start
> bower install
```

### 登録するタスクについて

scriptsにつらつらと書きすぎると逆に管理しきれなくなってしまうので、必要最低限にしたい。

[上記の記事](http://qiita.com/Jxck_/items/efaff21b977ddc782971)でもとても丁寧に紹介されてありましたが、私のプロジェクトではタスクが多すぎるので、gruntの開発時のタスクまで追加してしまうのは、適切ではなさそうでした。（そもそもタスクを整理せよという話ですが…）
記事で @Jxckさんが仰られているように、

> また、ガッツり開発する場合は、そこで使われている gulp や grunt-cli を -g で入れて、 scripts に書かれているのと同じタスクを実行すれば良い。
...
> そのユーザの関わり方、そのプロダクトについての知識量に応じて、適切な方法が選べるのがこの方針の良いところだと思っている。

というのがなるほどなぁと思い取り入れました。

私のプロジェクトでは、「フロント開発者」「非フロント開発者」の視点で分けて、
「**gruntとかbowerとか分からないけどフロントエンドの完成品が必要**」な非フロント開発者向けの簡易ツールとして ```npm-run-script``` を使うのが良いのではないかな、と今のところ思っています。
npm-run-scriptで得られるのは、**いますぐアプリケーションを動かしたい人にとって使える環境とビルド後の製品**。という位置づけ。


フロント開発者が開発時に必要な、複雑なタスクを実行するためには、やはりBowerやGruntを開発者自身で適宜インストールする必要があります。開発時のタスクをpackage.jsonに登録するのは余計な混乱を生みそうなので避けたい。

その代わり、詳細にpackage.jsonに記載していることで、フロントエンジニアもpackage.jsonの見方と使い方さえ分かれば一通り揃えることができます。


上記の例では、フロント開発者も非フロント開発者も

```zsh
$ npm install
$ npm run // タスクの確認
Available scripts in the SampleApp package:
  start
    bower install
  grunt
    grunt build
$ npm run start  // startの場合はrunを省略可能
$ npm run grunt
```

という４コマンドで、同等の製品を得ることができます。
Bowerに日頃慣れ親しんでいない開発者でも、別プロジェクトからきたバージョン環境の異なるフロント開発者でも。


さらに、フロント開発に関わる開発者向けには、

* node v0.10.x
* npm v1.4.1x
* ruby 1.8.7
* grunt-cli v0.1.13
* Compass 0.12.4
* Sass 3.2.x

上記のようにglobalでインストールしておく必要があるコマンドのバージョンと、導入手順をWikiに記載しました。

```zsh:導入手順
$ npm install
$ npm start  // ⇒ プロジェクト指定のバージョンでbower install

// 開発時：例。詳しくはGruntfileを参照
$ grunt build         // globalのgruntを使うので、バージョンに注意
$ grunt watch         // watchに監視したいタスク
$ grunt connect:server watch:app  // 簡易webサーバを起動しながら修正を監視
```

※ npm 2.0.0から```$ npm run```に引数を渡せるようになったそうなので、scriptsに ```"grunt": "grunt"``` と追加しておけば

```zsh
$ npm run grunt build  // buildを引数として、gruntのタスクを指定して実行
```

といったことができるようになりました。そうするともっとグローバル環境に依存せずにパッケージ管理ができるのかなぁと思います。すてき。


## npm shrinkwrapで依存ライブラリまでバージョンを固定する

上記のやり方だと、依存ライブラリのバージョンまでは指定することができません。
ライブラリが依存しているライブラリが「~」「^」をつかったバージョン指定をしていれば（多い）、当然バージョンに変動があり、厳密には対応しきれていません。

そこで、依存ライブラリまでを含めてバージョンを固定することができる[npm shrinkwrap](https://www.npmjs.org/doc/cli/npm-shrinkwrap.html)を使うのが一番確実そうです。（まだ導入できていないのですが…）

```npm shrinkwrap```は、依存ライブラリも含めてバージョンを固定して「npm-shrinkwrap.json」を出力します。
それがある環境で```$ npm install```をすると、npm-shrinkwrap.jsonに記載されたバージョンをインストールしてくれます。そのため、インストールするタイミングや環境によらず、開発者全員で同じバージョンのライブラリをインストールすることができます。
（問題がおきても、開発者ごとの問題である、と原因を特定しやすくなります。）

```zsh:導入手順
$ npm install shrinkwrap
shrinkwrap@0.4.0 node_modules/shrinkwrap
├── debug@0.8.1
├── eventemitter3@0.1.6
├── fusing@0.2.3 (predefine@0.1.2)
└── npm-registry@0.1.13 (extract-github@0.0.5, semver@2.2.1, licenses@0.0.19, mana@0.1.37)

$ npm prune  // pruneせずに実行すると[npm ERR! extraneous: shrinkwrap@0.4.0]というエラーが発生することがある

$ npm shrinkwrap
...
npm WARN shrinkwrap Excluding devDependency: grunt-webfont
npm WARN shrinkwrap Excluding devDependency: testem
wrote npm-shrinkwrap.json
```

デフォルトではdevDependenciesに書かれたパッケージはスルーされるので、含める場合は

```zsh
$ npm shrinkwrap --dev
```

で実行。
うちだと24167行のjsonファイルが出力されました。結構膨大…。

### Bowerはまだ

Bowerのshrinkwrapは実装途中のようで、[今も活発に議論](https://github.com/bower/bower/pull/1592)されているようです。
```npm-shrinkwrap.json```に対して、```bower.lock```というファイル名を予定している様子（Gemfile.lockみたい）。期待です…！



## まとめ

* npm-run-scriptで非フロント開発者にもフロント開発者同等のビルド製品を提供できる
* npm-shrinkwrapで依存ライブラリも含めたバージョン固定ができそう
* Bowerのshrinkwrapも期待

### 雑感

差分に気づいて対応を始めたら他の人のPCでエラーが起きて原因を探してなんだかんだしてたら凄く手間取ってしまいました。多分前任の先輩だったら一瞬だったはず。
それくらいめんどくさい、という割に、プロジェクトのノウハウとしてあまり積まれていかないものなんだなと思いました。
こういう、開発にすぐに結びつかない部分をなんとかうまくできればなぁと思っています。


### 参考
大変参考にさせていただきました、ありがとうございます。

* [npm で依存もタスクも一元化する](http://qiita.com/Jxck_/items/efaff21b977ddc782971)
* [Front-end with Node.js - Node学園祭 2014](http://havelog.ayumusato.com/misc/e633-nodefest_2014.html)
* [npmのあまり知られてない機能 10選](http://yosuke-furukawa.hatenablog.com/entry/2014/06/10/100410)
* [npmやbowerライブラリのバージョン管理](http://blog.koba04.com/post/2014/03/03/npm-and-bower-semantic-version/)