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.31.x.x の範囲で更新を許可(破壊的変更なし)
~1.2.31.2.x の範囲で更新を許可
1.2.3完全固定

npm install で追加すると既定で ^ が付きます。数字が何を約束しているかは [[セマンティックバージョニング]] にまとまっています。

installとciの使い分け

依存を入れるコマンドは2つあり、役割がはっきり違います。

  • npm installpackage.json の範囲指定を解決して入れる。package-lock.json が書き換わることがある
  • npm ci — ロックに書かれた正確なバージョンだけを入れる。node_modules/ を消して作り直し、ロックは更新しない

npm cipackage.json とロックが食い違うとその場でエラーで止まります。これは不便ではなく安全装置で、「ロックの更新を忘れたまま push した」状態を検知してくれます。

目安は「依存を変えたいときは install、再現したいときは ci」です。[[CI/CD]] や [[Docker]] のイメージビルドでは ci を使います。

依存はツリーになる

直接の依存が10個でも、node_modules/ には数百から千を超えるパッケージが並びます。入れたものがさらに別のパッケージを必要とするためで、これを推移的依存と呼びます。「入れた覚えのないパッケージ」に脆弱性やライセンスの問題が見つかるのはこのためです。

npm ls lodash   # lodash を要求しているパッケージを木構造で表示
npm outdated    # 新しい版が出ている依存の一覧
npm audit       # 既知の脆弱性を検出

警告に反射的な npm audit fix --force は危険です。メジャーバージョンを飛び越える更新まで行い、破壊的変更でアプリが動かなくなります。更新後は必ず [[自動テスト]] を通してください。

dependenciesdevDependencies の分かれ目も「本番で動かすときに必要か」の一点です。

scriptsと自動実行されるコード

package.jsonscripts は、プロジェクト固有のコマンドに名前を付ける場所です。

{
  "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 した時点でパッケージ側のコードが自分のマシンで動く
解説: インストールしただけで他人のコードが実行されるため、乗っ取られたパッケージや名前の似た偽パッケージを入れる事故の経路になります。