npmとパッケージ管理
パッケージ管理とは
パッケージ管理は、他人が作ったライブラリ(パッケージ)を導入・更新・削除し、バージョンの整合性を保つ仕組みです。JavaScript界では npm(公式には頭字語ではないとされるが、通称 Node Package Manager)が標準で、[[Node.js]] に同梱されています。
npm install react # 依存に追加
npm install -D vitest # 開発時のみの依存に追加
npm run build # package.json の scripts を実行
初学者向けポイント
package.json= プロジェクトの取扱説明書。依存パッケージと実行スクリプトを記録するpackage-lock.json= 依存の正確なバージョンを固定するファイル。必ずコミットするnode_modules/= 実際のパッケージ本体。巨大なので [[Git]] にはコミットしない(.gitignore対象)
セマンティックバージョニング
バージョン 1.2.3 は「メジャー.マイナー.パッチ」を意味します。
| 記法 | 意味 |
|---|---|
^1.2.3 | 1.x.x の範囲で更新を許可(破壊的変更なし) |
~1.2.3 | 1.2.x の範囲で更新を許可 |
1.2.3 | 完全固定 |
npm install で追加すると既定で ^ が付きます。数字が何を約束しているかは [[セマンティックバージョニング]] にまとまっています。
installとciの使い分け
依存を入れるコマンドは2つあり、役割がはっきり違います。
npm install—package.jsonの範囲指定を解決して入れる。package-lock.jsonが書き換わることがあるnpm ci— ロックに書かれた正確なバージョンだけを入れる。node_modules/を消して作り直し、ロックは更新しない
npm ci は package.json とロックが食い違うとその場でエラーで止まります。これは不便ではなく安全装置で、「ロックの更新を忘れたまま push した」状態を検知してくれます。
目安は「依存を変えたいときは install、再現したいときは ci」です。[[CI/CD]] や [[Docker]] のイメージビルドでは ci を使います。
依存はツリーになる
直接の依存が10個でも、node_modules/ には数百から千を超えるパッケージが並びます。入れたものがさらに別のパッケージを必要とするためで、これを推移的依存と呼びます。「入れた覚えのないパッケージ」に脆弱性やライセンスの問題が見つかるのはこのためです。
npm ls lodash # lodash を要求しているパッケージを木構造で表示
npm outdated # 新しい版が出ている依存の一覧
npm audit # 既知の脆弱性を検出
警告に反射的な npm audit fix --force は危険です。メジャーバージョンを飛び越える更新まで行い、破壊的変更でアプリが動かなくなります。更新後は必ず [[自動テスト]] を通してください。
dependencies と devDependencies の分かれ目も「本番で動かすときに必要か」の一点です。
scriptsと自動実行されるコード
package.json の scripts は、プロジェクト固有のコマンドに名前を付ける場所です。
{
"scripts": {
"build": "tsc -b && vite build",
"test": "vitest run"
}
}
npm run build と打てば誰でも同じ手順を実行できます。README の手順書より強いのは、手順がリポジトリに入り [[Git]] で履歴を追える点です。
注意が要るのは postinstall のようなインストール時に自動実行されるスクリプトです。npm install した時点で、そのパッケージのコードが自分のマシンで動きます。公開権限が乗っ取られる事故(サプライチェーン攻撃)や、名前が1文字違う偽パッケージを入れる事故が起きるのはこの経路です。入れる前に名前の綴り・週間ダウンロード数・最終更新日を見る習慣が効きます。
つまずきやすいところ
- ロックの競合を手で直さない — マージで
package-lock.jsonが衝突したらpackage.json側だけ解決し、npm installで作り直します。数万行のJSONを手で直すと壊れた依存グラフができます - 消して入れ直す前に原因を見る —
node_modules/を消してnpm ciで直る不具合はありますが、理由が分からないと再発します。ロックの変更や [[Node.js]] の版の差を先に疑います - グローバルインストールは最小限に —
-gで入れた道具はプロジェクトに記録されず、他の人の環境には存在しません
npmから次に読む地図
npm は「他人のコードを借りる窓口」です。周辺の記事は、借りる前・借りた後・動かす場面に分かれます。
何をどの版で借りるか決める
^1.2.3 の ^ が何を許すのかは、[[セマンティックバージョニング]] のルールがそのまま根拠です。この記法を読めると、更新して安全かを自分で判断できます。同じ「リポジトリから依存ごと取得する」考え方をOSの世界で見るなら [[Linuxパッケージ管理]] へ。
借りるものを点検する
外部のコードを取り込むとは、その弱点と利用条件を引き受けることです。[[脆弱性管理とCVE]] は弱点をどう優先して直すか、[[OSSとライセンス]] はそもそも使ってよい条件かを扱います。どちらも依存ツリーの奥まで効いてきます。
借りたコードを動かす
サーバー側で動かす土台が [[Node.js]]、書く言語が [[JavaScript]] と [[TypeScript]]。ブラウザへ届けるには [[ESモジュールとバンドラ]] のバンドル工程が要り、道具として [[Vite]] があります。
チームと自動化に載せる
ロックファイルを [[Git]] で共有すると全員の版が揃い、それを毎回機械で再現するのが [[CI/CD]] の npm ci です。コマンドを打つ場所の基礎は [[シェルとコマンドライン]] にあります。
関連技術とのつながり
- [[Node.js]] — npmの実行基盤
- [[Git]] — lockファイルの共有でチーム全員の環境を揃える
- [[CI/CD]] — CI上での
npm ciによる再現性のあるインストール - [[セマンティックバージョニング]] —
^や~の範囲指定が意味を持つ前提 - [[脆弱性管理とCVE]] / [[OSSとライセンス]] — 依存ツリーの奥まで点検する2つの観点
Q: `npm ci` の説明として正しいのはどれ?
- [x] package-lock.json に書かれた正確なバージョンだけを入れ、ロックファイルは更新しない
- [ ] package.json の範囲指定を解決し直し、必要ならロックファイルを書き換える
- [ ] 依存を入れずに脆弱性だけを検査する
解説: 再現したいときは ci、依存を変えたいときは install です。ci は package.json とロックが食い違うとエラーで止まる安全装置になっています。
Q: 「入れた覚えのないパッケージ」が node_modules に大量に入るのはなぜ?
- [ ] npm が人気パッケージを自動で追加するから
- [x] 入れたパッケージがさらに別のパッケージに依存しているから(推移的依存)
- [ ] package-lock.json が壊れているから
解説: 直接の依存が数個でも、その依存の依存をたどると数百以上になります。誰が引っ張っているかは `npm ls <パッケージ名>` で辿れます。
Q: `postinstall` のようなスクリプトについて、本文が注意点として挙げているのはどれ?
- [ ] 実行に管理者権限が必ず必要になる
- [ ] package.json に書いても CI では動かない
- [x] npm install した時点でパッケージ側のコードが自分のマシンで動く
解説: インストールしただけで他人のコードが実行されるため、乗っ取られたパッケージや名前の似た偽パッケージを入れる事故の経路になります。