ちゃんと使い分けてる? dependenciesいろいろ。


npmはJavaScriptのパッケージマネージャです(今更感)。依存するライブラリは`package.json`に登録されていき、`npm install`だけで必要なライブラリが読み込まれます。と書くとシンプルな話ですが、実は結構奥が深いのです。そんなnpmのdependencies5種類、ちゃんと使い分けしてますか? 本稿では、順番に見ていきたいと思います。([npmのドキュメント](https://docs.npmjs.com/files/package.json#dependencies)も見てね)

- dependencies
- **dev**Dependencies
- **peer**Dependencies
- **optional**Dependencies
- **bundled**Dependencies

## dependencies

試しに新しいプロジェクトで、

```bash
$ npm install --save co
```

とすると、`package.json`に次のように登録されます。

```json
{
  "dependencies": {
    "co": "^4.6.0"
  }
}
```

この`package.json`さえプロジェクトに含まれていれば、別環境で使う場合も前述の通り`npm install`すれば、同じ環境を作れます。便利ですね。

ライブラリ自体は`node_modules`内にダウンロードされていますから、それも確認してみましょう。	`co`というディレクトリができているはずです。

### dependencies の dependencies の...

例として挙げた、[co](https://github.com/tj/co)はシンプルなライブラリなので、他のライブラリへの依存を持っていません。でも、こういうライブラリは稀で、大抵なんらかのライブラリに依存しています。今度は、比較のために[express](http://expressjs.com/)を入れてみます。

```bash
$ npm install --save express
```

すると、あらためて`node_modules`内を確認すると、新たに40ディレクトリが作成されています...!

![1462772221-06B0C38D-80BF-47FC-A091-8811F3E332A5.png](https://qiita-image-store.s3.amazonaws.com/0/1858/5e11b403-8e22-4d02-796b-162e080ee9f7.png "1462772221-06B0C38D-80BF-47FC-A091-8811F3E332A5.png")

どういうことでしょうか? これは、expressが **依存しているライブラリも自動的にインストールしている** からです。ほうほう。でもちょっと待って! [expressのpackage.json](https://github.com/expressjs/express/blob/4.13.4/package.json#L28-L54)を見ると25個しか書かれていませんよ? express自身を足しても14個足りない計算になります。

```json
{
  "dependencies": {
    "accepts": "~1.2.12",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.1",
    "content-type": "~1.0.1",
    "cookie": "0.1.5",
    ...(そのほか合わせて25個の依存ライブラリ)
  }
}
```

答えは簡単です。expressが依存しているライブラリも、別のライブラリに依存しています。例えば`accepts`は[2つのライブラリに依存](https://github.com/jshttp/accepts/blob/1.3.3/package.json#L11-L14)しています。つまり、ライブラリのライブラリの...依存するすべてのライブラリが読み込まれて「40」という数字になったわけです。

```json
{
  "dependencies": {
    "mime-types": "~2.1.11",
    "negotiator": "0.6.1"
  }
}

```


## devDependencies

実のところ、ユーザとしてライブラリを使うには前項の`dependencies`だけ知っていれば十分なんです。ただ、ライブラリをnpmに公開したり、別のプロジェクトに組み込んだりし始めるとそうもいかなくなってきます。

よく目にするのは、`devDependencies`でしょう。例えば、テストのために[mocha](https://github.com/mochajs/mocha)を入れてみます。

```bash
$ npm install --save-dev mocha
```

すると、`package.json`に次のように追記されます。

```json
{
  "devDependencies": {
    "mocha": "^2.4.5"
  }
}
```

先ほどとの違いは、`dependencies`か`devDependencies`かだけです。「テストやタスクランナー系はdevDependenciesに入れろ!」と教えられてきたと思いますが、それは半分だけ正解です。エンドユーザとしてアプリケーションを書く分には **別にどっちでもいいのです。**

極端な話、`dependencies`に書いてある全てを`devDependencies`に書き直しても、基本的には同じように動きます。逆もしかりです。(作成しているアプリケーションが、ライブラリとして使われる可能性がなければ)

> ※注: ただし、[deployd](https://github.com/deployd/deployd)のように、`dependencies`に入っているものを問答無用でプラグイン扱いにする(強引な)実装のものもあります。その場合、`devDependencies`にするかどうかは、アプリケーションだとしてもクリティカルです。

### いつ使うべきか

では、どういった状況で`devDependencies`を使うのでしょうか? それは、ライブラリをdev(開発)するときです。前項で例に出した`co`に依存関係はないと書きましたが、`devDependencies`にはいくつかのライブラリが登録されています。

```json
{
  "devDependencies": {
    "browserify": "^10.0.0",
    "istanbul-harmony": "0",
    "mocha": "^2.0.0",
    "mz": "^1.0.2"
  }
}
```

これらは、`co`をライブラリとして使う場合はインストールされませんが、`co`をGitでクローンして`npm install`した場合は、インストールされます。開発時のみに必要なライブラリで、実行時には役に立たないので、含める必要がないのです。

- テストツール
- ビルドツール / バンドルツール
- タスクランナー

などが主な用途だけど、「ライブラリとして読み込む際はいらないもの」というのが、より正しい理解です。(時には、テストツールであってもdependenciesに入れるべきこともあります)

状況 | インストールされるもの
:-- | :--
ライブラリとして`npm install co` | co
`co`のルートで`npm install` | browserify, istanbul-harmony, mocha, mz

## peerDependencies

ほとんどのケースでは、`dependencies`と`devDependencies`だけ使い分ければOKです。ただし、次のケースでは`peerDependencies`の利用を考える必要があります。

- プラグインモジュール
- シングルトンモジュール

前者は、[ESLintのプラグイン](https://github.com/eslint/eslint/blob/v2.9.0/docs/developer-guide/working-with-plugins.md#peer-dependency)などのケースです。単体では機能せず、ESLintのインストールが必須であることを明示したいケースがこれにあたります。プラグインについて、共通していることは次の点です。

1. あるモジュールに対応するインターフェースを持っているが、あまりバージョンに依存しない
2. あるモジュールについて、親となるアプリケーション(やライブラリ)が指定するバージョンを優先したい

### プラグインモジュール

話が込み入ってくるので、表にしてみましょう。例えば、`something-cool`というライブラリがあったして、最新版が`1.2.0`、親アプリケーションでは固定バージョン`1.0.0`が要求されているとします。このケースでは、`something-cool-plugin`は、`1.0.0`でも`1.2.0`でも問題なく動きますが、依存性をどこに書くかで挙動が変わります。

&nbsp; | 状況 | 親アプリケーション | プラグイン
:-- | :-- | :-- | :--
A | `dependencies`に`"something-cool": "^1.0.0"` | `1.0.0` | `1.2.0`
B | `peerDependencies`に`"something-cool": "^1.0.0"` | `1.0.0` | `1.0.0`

Aの状況で、プラグインに最新版の`something-cool@1.2.0`がインストールされることに注意してください。これは、npmが適合する最新版を使おうとするためです。親アプリケーションですでに`1.0.0`が使われていても関係ありません。あくまでもモジュールごとに最適なバージョンが判断されるため、別のバージョンが入ってしまうことがありえるのです。

別バージョンが入ってしまうと、アプリケーション本体と、プラグイン内で参照している`something-cool`のバージョンが違うため、予想しない挙動になることは大いにありえます。危険ですね。

> ※注: バージョン固定の方法としては、[npm shrinkwrap](https://docs.npmjs.com/cli/shrinkwrap)を使う手もあります

### シングルトンモジュール

プラグイン以外のケースでも`peerDependencies`を必要とすることがあります。それは、あるモジュールが **アプリケーション全体でひとつ** であることが望まれる場合です。例として、筆者の遭遇したケースを引き合いに説明します。

- `riot-route`: ルータ
- `riot-action`: MV*フレームワーク的ななにか
- `my-application`: アプリケーション本体

最初の2つは筆者がメンテナンスしているライブラリですが、当初`riot-action`では`dependencies`に`riot-route`が登録されていました。最新版で試している限りは特に問題ないのですが、`riot-route`のバージョンが上がった時に問題は起きました。

前項の表と同様に`my-application`は旧バージョンに固定されていたため、異なるバージョンの`riot-route`がインストールされてしまったのです。`riot-route`はルータなので、登録されたルーティング情報はアプリケーション全体から参照される必要があります(シングルトン)。が、複数のインスタンスがあっては挙動の予想がつきません。

この場合も、`riot-action`の`peerDependencies`に`riot-route`を登録することで、親アプリケーションのバージョンが優先されるようになります。(実際そのように変更しました)

### peerDependencies と devDependencies の両方に書くというワザ

npmのバージョン2までは、`peerDependencies`に指定がある場合も、自動的にインストールがされていましたが、バージョン3以降、ユーザが明示的にインストールするように仕様が変更になりました。

ただ、`peerDependencies`に書いたものであっても、テストの際には必要になるかもしれません。そんなときは、`devDependencies`にも書いておきましょう。そうすれば、プラグインの開発中は、自動的に`devDependencies`に従ってインストールされ、プラグインを利用する段には`peerDependencies`が参照されます。

## optionalDependencies

環境によって依存ライブラリが変わる場合に有効です。

```bash
$ npm install --save-optional fsevents
```

わかりやすい例は、[chokidar](https://github.com/paulmillr/chokidar)です。`chokidar`は`fsevents`に依存していますが、これはMacでしか使えません。`fsevents`をLinuxでインストールしようとしても`"os": ["darwin"]`の[指定がある](https://github.com/strongloop/fsevents/blob/v1.0.12/package.json#L10-L12)のでインストールでこけます。`dependencies`に書いてあると、その場でインストールが止まってしまいますが、`optionalDependencies`に書けば、スキップしてくれます。インストール時に、

```bash
npm WARN optional Skipping failed optional dependency
```

とか出るけれど、気にしなくてOKです。ただし、読み込めなかったライブラリを使おうとすると例外が発生するので、プログラム中でケアしてあげる必要があるので要注意。[chockidarでの実装例](https://github.com/paulmillr/chokidar/blob/1.4.3/lib/fsevents-handler.js#L7):

```javascript
try { fsevents = require('fsevents'); } catch (error) {}
```

## bundledDependencies

ちょうど、さきほど例に挙げた`fsevents`が[該当します](https://github.com/strongloop/fsevents/blob/v1.0.12/package.json#L41-L43)。

```json
{
  "bundledDependencies": [
    "node-pre-gyp"
  ]
}
```

注意点としては、バージョン指定がないので文字列の配列として指定します。基本的には次のようなケースで使います(たぶん)。

- npmにないライブラリを使う
- 変更を加えたライブラリを使う

詳しくは[StackOverflowの回答](http://stackoverflow.com/a/25044361/1542139)にゆずります。あんまり自分で使う機会はなさそう。

## まとめ

以上、`dependencies`の違いがあやふやになりそうな人(主に将来の自分)のために、書き留めておきます。

npmを使っていると、昨日動いたプログラムが今日動かないみたいなことは、しょっちゅうです。でも、バージョンの依存解決がどのように行われるかを理解していると、あまり動じなくて済みますね(希望)。

## おまけ・ショートカット一覧

関連して、1日のタイプ量が劇的に減るショートカットの一覧を挙げておきます。

コマンド | 省略版
:-- | :--
`npm install` | `npm i`
`npm install --save` | `npm i -S`
`npm install --save-dev` | `npm i -D`
`npm install --global` | `npm i -g`