Node.js 環境構築メモ

# 概要

久しぶりに Javascript で遊んでみようかなと思い立ったのに挫折してしまう理由ランキング堂々の第1位は、「自分のマシンの Node.js 環境が今どういう状態かわからない」だと思う。そもそも自分は Node.js をどうやってインストールしたんだっけ？バージョン管理はどうしてたっけ？というのが毎回わからなくなり、気づけば nodebrew と n が同居していたり、npm のバイナリがあちこちにあったりする。

今回、過去の自分がどうやって Node.js をインストールしたのかを調べることに成功したので、わかったことを備忘録的にまとめた。同時に、これから Node.js の環境を整えたい人のヒントになればと思う。

# tl;dr

* n-install を使って n を入れる
* n を使って node を入れる
* node に同梱されたバージョンの npm が勝手に入る
* node のバージョンを切り替えると npm のバージョンも勝手に変わる
* グローバルインストールした npm モジュールは n でバージョンを切り替えてもそのまま放置される

# n

Node.js のバージョンを管理には n を使う。自分の場合、さらにその n のインストールに n-install (https://github.com/mklement0/n-install) を使った。理由はおそらく下記の2つ：

* n をインストールするために npm をインストールしだすと、鶏と卵の問題が発生してしまう。
* n は何も設定しないと /usr/local 配下を直接書き換えてくるが、n-install を使うとデフォルトで `$HOME/n` 配下にもろもろ（Node.js 実行環境や n そのもの）を押し込めてくれる。

というわけで n-install は全てデフォルト設定でインストール。その結果 n は `$HOME/n` 配下にインストールされる。同時に、 .bash_profile に下記のような設定が自動的に追記される。

```bash
export N_PREFIX="$HOME/n"; [[ :$PATH: == *":$N_PREFIX/bin:"* ]] || PATH+=":$N_PREFIX/bin"  
# Added by n-install (see http://git.io/n-install-repo).
```

# Node.js

n を使えるようにしたので、当然 Node.js は n でインストールする。

`$HOME/n/n/versions/node/` 配下にバージョンごとにインストールされていき、特に n で選択したバージョンが `$HOME/n/` 配下の bin や lib に配置される。具体的にいうと、node コマンドは `$HOME/n/bin/node` にあった。

# npm

npm は Node.js に付属しているので、別途インストール作業は不要。

node コマンドと同様、npm コマンドは `$HOME/n/bin/npm` にある。また、npm でグローバルインストールしたモジュールは `$HOME/n/lib/node_modules` に入っていく。

バージョンに関しては、n で Node.js のバージョンを切り替えたタイミングで、「その Node.js に同梱されたバージョンの npm」に自動的に切り替わる。例えば、

* Node.js v9.2.1 → npm v5.5.1
* Node.js v10.15.0 → npm v6.4.1

といったように決まっている。同梱された npm のバージョンは `$HOME/n/n/versions/node/x.x.x/lib/node_modules/npm/CHANGELOG.md` を見るとわかる。

なお npm に関しては、下記2つの注意点がある。

## 注意点1 : npm 自体のバージョン管理

何かあって手動で npm のバージョンを上げても、その後 n で Node.js 自体のバージョンを切り替えると、同梱版のバージョンの npm に切り戻ってしまう。そしてどうやら現時点でバージョンの固定はできないようだ。

n の README には、npm がうまく動かなくなったら下記のコマンドで最新版の npm を手動で入れろと [書いてある](https://github.com/tj/n#working-with-npm) が、Node.js のバージョンを切り替えるたびにこれをやり直す必要がありそう。

```bash
# sudo は環境によっては要らないかもしれない
curl -0 -L https://npmjs.com/install.sh | sudo sh
```

## 注意点2 : グローバルインストールした npm モジュール

n は、グローバルインストールした npm モジュールを Node.js のバージョンごとに分離して管理できない。例えば Node.js 10 系を有効にした状態で create-react-app をグローバルインストールして、その後 Node.js 9 系を有効にしても、create-react-app はさっき入れたバージョンのものがそのまま残り続ける。

Node.js のバージョンを切り替えるたびによく使うグローバルモジュールを入れ直さなくていいというメリットはあるが、なんとなく環境のクリーンさが損なわれてしまう気もする。この仕様の是非については議論が続いているらしく、issue もあった (https://github.com/tj/n/issues/405) 。

# yarn

ここまでくると yarn も入れたくなる。yarn は `npm install -g yarn` で入れる。グローバルインストールなので、先述の通りインストール先は `$HOME/n/lib/node_modules/yarn/bin/yarn.js` となり、さらに `$HOME/n/bin/yarn` にそのシンボリックリンクが置かれる。

さて、その yarn で入れたグローバルモジュールはどこに入るのだろうか？答えは `$HOME/.config/yarn/global/node_modules/` だった。また、 `/usr/local/bin/` にそのシンボリックリンクが配置されていた。

これらのパスは yarn が決めたものだ。ここまでくると n の設定からは外れて、完全に yarn の世界になる。前述の通り n はグローバルモジュールのバージョンを管理しないが、仮に今後のバージョンアップで管理できるようになったとしても、yarn の世界まで面倒を見てくれるかというと微妙そう。

# まとめ

ここまで書いて、会社のパソコンで `which npm` したら `$HOME/.anyenv/envs/ndenv/shims/node` と出てきた。つらい。
Docker を使おう。

# 付録：各コマンドのパスリスト

```bash
$ which n
/Users/megane42/n/bin/n

$ which node
/Users/megane42/n/bin/node

$ which npm
/Users/megane42/n/bin/npm

$ which yarn # npm install -g で入れたもの
/Users/megane42/n/bin/yarn

$ which firebase # yarn global add で入れたもの
/usr/local/bin/firebase
```