yarnがnpmと何が違うのか試してみた

# はじめに

こんな`package.json`があるとして

```sh
$ cat package.json
{
  "name": "sample",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "gulp-cache": "^0.4.5",
    "gulp-debug": "^2.1.2",
    "gulp-eslint": "^3.0.1",
    "gulp-size": "^2.1.0"
  }
}
```

`yarn`も`package.json`に対応しているため、
`npm install`同様(デフォルトでは)`node_modules`にモジュールが降ってきます。

```sh
# npm の場合
$ npm install

# yarn の場合
$ yarn
# または
$ yarn install
```

ちなみに上記の`package.json`は、以下のコマンドで、作成からモジュールのインストールまで行えます。

```sh
# package.json の作成
$ yarn init
# パッケージの追加すると、同時に取得される
$ yarn add gulp-cache gulp-debug gulp-eslint gulp-size
yarn add v0.15.1
[1/4] Resolving packages...
```

# `npm install`と何が違うの？

## `yarn.lock` なるファイルが作成されます。

`yarn`の場合`yarn.lock`が生成されます。

これは`npm`の shrinkwrap に相当する機能で、
これにより依存するモジュールのバージョンを固定することができます。

```sh
# 中身はこんな感じ
$ head yarn.lock
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
acorn-jsx@^3.0.0:
  version "3.0.1"
  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
  dependencies:
    acorn "^3.0.4"

acorn@^3.0.4:
  version "3.3.0"
```

じゃあ shrinkwrap との違いは何かといえば

* デフォルトで生成されるので簡単に利用できる
* 各モジュールがソートされており構成管理が容易

確かに人が見ても`npm-shrinkwrap.json`と比べて記載が非常に分かりやすいです。

[Yarn: A new package manager for JavaScript](https://code.facebook.com/posts/1840075619545360/yarn-a-new-package-manager-for-javascript/)


## モジュールのフォルダ構造が変わります。(追記あり）

`node_modules`の中身を見れば一目瞭然

```sh
# npm(v2) の場合
$ ls node_modules/
gulp-cache/  gulp-debug/  gulp-eslint/  gulp-size/

# yarn の場合
$ ls node_modules/
acorn/                 generate-object-property/  normalize-package-data/
acorn-jsx/             get-stdin/                 number-is-nan/
ajv/                   glob/                      object.omit/
ajv-keywords/          globals/                   object.pick/
ansi-escapes/          globby/                    object-assign/
ansi-regex/            glogg/                     once/
..... 略
```

`yarn`は依存モジュールも含めて`node_modules`直下に入るためフォルダの数が多くなります。
でもファイルサイズは`yarn`の方が小さいです。

```sh
# npm(v2) の場合
$ du -sh node_modules/
32M     node_modules/

# yarn の場合
$ du -sh node_modules/
20M     node_modules/
```

これは依存により重複しているモジュール、
例えば`readable-stream`の場合だと、以下のように配置されていました。

```sh
# npm(v2) の場合
$ find -type d | grep -e readable-stream$
./node_modules/gulp-cache/node_modules/gulp-util/node_modules/multipipe/node_modules/duplexer2/node_modules/readable-stream
./node_modules/gulp-cache/node_modules/gulp-util/node_modules/through2/node_modules/readable-stream
./node_modules/gulp-cache/node_modules/readable-stream
./node_modules/gulp-debug/node_modules/gulp-util/node_modules/multipipe/node_modules/duplexer2/node_modules/readable-stream
./node_modules/gulp-debug/node_modules/through2/node_modules/readable-stream
./node_modules/gulp-eslint/node_modules/bufferstreams/node_modules/readable-stream
./node_modules/gulp-eslint/node_modules/eslint/node_modules/concat-stream/node_modules/readable-stream
./node_modules/gulp-eslint/node_modules/gulp-util/node_modules/multipipe/node_modules/duplexer2/node_modules/readable-stream
./node_modules/gulp-eslint/node_modules/gulp-util/node_modules/through2/node_modules/readable-stream
./node_modules/gulp-size/node_modules/gulp-util/node_modules/multipipe/node_modules/duplexer2/node_modules/readable-stream
./node_modules/gulp-size/node_modules/through2/node_modules/readable-stream

# yarn の場合
$ find -type d | grep -e readable-stream$
./node_modules/concat-stream/node_modules/readable-stream
./node_modules/duplexer2/node_modules/readable-stream
./node_modules/readable-stream
./node_modules/through2/node_modules/readable-stream
```

`npm`で重複されていたモジュールが`yarn`ではきれいに整理されています。
ちなみにそれぞれ以下のバージョンのモジュールがインストールされてました。

module                                                    | version
-------------------------------------------------------   | ----------
./node_modules/concat-stream/node_modules/readable-stream | 2.0.6
./node_modules/duplexer2/node_modules/readable-stream     | 1.1.14
./node_modules/readable-stream                            | 2.1.5
./node_modules/through2/node_modules/readable-stream      | 2.0.6

この中で最新の(v2.1.5)だけを利用しても問題なさそうな場合は`--flat`をつけて実行します。
すると以下のようにどのバージョンのモジュールを使用するかを選択できます。

```sh
$ yarn --flat
info Unable to find a suitable version for "readable-stream", please choose one by typing one of the numbers below:
  1) "readable-stream@^2.0.4, readable-stream@^2.0.2" which resolved to "2.1.5"
  2) "readable-stream@~2.0.0" which resolved to "2.0.6"
  3) "readable-stream@~1.1.9" which resolved to "1.1.14"
Answer?:
```

選択したバージョンが`package.json`に`resolutions`として勝手に記録されました。

```sh
$ cat package.json
{
  "name": "sample",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "gulp-cache": "^0.4.5",
    "gulp-debug": "^2.1.2",
    "gulp-eslint": "^3.0.1",
    "gulp-size": "^2.1.0"
  },
  "resolutions": {
    "object-assign": "4.1.0",
    "readable-stream": "2.1.5",
    "vinyl": "1.2.0",
    "minimist": "1.2.0",
    "strip-bom": "3.0.0",
    "estraverse": "4.2.0",
    "acorn": "4.0.3"
  }
}
# この package.json の状態でインストール
$ yarn
```

これにより構造はさらにシンプル(1階層のみ)になり、フォルダサイズもちょっと小さくなりました。

```sh
$ find -type d | grep -e readable-stream$
./node_modules/readable-stream

$ du -sh node_modules/
19M     node_modules/
```

ただしこの場合、
バージョン指定と異なるモジュールの影響で正しく動作しない可能性があるので注意。

---
**追記**
フォルダ構成の違いは、利用している`npm`のバージョンがv2であることが原因でした。
v3を利用した場合は `yarn`とほぼ同じフォルダ構成が得られました。

```sh
$ npm -v
3.10.9
$ npm install
$ find -type d | grep -e readable-stream$
./node_modules/concat-stream/node_modules/readable-stream
./node_modules/duplexer2/node_modules/readable-stream
./node_modules/readable-stream
./node_modules/through2/node_modules/readable-stream
```

このあたり、リンク先の説明が非常に分かりやすかったです。
[npm v3 Dependency Resolution](https://docs.npmjs.com/how-npm-works/npm3)

ただ私の環境ではフォルダ構成は完全に一致せず、
一部のモジュールで違いがあり、やはり`yarn`のほうが若干シンプルな構造になっているようでした。

```sh
# npm(v3.10.3)の場合
$ npm cache clean
$ npm install
$ find -type d | grep -e /isarray$
./node_modules/concat-stream/node_modules/isarray
./node_modules/doctrine/node_modules/isarray
./node_modules/isarray
./node_modules/isobject/node_modules/isarray
./node_modules/readable-stream/node_modules/isarray
./node_modules/through2/node_modules/isarray
$ du -sh node_modules/
21M     node_modules/

# yarn(v0.15.1)の場合
$ yarn -v
$ yarn cache clean
$ yarn install
# やはり yarn のほうがよりシンプルな構造になっている
$ find -type d | grep -e /isarray$
./node_modules/duplexer2/node_modules/isarray
./node_modules/isarray
$ du -sh node_modules/
20M     node_modules/
```

コメントの指摘を受け、追記させてもらいました。

---


# まとめ

結局使えるのか？

自分のプロジェクトに適用した場合、`npm`に比べてモジュールのインストールがかなり早かったです。
`node_modules`のファイルサイズも小さくなってまして、いつでも`npm`に戻れるのでとりあえず使ってみようかなと。

方式                | サイズ
------------------- | -----
npm install(v2)     | 378M
npm install(v3)     | 248M
yarn install        | 237M
yarn install --flat | 170M

そもそも利用モジュールを減らしたい...

上記の違い以外にも、オフラインモードやネットワークのパフォーマンス改善など、
そのあたり詳しくはやっぱり[公式サイト](https://yarnpkg.com/)をどうぞ。