そろそろ適当に npm install するのを卒業する


# はじめに
ネットに転がっている記事などで `npm install` のコマンドをよく分からず実行してきましたが、そろそろその状態から卒業したかったので備忘録をかねてこちらの記事を投稿しました。

`npm install` コマンドに関することをメインにお伝えしますが、package.json や node_modules など npm を語る上で基本的なことについても触れていきたいと思います。

# 目的

この記事の目的は、以下の4つの `npm install` コマンドを実行したときの挙動の理解と、これら4つのコマンドを必要な状況に応じて使いわけるようになることです。

- `npm install`
- `npm install -g <package>`
- `npm install <package>`
- `npm install -D <package>`

つまり、適当に `npm install` するのを卒業することです。

# 動作環境

```shell
# Node.js のバージョン確認
$ node -v
v14.13.1

# npm のバージョン確認
$ npm -v
6.14.8
```

ちなみに npm 自体のアップデートは以下のコマンドになります。
npm はメジャーバージョンが異なるとかなり挙動が異なるので、参照する記事の npm のバージョンにも注意が必要です。

```shell
# npm のアップデート
$ npm update -g npm
```

# npm install を説明する前に知っておきたいこと

この辺を理解しておかないと適当に `npm install` してしまうことになるので、先に説明します。
知っている方は飛ばしても大丈夫です。

## Node.js
Node.js とはサーバサイドで動く JavaScript のことです。

## npm
npm（Node Package Manager）とは Node.js のパッケージ（Package）を管理する（Manager）ツールです。

Node.js のパッケージ（Package）とは予め用意された便利な機能（各種フレームワークやライブラリ）をまとめたものです。

## yarn

yarn とは 2016 年にリリースされた npm と互換性のあるパッケージマネージャーです。

今回は yarn コマンドについては触れませんが、以下の意見にとても納得しました。
ご参考まで。

> ちなみに: 似たような CLI として Facebook が開発した Yarn がある。これは npm の色々な欠点(スピードなど)を補うように作られたものであり、かなり人気がある。npm パッケージの README でしばしば npm と yarn でインストールする方法が両方書かれていたり、時には「yarn を使用することを推奨する」と書かれていたりする。しかし、npm も改善されてきており、わざわざ yarn をインストールして使用するメリットはあまりないと筆者は考えている。特に初心者にとっては、スタンダードでないツールを使用すると無駄に学ぶことが増えるのでおすすめしない。
>
>
> 【初心者向け】NPMとpackage.jsonを概念的に理解する （抜粋）
> https://qiita.com/righteous/items/e5448cb2e7e11ab7d477

## package.json

package.json とは Node.js ベースの JavaScript アプリ開発において、自身のパッケージ（= プロジェクトそのもの）を管理するために使われるファイルのことです。

`npm init` とコマンド実行すると package.json が作成されます。

## package-lock.json
簡単に説明すると package.json を用いてパッケージをインストールした結果が記載されるファイルです。
今回は説明を省略しますが、詳しく知りたい人は【参考】のリンク先を参照してください。

【参考】
[package-lock.json ってなに？](https://qiita.com/sugurutakahashi12345/items/1f6bb7a372b8263500e5)

## npm init

package.json は普通に touch コマンドなどでも作成できますが、基本的には `npm init` とコマンド実行して package.json を作成します。

（【脱線】[npm init しないとどうなる？](https://qiita.com/sugurutakahashi12345/items/1049a33b86225f6345fe)）

```shell
# 何もない空のディレクトリ
$ ls
（標準出力なし）

# npm init による package.json の作成（質問をされるが一旦すべて Enter で通過する = npm init -y コマンド実行時と同じ挙動）
$ npm init

# package.json が作成されている
$ ls
package.json
```
package.json の初期値は以下のようなものになります。

```json:package.json
{
  "name": "<current directory name>",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

package.json にはいろいろ項目がありますが `name`, `version`, `description`, `keywords`, `author`, `license` などのデータは単なる自身のパッケージ（= プロジェクトそのもの）のメタデータであり、自身のパッケージ（= プロジェクトそのもの）を公開しなのであれば、気にする必要はありません。

`dependencies`, `devDependencies`, `scripts`, `config` の4つぐらいを抑えれば、あとは必要に応じて調べて設定すれば問題ないかと思います。

【参考】
[package.jsonの構造](https://qiita.com/mzmz__02/items/dfa7abb4237a3842a2a8)

## npm と package.json の関係

公開されているパッケージ（ライブラリやフレームワークなど）は `npm` コマンドによってインストールすることができます。

package.json が存在するディレクトリで `npm` コマンドによってパッケージをインストールすると、自動的に package.json が更新されます。

また、**人間が package.json に記述されているパッケージ名やバージョン情報を直接編集することはありません。**

**それらは `npm` コマンド経由で更新します。（大事）**

## node_modules
node_modules とは package.json を元にしてインストールされる各種パッケージがインストールされるディレクトリ先のことです。

package.json さえあれば、`npm install` コマンドの実行によって node_modules が生成することが可能です。

そのため通常は `.gitignore` に指定されるディレクトリになります。

# npm install

ここからがこの記事の本題になります。

パッケージを node_modules にインストールするには `npm install` とコマンド実行します。

この `npm install` というコマンドですが、これがいろいろと種類があってややこしいので詳しく説明していきます。

## とりあえずよく使うコマンド一覧

**引数なしでの実行する場合**

```shell
# 引数なし実行（package.json のあるディレクトリで実行する）
npm install
```

**グローバルインストール**

```shell
# グローバルインストール
npm install -g <package>
npm install --global <package>
```

**ローカルインストール**

```shell
# ローカルインストール
# package.json の dependencies に追加するとき（npm v4 以下では --save (= -S) のオプションが必要、npm v5 以上ではデフォルトになったため不要）
npm install <package>
npm install --save <package> # デフォルトで入っているオプションなので使う必要なし
npm install -S <package> # デフォルトで入っているオプションなので使う必要なし

# package.json の devDependencies に追加するとき --save-dev (= -D)
npm install --save-dev <package>
npm install -D <package>
```

**バージョン指定のインストール**

```shell
# バージョンに関する指定
npm install <package>@x.y.z # バージョンを指定する場合
npm install <package>@latest # 最新版を指定する場合（ @latest とつけなくても指定しなければ最新版がインストールされる）
```

## 引数なし `npm install`

引数のない `npm install` コマンド実行時の挙動は、引数のある `npm install <package>` とわけて考えたほうがわかりやすいです。

**引数のない `npm install` コマンド実行をすると、カレントディレクトリにある package.json に記述されている情報を元に、そこに記述されている パッケージを node_modules （インストール先）にインストールします。** 

**使うタイミングは 「package.json を共有されて node_modules を作成していない時」や「package.json が更新された時」などになります。** 

また、`npm install` コマンド実行する前に、あらかじめ package.json にインストールしたいパッケージ情報を記述しておく必要があります。

そして、その package.json へのパッケージ情報を記述は、引数ありの `npm install <package>` のコマンドの実行によって行います。

## 引数あり `npm install <package>`

大きな分類としてグローバルインストールとローカルインストールに分けられます。
また、ローカルインストールのなかでも package.json の記述先の違いで2つに分けられます。

全体で見るとグローバルインストール1種類、ローカルインストール2種類の合計3種類になります。

コマンド | 大分類 | package.json の記述先
--- | --- | ---
`npm install -g <package>` | グローバルインストール | -
`npm install <package>` | ローカルインストール | dependencies
`npm install -D <package>` | ローカルインストール | devDependencies

ちなみに「グローバルインストール」と「ローカルインストール」の両方とも自身のPCの環境へインストールされます。

### グローバルインストール
グローバルインストールすると自身の PC の環境ならどこでもインストールしたパッケージ（コマンド）が実行できます。

ただし、グローバルインストールしても `nodebrew` などで複数の Node.js のバージョンを管理している場合は、それぞれのバージョンで `npm install -g <package>` したパッケージが、それぞれ別のものとして管理されるので注意が必要です。

【参考】
[【npm】 パッケージのインストール先の確認（npm list）](https://qiita.com/sugurutakahashi12345/items/8820b09db0dc1507f563)

**グローバルインストールの挙動**

```shell
# 未インストールなので cowsay コマンドは実行できない
$ cowsay "hoge"
zsh: command not found: cowsay

# cowsay コマンドをグローバルインストール
$ npm install -g cowsay
$ cowsay "hoge"
 ______
< hoge >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
```

### ローカルインストール
ローカルインストールすると node_modules と同じディレクトリにある場合にパッケージ（コマンド）が実行できます。

【参考】
[npm でローカルインストールしたパッケージを CLI でコマンド実行する方法（npm-srcipts, npx）](https://qiita.com/sugurutakahashi12345/items/b814a09b65d8852226ad)

#### ローカルインストールの挙動（ `-D` オプションなしの場合）

```shell
# 適当なパッケージ（cowsay）をインストール
$ npm install cowsay
+ cowsay@1.4.0

# dependencies の項目に cowsay が追加されていることが確認できる
$ cat package.json
{
  （省略）
+ "dependencies": {
+   "cowsay": "^1.4.0"
+ },
  （省略）
}

# node_modules にインストールされるのでパスを通しながら実行可能
$ ./node_modules/.bin/cowsay "hoge" 
 ______
< hoge >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
```


#### ローカルインストールの挙動（ `-D` オプションありの場合）

```shell
# 適当なパッケージ（typescript）を -D のオプションをつけてインストール
$ npm install -D typescript
+ typescript@4.0.3

# devDependencies の項目の typescript が追加されていることが確認できる
$ cat package.json
{
  （省略）
  "dependencies": {
    "cowsay": "^1.4.0"
  },
+ "devDependencies": {
+   "typescript": "^4.0.3"
+ },
  （省略）
}

# -D のオプションをつけても変わらずに node_modules にインストールされるのでパスを通しながら実行可能
$ ./node_modules/.bin/tsc --version 
Version 4.0.3
```

## グローバルインストール と ローカルインストールの使い分け

では、パッケージをインストールする場合、**グローバルインストール** と **ローカルインストール** のどちらにインストールすべきでしょうか？

状況によりますが、**闇雲にグローバルインストールするのは避けたほうがいいです。**

グローバルインストールされたパッケージは環境が異なると使用することができません。

例えば、PC を買い替えたらそのパッケージを使用することができないですし、他人の PC でもそのパッケージを使用することができません。

そいういうときにローカルインストールで作成される package.json だけを共有すれば、どの環境でも `npm install` を実行するだけで、同じパッケージを使用することができます。

**『どんなディレクトリでも、いつでもどこでもそのコマンドを使いたい』というパッケージだけをグローバルインストールして、それ以外はローカルインストールするのが無難だと思います。**

## ローカルインストール時に `-D` オプションをつけるべきケース

**一般的に、開発環境でしか使用しないパッケージについては `npm install -D <package>` として、そうではないパッケージについては `npm install <package>` とします。** 

パッケージ名の引数をとらない `npm install` には `--production` とオプションをつけることで、`npm install -D <package>` でインストールしたパッケージを除いて node_modules にインストールすることができます。

このように `--production` のオプションをつけることによって開発環境でしか使わないパッケージを除外できるので、本番環境デプロイ時に参照されることのないパッケージをインストールしなくて済むようになります。

ローカルインストール時の `-D` オプションの有無による違いをまとめると以下のようになります。

|実行するコマンド|package.json の記述先|それぞれの使い分け|
|:----|:----|:----|
|`npm install <package>`|`dependencies`|開発環境以外でも使用されるパッケージ<br>ex) express|
|`npm install -D <package>`|`devDependencies`|開発環境のみで使用されるパッケージ<br>ex) eslint|

また、パッケージ名の引数をとらない `npm install` 実行時の `--production` オプションの有無による違いをまとめると以下のようになります。

|実行するコマンド|インストール対象|実行するタイミング|
|:----|:----|:----|
|`npm install`|・ `dependencies`<br>・ `devDependencies`|開発環境|
|`npm install --production`|・ `dependencies` のみ|本番環境など開発環境でしか使われないパッケージをインストールしたくないとき|

いろいろ書いていますが、ケースバイケースなので、実際に `npm install --production` をするときになってから考えればいいと思います。

【参考】
[【package.json】dependencies, devDependencies の使い分けを考える](https://qiita.com/karur4n/items/3d9d28f6f21c3533020d)

# まとめ

それぞれの `npm install` コマンドの使用するタイミングをまとめると以下になります。

| コマンド                   | 使用するタイミング                                                                                                                                      | 
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | 
| `npm install`              | 新規 or 更新された package.json を元に node_modules を作成するとき                                                                                      | 
| `npm install -g <package>` | グローバルインストールするとき（= どんなディレクトリでもそのコマンドを使えるようにしたいとき）                                                          | 
| `npm install <package>`    | package.json にローカルインストールしたい（= そのディレクトリだけで使用できるようにしたい）パッケージを追加するとき（本番環境でも使用するパッケージ）   | 
| `npm install -D <package>` | package.json にローカルインストールしたい（= そのディレクトリだけで使用できるようにしたい）パッケージを追加するとき（開発環境でのみ使用するパッケージ） | 


# さいごに
いかがだったでしょうか？
もうこれで、以下の4つの `npm install` コマンドは使い分けできると思います。

- `npm install`
- `npm install -g <package>`
- `npm install <package>`
- `npm install -D <package>`

<br>
**ここには入りきらなかった npm に関する記事たちのURLを貼っておきます↓**

Node.js のバージョン管理についてのメモ（nodebrew, nodenv）
https://qiita.com/sugurutakahashi12345/items/20803f553b5716c13902

【npm】 パッケージのインストール先の確認（npm list）
https://qiita.com/sugurutakahashi12345/items/8820b09db0dc1507f563

セマンティック バージョングについてのメモ
https://qiita.com/sugurutakahashi12345/items/68e9dfb11e84d20acc6d

package.json に記述される チルダ ^ や キャレット ~ について
https://qiita.com/sugurutakahashi12345/items/0876ce674587515c66ba

package-lock.json ってなに？
https://qiita.com/sugurutakahashi12345/items/1f6bb7a372b8263500e5

npm init しないとどうなる？
https://qiita.com/sugurutakahashi12345/items/1049a33b86225f6345fe

npm でローカルインストールしたパッケージを CLI でコマンド実行する方法（npm-srcipts, npx）
https://qiita.com/sugurutakahashi12345/items/b814a09b65d8852226ad

npx でパッケージ名とコマンドが異なる場合（`npx -p <package> -c "<commond>"`）
https://qiita.com/sugurutakahashi12345/items/329e0cdbaf337edb81d3

npm-scripts の pre, post プレフィックス
https://qiita.com/sugurutakahashi12345/items/91a133abacfc38b3d7a7

npm-scripts の 順次・並列実行（npm-run-all）
https://qiita.com/sugurutakahashi12345/items/2a17a3cdfbc4a7e5e4eb

package.json に記載されているパッケージのバージョンアップ方法【npm-check-updates, outdated】
https://qiita.com/sugurutakahashi12345/items/df736ddaf65c244e1b4f

package.json の config の使い方について
https://qiita.com/sugurutakahashi12345/items/357fc6c2ae04f48b2076