コマンドパスを自動で通し npm install -g しない

  • 137
    Like
  • 0
    Comment
More than 1 year has passed since last update.

追記

npm install -g cosidered harmful

何かコマンドラインツールなどが必要なために npm install -g を強要するリポジトリがたまにある。

もっと面倒なのは、依存するツールがあるくせに README とかに書いてない場合だ。リポジトリにある設定ファイルからこちらが察して入れてやらないといけない。

グローバルに入れるツールは package.json の管理外なので、そこのバージョンは指定できない。

入れれば済むなら良いけれど、同じコマンドを他のリポジトリでも使っているような場合、求められるバージョンが違ったりすると面倒だ。

$ npm install -g gulp
$ gulp test

一番最悪なのは以下のような場合だ。

"scripts": {
  "postinstall": "npm install -g gulp"
}

npm install したのをフックして、否応無くグローバルに浸食する。こんなことができるからうかつに npm install も叩けない。駄目絶対。

どうするか

そもそも依存は全て package.json に書くべきで、必要なツールは devDependencies に入れておくべきだ。
ただし npm install したらツールも含めて node_modules に入る。

このとき、もしモジュールが bin (コマンド) を持っているなら、npm はそれを node_modules/.bin に入るという決まりがある。グローバルには入らない。

したがって README にこんな記述を書いているリポジトリもある。
(以下 lint なのは例であって tsc でも browserify でもなんでもいい)

$ ./node_modules/.bin/gulp lint

ローカルに入っているコマンドを指定するので、まだお行儀が良い。
ちなみに npm bin というコマンドがこのパスを出力するので、こう書ける。

$ $(npm bin)/gulp lint

でもこれは、がっつりコントリビュートするならコマンドはいるかもしれないが、「とりあえず lint を流したい」のであれば、面倒なだけだ。がっつりやるのではなく、このモジュールに対してちょっとだけいじってみる興味/必要があるライトなユーザには、それは npm から実行できていいはずだ。

それが 以前のエントリで言いたかったこと だ。

具体的には package.json にこう書く。

"scripts": {
  "lint": "gulp lint"
}

ここでは node_modules/.bin へのパスが自動で通るので、コマンドを書くだけで良い。(もちろんコマンドは devDependencies に入れておく)

README に必要なのは以下の二行だ。package.json をきちんと使えばこれだけで済むというのは知っておきたい。

$ npm install
$ # ちょっとコードをいじる
$ npm run lint

開発者は?

ところが開発者、がっつりコントリビュートする人はそうはいかないだろう。
上の例はカジュアルな、ちょっと自分でビルドしたい、ちょっと PR したい程度のユーザへの負荷を減らす設定で、開発中は gulp を叩くことになる。がっつりやるなら、どっちにせよ入れないといけないのだ。

このリポジトリのみの開発に専念しているなら -g で入れればいいだろう。しかし、複数のリポジトリでツールを使い回していると、場合によっては同じ問題にぶつかり振り出しに戻る。

一番シンプルな考えは、こうだ。

「常に今いるリポジトリの $(npm bin) にパスを通す。」

これを解決するソリューションは npm にはない。
似た考え方は Python の virtualenv に由来する仮想環境ツールだけど、俺は env 系ツールは嫌いで、実環境のパスを把握できる範囲でいじる方が好きだ。(node 自体も nodebrew でパスを切り替えている)

なので PATH 設定でやっている。

export PATH=$PATH:./node_modules/.bin

常に今いる場所の相対パスで解決する方法。この場合は $(npm bin) にしてしまうと、一回しか解決されないので直接書く必要がある。

(補足: 最初 $(npm bin) をディレクトリ移動毎に実行する zsh 関数を書いていたが、相対パスを直接書けばそんなものは必要なかった。@hokaccha さんに 指摘 されて気づいた、ありがとうございます!)

自分はこれで npm install -g のパスにある全てのモジュールを消したし、今のところ非常に快適。

かつ、この考えは bundle exec rails な環境とかでも同じように通用できるのではないかと思う。

複数のリポジトリを手がけたり追いかけたりするのが普通な時代なので、たかが一個のリポジトリにコントリビュートするために、開発者のローカル環境全体に影響を及ぼすようなことは、極力避けて行きたいという話。