定義と結論
依存関係とは、自分のプロジェクトが動作・開発・ビルドするために必要とする外部パッケージとの関係です。 パッケージは再利用可能なコードや設定を配布できる単位で、パッケージマネージャーはその取得、バージョン解決、記録、削除を担当します。
npm install date-fns
これは単にファイルを一つダウンロードする命令ではありません。npmはプロジェクトの設定を読み、date-fnsと必要な依存を解決し、通常はnode_modules、package.json、package-lock.jsonへ結果を反映します。
なぜ理解する必要があるのか
現代のWeb開発では、フレームワーク、テスト、型検査、CSS処理などを組み合わせます。既存パッケージを使えば短時間で品質の高い機能を利用できますが、更新、脆弱性、互換性、ライセンスもプロジェクトの責任になります。
依存関係を理解すると、「自分のコードを変えていないのに環境によって動かない」「node_modulesが巨大」「lockfileの差分が大量」といった現象を層ごとに調べられます。パッケージを追加することは、コードだけでなく保守対象を追加することです。
登場人物とファイル
| 要素 | 役割 |
|---|---|
| プロジェクト | パッケージを利用する側 |
| パッケージ作者 | コード、バージョン、依存条件を公開する側 |
| npm registry | 公開パッケージとメタデータを配布する場所 |
| npm CLI | 依存グラフを解決してローカルへ配置する道具 |
package.json | 直接依存、scripts、要求環境などの宣言 |
package-lock.json | 解決された依存グラフの再現用記録 |
node_modules | インストールされた実体の配置先 |
「npm」は登録サービス、Webサイト、CLIなどを指すことがあり、文脈で区別します。また、ライブラリは用途を表す一般語、パッケージは配布単位です。一つのパッケージが複数モジュールを提供することもあります。
npm installの流れ
- npmが現在の
package.json、lockfile、設定を読む - 指定パッケージのメタデータと利用可能な版を確認する
- バージョン範囲、既存lockfile、Node.js要件などから版を解決する
- そのパッケージが必要とする依存も再帰的に解決する
- パッケージを取得し、整合性情報を照合して配置する
- 直接依存の宣言とlockfileを必要に応じて更新する
- パッケージが定義するライフサイクルスクリプトがあれば規則に従い実行する
ネットワーク取得が毎回起きるとは限らず、キャッシュが利用される場合もあります。配置方法もnpmの版や依存関係により異なるため、node_modulesの形を手で前提にしないことが大切です。
直接依存と推移的依存

自分が明示的に選びpackage.jsonへ記録したものが直接依存です。そのパッケージが必要とする別パッケージは推移的依存です。
my-app
└─ package A(直接依存)
└─ package B(推移的依存)
推移的依存は自分が選んでいなくても実行経路に入り得ます。Aと別のCが異なる範囲のBを要求すれば、npmは条件を満たす配置を探します。これが依存グラフです。node_modulesが直接依存の数より大きくなる主因でもあります。
推移的依存を直接package.jsonへ写す必要はありません。自分のコードから直接利用するものだけを直接依存として宣言します。
主要な分類と比較
dependenciesとdevDependencies
| 分類 | 典型例 | 判断基準 |
|---|---|---|
dependencies | サーバ実行時に読み込むライブラリ | 配布後の実行に必要か |
devDependencies | テスト、lint、型検査、ビルド道具 | 開発工程だけで必要か |
静的サイト生成器のように、本番サーバでは動かなくても成果物を作るために必要な道具があります。ホスティング環境がビルドするならdevDependenciesもインストールする必要があります。「本番で必要」の意味は配置方式で変わるため、既存プロジェクトとデプロイ先の規約に合わせます。
バージョン指定
"1.2.3"は固定版、^1.2.3は一般に同じメジャー版内の互換更新を許す範囲、~1.2.3はより狭い更新範囲です。ただし0系ではcaretの範囲がより厳しくなります。宣言した範囲と実際に解決された版は別物で、後者をlockfileが記録します。
npm、pnpm、Yarn、Bun
いずれも依存管理を行いますが、lockfile、配置、コマンド、対応機能が異なります。優劣だけで混在させず、リポジトリにあるlockfileとpackageManager指定を確認します。同じ変更で複数種類のlockfileを作らないのが基本です。
具体例:日付表示を追加する
日付計算を自作せずdate-fnsを導入するとします。まず本当に標準APIで十分でないか、保守状況、ライセンス、容量を確認します。採用後はソースで直接importするため、直接依存として追加します。
import { format } from "date-fns";
const label = format(new Date(2026, 6, 9), "yyyy-MM-dd");
別の開発者はリポジトリを取得し、lockfileに従うnpm ciで同じ依存グラフを再現できます。node_modulesをGitに含める必要は通常ありません。宣言とlockfileを共有し、実体は各環境で復元します。
よくある誤解
package.jsonだけで完全に同じ環境になる
バージョン範囲は複数の版を許すことがあります。再現性にはlockfile、Node.js/npmの版、OSやCPUなども関係します。
lockfileは自動生成だから無視してよい
アプリケーションでは通常コミットします。意図しない更新が混ざっていないかレビュー対象です。手編集ではなくパッケージマネージャーで更新します。
npm installを繰り返せば直る
症状を変えることはあっても原因特定にはなりません。エラー、実行環境、差分を保存せずにlockfileやディレクトリを消すと重要な手掛かりも失います。
脆弱性警告はすべて即時に自動修正する
到達可能性と利用環境、修正版の破壊的変更を確認します。npm audit fix --forceは依存の大幅更新を伴い得るため、内容を理解せず実行しません。
注意点とベストプラクティス
- パッケージ追加前に標準機能や既存依存で解決できないか確認する
- 名前の似たパッケージを避け、公式リポジトリ、作者、利用状況、ライセンスを確認する
package.jsonとlockfileを同じ変更としてレビューする- Node.jsとパッケージマネージャーの版をプロジェクトで揃える
- 更新は小さく行い、テストとビルドを通す
- ライフサイクルスクリプトはコードを実行し得ると理解する
- 不要になった依存はCLIで削除し、importも残っていないか調べる
デバッグと確認方法
まずnode -vとnpm -v、エラー全文、実行したディレクトリを確認します。次にgit diff -- package.json package-lock.jsonで宣言と解決結果の変化を見ます。
npm explain date-fns
npm ls date-fns
npm outdated
npm explainはなぜそのパッケージが入ったか、npm lsは依存ツリー上の位置、npm outdatedは宣言範囲と更新候補の確認に使えます。CIやクリーン環境ではnpm ciを使うとlockfileと宣言の不一致を検出しやすくなります。
解決しない場合は、失敗が取得、版解決、ビルド、実行のどの段階かを分けます。404ならパッケージ名やregistry、ERESOLVEなら競合する要求範囲、実行時のmodule not foundならimport名、インストール場所、配布設定を確認します。
まとめ
依存関係は外部パッケージとの契約であり、パッケージマネージャーは依存グラフを解決して宣言、固定記録、実体を整えます。直接依存と推移的依存、package.jsonとlockfileの役割を分けると、インストールの差分を読めます。便利さだけで追加せず、導入理由、更新方法、削除条件まで考えることが依存管理の基本です。