npm で依存もタスクも一元化する

# タスク管理

package.json にはパッケージの依存を書いて npm install するのが基本だけど、
タスクの管理をどうするかというのは、別途また考えないといけない。

自分は gulp が良いと思っているが、 grunt や jake や make を使う人もいる。

また、たくさんオプションをつければほぼ一つのタスクが実行できてしまう `browserify`, `jsh/eslint`,  `mocha` などのコマンドを提供するツールもある。

そして、 npm にも一部それらをサポートする `npm run` 機能があるので、そこに Unix ワンライナーを書くこともできる。

今回は、「どのタスクツールが最良か」みたいな話ではなく、それらをどうやって実行するか、または npm との棲み分けとか構成の流儀について、最近良いと思っているやり方について書いておく。

各方針で問題点を書いていくが、 npm にはとかく機能がおおいので、「それこうやればできる」などあれば遠慮なく指摘いただきたい。

以下に書く「ユーザ」とは、そのリポジトリを clone して開発する人をさしている。
例えば、ネット上のコントリビュータだったり同僚だったり、未来にそれを引き継ぐまだ見ぬ開発者だったりを想像して欲しい。

## ツールの導入

grunt や gulp のツール自体は `npm install -g` で入れて叩くのが基本だろう。
これには、別の以下の問題があると思っている。(問題にならない場合もある)


- ユーザに別途、グローバルインストールさせる必要がある。
 - つまりユーザは、そのプロジェクトに必要なツールを調べないといけない
 - その依存を書くスペースは `package.json` にはない
- ユーザがすでに同じツールを入れている場合もある
 - それが同じバージョンとは限らない
 - 違っても気づきにくい
- npm のフックで無理矢理 `-g` で入れさせるのは良くない
 - `"preinstall": "npm install -g gulp"` などで無理矢理出来なくはない。
 - ユーザが持つ他のプロジェクトに影響する可能性がある
 - ユーザのローカル環境を勝手に変えるな

(追記: ここの書き方が悪くて、 `preinstall` での `npm install -g` を推奨するようなミスリードを誘ってしまったようでした。「勝手にグローバルをいじるとか、しない方がいい」という記事なので、言い回しを直しました。)


## ツールも `package.json` で

そこで、 `package.json` の `devDependencies` に -g で入れるべきツールも全て書く。
すると、ユーザはとにかく一度だけ `npm install` すれば、必要なものが全て揃う。

- 必要なツールのを `devDependencies` に書く
 - 依存が明示される
 - バージョンも指定できる
 - `preferGlobal` がついてるとだめっぽいがたぶんあまりない
- `node_modules/.bin` に入る
 - グローバルを汚さない
 - パスを通す必要がある


## タスクの実行

このままでは、実行はたとえば以下のようになる。

```bash
$ node_modules/.bin/gulp build
```

しかし、このパスは `npm bin` で取れる。

```bash
$ $(npm bin)/gulp build
```

じゃあ、ユーザはこうしろというのか？

```bash
$ alias gulp="$(npm bin)/gulp"
```

違うそうじゃない。


## タスク実行も `package.json` で

`package.json` の `scripts` の下には、タスクが書ける。

こんな感じ。

```js
"scripts": {
  "build": "$(npm bin)/gulp build"
}
```

ところが、実は "script" を "npm run" で実行する場合は、パスが自動で通るのでこう書ける。

```js
"scripts": {
  "build": "gulp build"
}
```

実行はこう。

```bash
$ npm run build
```


`install`, `start`, `test` など `run` 無しで実行できるデフォルトのタスクと、それに依存する `preinstall`, `postinstall` なども適切に使い分けるべき。

```bash
$ npm start # run はいらない
```

## タスク自体は書かない

`scripts` には何でも書ける。
例えば以下のように、各コマンドを直接叩いたり、 Unix コマンドでワンライナーを書くことができるが、できれば避けたい。

```js
"scripts": {
  "start": "node app.js 3000",
  "test" : "mocha test/*",
  "build": "cat js/* > bundle/bundle.js",
  "clean": "rm -rf bundle/*"
}
```

理由は 3 つある。

- Unix ワンライナーだと Windows で動かないも
 - ビルドすらできないとか、テストすら走らないケース。
 - タスクが node で書かれていれば動く。
 - npm のエコシステムが使いづらい。
 - あとただの JSON 上の文字列なので保守がしづらいし、すぐ長くなる。
- タスクがちらばる
 - 上の例では、 gulp に集約されてるだろうと思ったらそうじゃなかった
 - タスクを定義するときどちらに書くかみたいな乖離がでる
 - しかも上の例でさらに `gulp clean` も定義されていたりしたら。。(経験談)

ちょっとした処理でも、基本は Node で書き、さらに gulp やら grunt を使うなら、そこで全て管理する。メジャーなツールであればプラグインはだいたいある。タスクを追加する時は必ず gulp や grunt で書け、どんな些細な処理でもだ。という規約だけを押し付ければ良くなる。

すると、 `scripts` は実際は以下のようにただの shim になる。

```js
"scripts": {
  "start": "gulp run",
  "test" : "gulp test",
  "bower": "gulp bower",
  "lint" : "gulp lint",
  "build": "gulp build",
  "clean": "gulp clean"
},
```

ここまでの文脈を理解していないと、これは「意味の無いもの」に思えるかもしれない。
そう指摘されたらこのエントリを見せて、それでもいらなければやる必要は無い。


定義されたタスクの一覧は `npm run` で取れる。

```bash
$ npm run
Available scripts in the PACKAGE_NAM package:
  start
    gulp run
  test
    gulp test
  bower
    gulp bower
  lint
    gulp lint
  build
    gulp build
  clean
    gulp clean
```

- タスクの実行を script に書く
 - タスク本体はタスクツールで書くので長くならない
 - bin へのパスは自動で通る
 - 事前定義のものは run なしでいい
 - グローバルにコマンドが必要ない
- タスクはかならずタスクツールで
 - Node で書いて環境依存をなくす
 - タスクが散らばるのを防ぐ
- 一覧も取れる
 - npm run だけ覚えておけば良い
 - 一覧から適切なのを選んで実行すれば良い
- npm で実行する
 - npm だけ入ってればいい
 - npm だけ覚えれば良い
 - 例えばタスクツール自体が新しいものになっても、 npm から使うユーザは気にしないで良い。


## ユーザが知っておくべき知識

これだけ。
で済むという旨を README に書いておけば良い。

```bash
$ npm install
$ npm run
```

初めて使うユーザは、とにかく `npm install` して `npm run` で一覧を取得し必要なタスクを実行する。
また、ガッツり開発する場合は、そこで使われている `gulp` や `grunt-cli` を `-g` で入れて、 `scripts` に書かれているのと同じタスクを実行すれば良い。
もしタスクを修正する場合は、 `scripts` の更新も忘れないようにする。ただし `scripts` 自体にタスクを書かせない、絶対に。

そのユーザの関わり方、そのプロダクトについての知識量に応じて、適切な方法が選べるのがこの方針の良いところだと思っている。

そして、 CLI などの連携は npm 経由で行うことで、グローバル環境への汚染を減らし、タスクツールの移行も楽になる。


## npm にまとめる

- npm は node に標準で入っている
- gulp, grunt は変わるかもしれないが npm は安定してこれからも使われるだろう。
- npm を経由することで、実際に使うタスクツールの移行は楽になる。
- これからはフロントエンドも npm へのエコシステム集約が進む可能性がある。

## まとめ

- 提供側
 - `devDependencies` にタスクツールを含める
 - `scripts` にタスクの実行を書く
 - `scripts` にタスク自体は書かない
 - npm で完結していることを README に書いておく

- ユーザ
  - `npm install`, `npm run` の二つだけ覚えればいい。
  - 他に何もいらない
  - タスクツールが変わっても、実行方法変わらない
  - プロダクトに対する知識が増えたら、ツールをグローバルに入れるなり勝手にやれば良い。


Node 学園祭でも @ahomu さんとこの話をしたところ、概ね同意頂けた。俺は裏で聞けなかったけど、彼もパッケージ周りの話をしていたので、合わせてどうぞ。[Front-end with Node.js - Node学園祭 2014](http://havelog.ayumusato.com/misc/e633-nodefest_2014.html)