【初心者向け】NPMとpackage.jsonを概念的に理解する

## 概要

npm に初めて触れるときは、package.json がどういう役割をもっているのか、パッケージをインポートするとはどういうことなのかなど分からないことだらけであり、筆者も少しずつ調べては試すことを繰り返した記憶がある。これから Node.js を学ぼうという人にはこのような部分でつまづいてほしくないため、この記事では npm を使う上で必要な概念的知識を説明する。この記事を読めばスムーズに Node.js の学習が始められると思われる。

## NPM とは

NPM と名のつくものは実は 2 つあり、ひとつはオンライン上のパッケージレジストリ、つまり世界中の開発者が作った Node.js のパッケージが集められた場所である。もう一つは Node.js に付属している、パッケージを操作するための CLI(コマンドラインインターフェイス; コマンドラインから実行できるプログラム)である。

以降、曖昧さを回避するためにパッケージレジストリの方を大文字の **NPM**、CLI の方を小文字の **npm** で表記する。

## NPM(レジストリ)

NPM は現時点で他の言語のものを含め[世界最大のパッケージレジストリ](https://www.linux.com/news/event/Nodejs/2016/state-union-npm)であり、主にブラウザ用のライブラリ、Node.js 用のライブラリが豊富に存在する。NPM に登録されているパッケージは[公式サイト](https://www.npmjs.com/)でブラウジング、検索できる。

ちなみに: 「なんでブラウザ用のライブラリがNPMにあるの？」と思うかもしれない。確かに、`document.querySelector("div")` みたいなコードは Node.js で実行することはできない。しかし jQuery や Vue などのライブラリは確かに NPM に存在する。この理由は、近年ではフロントエンドアプリ(ブラウザ上で実行されるアプリ)も、Webpack のような Node.js 上で動くツールを使って開発することが多いからである。

パッケージを利用する点で注意すべきなのは、誰でもパッケージを公開できるため安全であるという保証はないということである[^1]。特に業務レベルでパッケージを使う際には、その dependency (そのパッケージが使用しているまた別のパッケージ)を含めて本当に信頼できるかを吟味する必要がある。しかし React などのある程度高機能なライブラリになると、dependency の dependency...とたどっていくと膨大なパッケージ数となり、とても手作業でチェックできるものではないという問題がある。実際には、「有名なライブラリなんだから dependency もちゃんと安全なのを使っているだろう」と信じて使っている人が多いだろう。

**Note:** ちなみにほとんどのパッケージは GitHub にソースコードが載せられているが、そのコードがそのまま NPM に登録されているという保証はどこにもなく、コードをチェックしたい場合は NPM から直接ダウンロードするべきである。

[^1]: `npm audit`で脆弱性のある dependency があるか自動でチェックすることができるが、「報告されている」脆弱性しか考慮されないので安心はできない。

## npm(CLI)

npm (node package manager) はその名の通り Node.js のパッケージを管理するための CLI であり、パッケージを作成したり、NPM 上のパッケージをローカルにインストールしたり、自分のパッケージを NPM に公開したりと、Node.js の開発に欠かせないツールである。Node.js をインストールすると自動的に npm もインストールされる。

ちなみに: 似たような CLI として Facebook が開発した Yarn がある。これは npm の色々な欠点(スピードなど)を補うように作られたものであり、かなり人気がある。npm パッケージの README でしばしば npm と yarn でインストールする方法が両方書かれていたり、時には「yarn を使用することを推奨する」と書かれていたりする。しかし、npm も改善されてきており、わざわざ yarn をインストールして使用するメリットはあまりないと筆者は考えている。特に初心者にとっては、スタンダードでないツールを使用すると無駄に学ぶことが増えるのでおすすめしない。

## パッケージ

パッケージとはプログラムがたくさん入ったディレクトリ/フォルダーのようなもの[^pack]であり、NPM で公開されているほとんどのパッケージは外から使うためのライブラリである。世の中にあるパッケージを使えば自分で一からコードを書かなくとも高度な機能を実現することができる。

[^pack]: 実際にはjsファイルとは限らず、cssファイルだけのライブラリなども存在する。

パッケージを利用するとなったときに、「直接パッケージをダウンロードして自分のプロジェクトに含めればよいのではないか」、さらには「Git リポジトリに含めてよいのではないか」と思うかもしれない。しかし、もしそのパッケージに新しいバージョンが出て、バグ修正や機能の追加がされたとき、自分のプロジェクト内のパッケージもアップデートしたくなるかもしれない。そうすると自分のプロジェクト内のファイルを手動で更新しなければならなくなる。シェルを使えば簡単に更新できるかもしれないが、全く同じコードが複数の場所(今の場合 NPM と自分の Git リポジトリ)で管理されるというのは無駄であり、「本当に自分のプロジェクトに含まれているパッケージは NPM 上のパッケージと内容が一致しているのか」という懸念も生じる。

よって外部のパッケージは自分のプロジェクトに含めるのではなく、「このプロジェクトは NPM のこのパッケージに依存している」、という依存情報だけを「宣言」するのがよいということに落ち着く。このような依存先のパッケージをこの界隈の言葉で dependency (depend=依存する)と呼ぶ。NPM ではパッケージは別のパッケージに依存し、そのパッケージがまた別のパッケージに依存し...と、パッケージが dependency のネットワークを成すことになる。これは Maven、pip といった他の言語のパッケージレジストリについても同じである。例えば express というパッケージの dependency tree は下の図のようになっている。

![npm-network-express.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/a9e5a9ee-9090-87ef-c83c-688bda887a6d.png)
(from [NPMGraph](http://npm.broofa.com))


## プロジェクト=パッケージ

Note: ここでいうプロジェクトとは業務上のプロジェクトという意味ではなく、コードのまとまりのことである。Visual Studio や Intellij のような開発環境でも「Open Project」みたいなボタンがあるがこれのことである。

通常 Node.js のプロジェクトは npm のパッケージ 1 個に対応する。つまり、プロジェクトを 1 つ開発するということは、公開するしないにかかわらず**パッケージを 1 つ作り上げるということ**に相当する。よって Node.js で開発するためには、パッケージというものがどういう構造になっているのかを理解する必要がある。

npm にとってパッケージというのは**`package.json`というファイルの親ディレクトリに含まれるファイル群**である。例えばディレクトリ`~/projects/my-project/`に`package.json`があれば、`~/projects/my-project/`がそのプロジェクトのルートディレクトリ(一番根っこのディレクトリ)となる。npm のコマンドは常にルートディレクトリで実行することになる。

依存パッケージを自分のパッケージに含めないと先に述べたが、実際には依存パッケージのファイルをローカルのどこかにダウンロードする必要がある。npm で依存パッケージをインストールすると、それらはルートディレクトリ直下の`node_modules`ディレクトリにあくまで**仮置場**としてダウンロードされる。そのため、このディレクトリは`.gitignore`で Git リポジトリから除外するのが普通であり、このディレクトリ内のファイルは**編集してはいけない**。

これ以外には `.npmrc` や `npm-shrinkwrap.json`、後述の `package-lock.json` などの特別なファイルがある。`.npmrc` と `npm-shrinkwrap.json` はとりあえず必要ないのでここでは説明しない。

以上のファイル、ディレクトリ以外についてはどのようにファイルをおいてもよい(当然、使用するフレームワークなどによってはファイルの配置に制約を受けることはある)。

## パッケージの作成

**Note:** 以降、Node.js と npm (v6 以上)が正常にインストールされておりそれぞれコマンドラインで`node`、`npm`という名前でアクセスできる(PATH に登録されている)ことを前提とする。

プロジェクト、すなわちパッケージを一から作成するにはまず`package.json`を作成することから始まる(ただし、例えば React のようにプロジェクトを生成する CLI パッケージが用意されている場合は代わりにそれを用いればよい)。

以下を実行すれば、パッケージ名などがインタラクティブに質問されすべて答えると`package.json`が生成される。質問をすべてスキップするには`-y`オプションをつければよい。

```bash
# 現在のディレクトリに package.json を生成する
npm init
```

この時点では dependency は何もない。

## package.json の中身

世の中のパッケージの`package.json`は例えば以下のようになっている。`.json`という拡張子からもわかるように、このファイルは JSON と呼ばれる形式に従っている。

```json
{
  "name": "my-package",
  "description": "my first package ever",
  "license": "MIT",
  "version": "1.0.0",
  "bin": "./cli.js",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "axios": "^0.18.0"
  },
  "devDependencies": {
    "eslint": "^5.14.1"
  }
}
```

name, version, description, license などのデータは単なるパッケージのメタデータであり、パッケージを公開するつもりがないならばあまり気にする必要はない。機能的に重要なのは bin, main, dependencies, devDependencies, scripts であり、以下でそれぞれ説明する。

`package.json` で指定できるすべての項目は[ここ](https://docs.npmjs.com/configuring-npm/package-json.html)で確認できる。

### dependencies & devDependencies

先述の通り、dependency とはそのパッケージが依存する別のパッケージであり、`package.json` には dependency のパッケージ名とバージョンが書かれる。これらに変更を加える方法は後で説明する。

dependencies と devDependencies の違いであるが、意味としては前者は実行に必要なパッケージ、後者は開発やテストにのみ必要なパッケージである。機能的な違いとしては、あるパッケージ A を dependency としてインストールするときにデフォルトでは A の dependencies はインストールされるが A の devDependencies はインストールされない。

例えば JavaScript の Linter である ESLint は開発のときにのみ必要で実行するときには不要なため、devDep としてインストールするのが適切である。(ただし、どちらに分類すべきか自明でない場合もある。例えば Webpack を用いてフロントエンド(ブラウザ)アプリを開発するときは、そもそもパッケージとして外から実行できるものではないため分類しがたい。この場合は、Webpack などのビルドツールは devDep として、jQuery などのフロントエンドに含まれるべきライブラリは dep として指定するのが普通である。)

自分のパッケージを NPM で公開するつもりならば、両者の違いに気をつける必要がある。公開しないとしても、どのパッケージが実行に必要なのかを意識し dep と devDep を使い分けることは、dependency を整理する上でも良い習慣だと思われる。

`package.json`では次のような形式で dependency が指定される。このときバージョンの先頭にキャレット`^`またはチルダ`~`がついていることが多い。

```json
  "dependencies": {
    "express": "^4.17.1",
    "request": "~2.88.0"
  },
```

そもそもバージョンは`.`で区切られた 3 つの数字から構成されているが、これは **Semver** (Semantic Versioning) という規則に則っており、下の図のようにそれぞれの数字に意味がある(無論、このルールに従うのは開発者の責任である)。「時間.分.秒」のように、順に細かくなっていくとイメージすればよい。

![fig-semver.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/ebe6dfbd-ee4a-5879-2bde-99141b92b351.png)

あるパッケージの新しいバージョンが公開されるとき、1つ目の数字 Major が上がっていれば「大きなAPIの変更があった」という意味である。Major が変われば使う側のコードもしばしば変更する必要があるだろう。Minor が上がれば「新しい機能が追加された」という意味であり、使用する側のコードはおそらく変更する必要はない。Patch は「バグが修正された」という意味であり、これも自分のコードを変更する必要はない。

dependencies または devDependencies でバージョンを指定する時、キャレット`^`をつけると「Major は一致し Minor と Patch は指定されたもの以上」という意味になり、チルダ`~`をつけると「Major と Minor は一致し Patch は指定されたもの以上」という意味になる。例えば、`^4.17.1`は`4.17.10`や`4.20.0`にマッチするが`4.16.8`や`3.20.4`にはマッチしない。`~2.88.0`は`2.88.5`にマッチするが`2.90.3`にはマッチしない。逆に `^` も `~` もつけなければ ちょうどそのバージョンにのみマッチする。

後述の `package-lock.json` ファイルが存在せず、dependency がローカルにインストールされていない状態で `npm install` を実行すると、上記のルールにしたがい、`package.json` に指定されたバージョンにマッチする中で**最も新しいバージョン**がインストールされる。

NOTE: バージョンを固定したいときは後述の`package-lock.json`を使えばよいため、わざわざキャレットやチルダを外す必要はない。

### scripts

scripts は簡単に言えばコマンドのエイリアスであり、任意のコマンド(i.e. コマンドラインのコマンド)に名前をつけることができる。例えば以下のような形である。

```json
  "scripts": {
    "start": "node index.js",
    "lint": "eslint"
  },
```

ここに記載された script は`npm run <name>`で実行できる。例えば上の`lint`は`npm run lint`で実行できる。

ただしいくつかの名前は特別扱いされ、例えば`start`は普通プログラムを実行するコマンドを指定し、`npm start`で実行できる。`test`はテストを実行するコマンドを普通指定し、`npm test`で実行できる。また、script 名の先頭に`pre`がついていると、ついてない名前の script が実行される前に自動的に実行される。例えば script`build`と`prebuild`が存在するとき、`npm run build`を実行すると、`build`の前に`prebuild`が自動的に実行される。逆に`post`を先頭につけると元の名前の script の後に実行される。

`preinstall`と`postinstall`はそのパッケージをインストールする前後で自動的に実行されるものであり、何らかのパッケージのインストールが失敗するときはそのパッケージの`preinstall`や`postinstall`をチェックすると解決することもある。

`npm run <name>`は簡単な task runner[^2]として使えるため、何度も実行するコマンドは script として登録すると開発を効率化できる。また、scripts はプロジェクトのテンプレートに最初から含まれていることが多い。例えば、react のプロジェクトを`create-react-app`で生成すると`start`や`build`といった script が用意されており、すぐに開発やビルドができるようにセットアップされている。

[^2]: task runner とは複雑なビルド、テストなどの処理を行いやすくするツールのことであり、Node.js では gulp.js や Grunt などが有名である。

### main

main は**そのパッケージを外からインポートするときにどの JavaScript ファイルが入り口であるか**を指定するものである。誰かのパッケージを外から使うときに、そのパッケージをインポートするとは具体的にどのファイルをインポートするということなのかを確認するときに見ればよい。自分のパッケージの`package.json`の main は、そのパッケージを NPM で公開しない限り重要ではない。

例えば、HTTP リクエストライブラリである request というパッケージを下のようにインポートしたいとする。Node.js では外部の JavaScript ファイルをインポートするとき`require`または`import`を使う(後者については近年新しく導入された構文でありここでは触れない)。下のコードで、この`req`という変数には具体的に何が入るのかを調べたいとする。

```js
const req = require('request')
```

ここで、request をインストールしたあとにその `package.json` (`node_modules/request/package.json`)を見ると、下のように main は`index.js`となっている。

![ss-request-packagejson.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/7bf18c91-7a10-c83e-3cc0-fb735faf648f.png)

これは、`index.js`がエクスポートした値が、`require('request')`の戻り値になることを表している。`index.js`の中身を見ると以下のようになっている。

![ss-request-export.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/7791b77f-dc7b-7daf-105f-65157708d62d.png)

`module.exports`に`request`という変数の値が代入されていることがわかる。これはこのモジュールが変数 `request` の値をエクスポートしているということである。これ以上は触れないが、この `request` という変数に代入されている値をコードをたどって調べれば、最初の変数`req`に代入される値が分かる。

### bin

これも、パッケージを外から使うときにのみ重要になる項目である。パッケージ A の `package.json` の bin に何らかの実行可能ファイルが指定されていると、パッケージ A をインストールすればそれを **CLI として実行できるようになる**。

例えば自分のパッケージの dependency としてパッケージ `my-cli` をインストールしており、`scripts` に同パッケージを実行するスクリプトを指定したとしよう。

```json
{
  "dependencies": {
    "my-cli": "~1.0.0"
  },
  "scripts": {
    "foo": "my-cli 12345"
  }
}
```

`my-cli` の `package.json` が以下のようになっているとする。

```json
{
  "bin": "./src/cli.js"
}
```

このとき、自分のパッケージで `npm run foo` を実行すると、`node_modules/my-cli/src/cli.js 12345` が実行されることになる(実際にはこの `cli.js` ファイルを直接実行しているわけではなく、`node_modules/.bin` ディレクトリに自動生成された `my-cli` というファイル(内容は `cli.js` と同じ)を実行している)。

ただし、scripts からではなくコマンドラインで直接 `my-cli` を CLI として実行したいときは、

```bash
my-cli 12345
```
と実行することはできない。なぜなら `node_modules/.bin` ディレクトリは PATH に登録されていないからである。代わりに以下のように実行する必要がある。

```bash
./node_modlues/.bin/my-cli 12345

# または

npx my-cli 12345
```

ちなみに npx の使い道はこれだけではない。詳しくは👇を参照。

[npm 5.2.0の新機能！ 「npx」でローカルパッケージを手軽に実行しよう](https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d)

一方、bin が指定されたパッケージをグローバルインストールするとコマンドラインから直接実行できるようになる。例えば今の `my-cli` をグローバルインストール(`npm install -g my-cli`)すると、直接コマンドラインで `my-cli 12345` のように実行することができる。グローバルインストールされたパッケージは、特定のローカルのパッケージの dependency としてインストールされるわけではなく、PC上のある決まった場所にインストールされるので注意されたい。

## dependency の編集

すでに`npm install`ですべての dependency をインストールした状態で、dependency を追加・削除・アップデートしたいときは普通**直接`package.json`を編集せず、npm を通じて行う**。npm コマンドで dependency を変更すると自動的に`package.json`にも反映される。もし`package.json`を直接編集した場合は再度`npm install`を実行して`node_modules`内のファイルを更新する必要がある。

**Note:** npm v4 以下では dependencies として追加するときに`--save`を指定しないと`package.json`に反映されない。

### dependency の追加/バージョン変更

devDep として追加する場合は`-D`(`--save-dev`のエイリアス)を指定する。

```bash
# dependencies に追加
npm install <package>

# devDependencies に追加
npm install -D <package>
```

バージョンを指定したい場合は `@` の後に書けば良い。

```bash
# react の v16.8.6 を追加
npm install react@16.8.6

# react の最新バージョンを追加
npm install react@latest
```

### dependency の削除

```bash
npm uninstall <package>
```

## package-lock.json

NOTE: 以降、dependencies と devDependencies をまとめて dependency と呼ぶ。

これは dependency のバージョンを lock(ロック、固定)するためのファイルであり、`npm install`の実行時に自動的に作成される。

一般に npm パッケージは他のチームメンバーの開発マシン、テスト環境、本番環境など複数の環境で実行されるため、すべての環境で全く同じバージョンの dependency をインストールしたいと思うのは自然である(バージョンが同じでなければ「ある環境ではうまく実行でき、ある環境ではエラーが出る」といったことが起こりうる)。

実は、`package.json`だけではこれは実現できない。キャレット`^`やチルダ`~`を使わなければいいのではないかと思うかもしれないが、問題はそう単純ではない。例えば自分のパッケージの dependency として`"A": "1.2.0"`と指定したとしよう。パッケージ A はまた別のパッケージ B に依存しており、`"B": "^2.5.0"`が指定されている。いま、B の最新が v2.8.0 とすると、`npm install`すれば A の v1.2.0、B の v2.8.0 がインストールされる。しばらくして B の新バージョン v2.9.0 がリリースされたとする。いま、別のマシンで自分のパッケージを`npm install`すると A の v1.2.0 と B の**v2.9.0**がインストールされる。このように、同じ`package.json`でもインストールされる dependency のバージョンが異なってしまうということが起こりうる。

これを解決するために npm v5 以降で`package-lock.json`が導入された。このファイルには dependency、dependency の dependency...と間接的なものも含めすべての dependency のバージョン(とその integrity)が記録される。

npm v6.9.0 時点では`npm install`の具体的な挙動は次のようになっている。

- `package-lock.json`が存在しないとき
  - `package.json`に基づいて dependency がインストールされ、実際にインストールされたバージョンが`package-lock.json`に書かれる。
- `package-lock.json`が存在するとき
  - `package-lock.json`に基づいてインストールされるが、`package.json`指定されたバージョンとの矛盾があれば、`package.json`が優先され、実際にインストールされたバージョンが`package-lock.json`に書かれる。

`package-lock.json`を優先したい場合は`npm ci`を実行すればよい。このコマンドは`package-lock.json`に基づいて dependency をインストールし、もし`package.json`との矛盾があればエラーを出力する。また、インストール前に `node_modules` を削除するので、クリーンインストールしたいときにも使える。

## 結局いつ `npm install` するのか

### `npm install` (パッケージ名なし)

繰り返すと、パッケージ名を指定せず単に `npm install` を実行すると、`package.json` と `package-lock.json` に基づいて dependency が**すべて**ローカル(`node_modules`)にインストールされる。

これを実行する必要があるのは、開発中のパッケージのソースコードだけ手元にある状態で dependency がインストールされていないときである。これに当てはまるのは例えば以下のケースである。

- GitHub 上のパッケージを開発/実行したいので、新しいマシン上に clone してきた
- create-react-app などの、新しくプロジェクトを生成するツールを使ってパッケージを作成した
    - そのツールが自動で `npm install` まで実行してくれることもある
- `node_modules` 内のファイルを誤っていじってしまい、一旦 `node_modules` ごと削除した

### `npm install <パッケージ名>`

パッケージ名を指定すれば、そのパッケージがローカルにインストールされる。これを使うのは、「dependency は既にすべてインストールされており、新しく dependency を追加したいとき」である。

## GitHub でパッケージのコードを見るときの注意点

NPM パッケージは高確率でソースコードが GitHub でも公開されており、ローカルにインストールすることなくコードを確認したいときに便利である。パッケージのページの Repository の欄をクリックすれば該当の repo に飛ぶことができる。

![ss-npm-express.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/8b61921b-b3d4-1a87-0a7d-a77f4f9ac75e.png)

先に述べたように、repo にあるコードがそのまま NPM にアップロードされているとは限らない。何らかのビルド処理をしたあと必要なファイルだけアップロードされることもよくあり、`package.json`の`scripts`を見ればどのようなビルド処理をしたのか推測することもできる。

パッケージの repo はしばしば NPM でのバージョンに対応した tag がつけられており、特定のバージョンのソースコードを見たいときに便利である。

![ss-express-tags.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/44b5a8f3-7f75-8065-4e1a-644f6ef74184.png)