

プロ太先生、package-lock.jsonがバージョンを固定するファイルだってことは分かったんですが、なんでそんなに重要なんですか?package.jsonがあれば十分な気がするんですが…

いい質問だね!じゃあ具体例で説明してみよう。
まず、package.jsonの中身を見てみて。
{
"dependencies": {
"react": "^18.2.0",
"lodash": "^4.17.21"
}
}

バージョンの前に「^」マークが付いてますね。

そう!この「^」がくせ者なんだ。これは「このバージョン以上で、メジャーバージョンが同じもの」という意味なんだよ。
バージョンの読み方を理解しよう

バージョン番号は「メジャー.マイナー.パッチ」の形式になってる。
例えば、18.2.0 なら
- メジャー:18
- マイナー:2
- パッチ:0

なるほど!

「^18.2.0」は以下のバージョンを受け入れるんだ。
- ✅ 18.2.0(指定通り)
- ✅ 18.2.5(パッチアップデート)
- ✅ 18.3.0(マイナーアップデート)
- ❌ 19.0.0(メジャーアップデート)
問題が起きる具体例

でも、新しいバージョンの方がバグ修正されてて良いんじゃないですか?

それが落とし穴なんだよ。実際に起きた問題を見てみよう。
ケース1:チーム開発での悲劇

太郎くんが1月にプロジェクトを作ったとき、以下のバージョンだったとします。
npm install時点での最新バージョン:
- react: 18.2.0
- lodash: 4.17.21

はい。

そして、3月に花子さんが同じプロジェクトで npm install
したとき、
npm install時点での最新バージョン:
- react: 18.2.7(パッチが更新された!)
- lodash: 4.17.22(新バージョンがリリースされた!)

あっ!同じpackage.jsonなのに、インストールされるバージョンが違う!

その通り!そして花子さんの環境では新しいバージョンの影響でアプリが動かなくなった。
// lodash 4.17.21では動いていたコード
const result = _.someFunction(data);
// lodash 4.17.22で仕様が変更されてエラーに!
// TypeError: _.someFunction is not a function

うわあ…それは困りますね。
ケース2:本番環境での災難

さらに恐ろしいのは本番デプロイ時だ。開発環境では問題なく動いていたのに…
# 開発環境(1月にnpm install)
react: 18.2.0 ← 動作OK
# 本番環境(3月にデプロイ時にnpm install)
react: 18.2.7 ← 微妙な仕様変更でエラー発生!

えー!本番で止まったら大変ですよ!

実際に某有名サービスで、依存パッケージの自動更新によってサイト全体が数時間ダウンしたことがあるんだ。
package-lock.jsonが解決する問題

package-lock.jsonがあると、こんな風に完全に固定される
{
"name": "my-app",
"lockfileVersion": 2,
"dependencies": {
"react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
}

今度はバージョンが完全に固定されてますね!

そう!このファイルがあれば、
- 太郎くんが
npm install
→ react 18.2.0 - 花子さんが
npm install
→ react 18.2.0 - 本番環境で
npm install
→ react 18.2.0
すべて同じバージョンがインストールされる!
さらに複雑な依存関係の問題

でも、これだけなら package.json で「^」を取って「18.2.0」って書けば良いんじゃないですか?

鋭い質問だね!でも実は、依存関係はもっと複雑なんだ。
間接的な依存関係

君がインストールするパッケージも、他のパッケージに依存してるんだよ。
あなたのアプリ
├── react (18.2.0)
│ ├── loose-envify (^1.1.0) ← reactが依存
│ └── js-tokens (^3.0.0 || ^4.0.0) ← reactが依存
└── lodash (4.17.21)
└── (依存なし)

あー、パッケージの中に更にパッケージが…

実際のプロジェクトだと、こんな感じになる
node_modules/
├── react/
├── lodash/
├── loose-envify/
├── js-tokens/
├── object-assign/
├── prop-types/
├── scheduler/
├── react-dom/
├── ...(数百個のパッケージ)

うわー、こんなにたくさん!
バージョン競合の問題

さらに複雑な問題もある。例えば、、、
あなたのアプリ
├── パッケージA
│ └── lodash (^4.17.0) ← 4.17.5をインストール
└── パッケージB
└── lodash (^4.16.0) ← 4.17.5でも動く

同じlodashを2つのパッケージが使ってますね。

そう。この場合、どのバージョンのlodashをインストールするかは、npmの複雑なアルゴリズムで決まる。package-lock.jsonがないと、毎回違う結果になる可能性があるんだ。
実際の開発現場での体験談

実際にどんな問題が起きるんですか?

僕が経験した実例を話そう。
体験談1:「僕の環境では動くんですが…」

新人のエンジニアが「僕の環境では動くんですが、テストサーバーでエラーになります」って相談に来たことがある。
# 新人エンジニアのローカル環境
moment.js: 2.29.1 ← 古いバージョン
# テストサーバー
moment.js: 2.29.4 ← 新しいバージョンで仕様変更

あー、バージョンの違いで動作が変わったんですね。

結局、package-lock.jsonがなかったせいで、環境ごとに違うバージョンがインストールされてた。修正に半日かかったよ。
体験談2:深夜の緊急対応

もっと深刻だったのは、深夜に本番サーバーでアプリがクラッシュした件。
# 緊急修正をデプロイしたら...
npm install実行
→ 依存パッケージが最新版に更新
→ 新しいバグが混入
→ サービス全停止!

うわー、それは大変ですね!

結局、原因を特定して修正するまで3時間かかった。package-lock.jsonがあれば防げた問題だったんだ。
まとめ:なぜpackage-lock.jsonが必要か

整理すると、package-lock.jsonは以下の問題を解決するんだ。
1. 環境差異の解決
- 開発者Aの環境 = 開発者Bの環境 = 本番環境
- 「僕の環境では動く」問題の根絶
2. 時間差による問題の解決
- 1月にセットアップ = 6月にセットアップ
- 依存パッケージの自動更新を防ぐ
3. 複雑な依存関係の管理
- 間接的な依存関係も含めて完全に固定
- バージョン競合の問題を回避
4. デプロイの安定性
- 本番環境で予期しない問題が起きるリスクを軽減
- 緊急対応時の余計なトラブルを防ぐ

なるほど!package.jsonが「何が必要か」を書いてて、package-lock.jsonが「具体的にどのバージョンを使うか」を記録してるんですね。

完璧な理解だ!料理で例えると、
●package.json:「醤油、砂糖、みりんを使う」(レシピ)
●package-lock.json:「キッコーマン醤油500ml、三温糖200g、本みりん300ml」(具体的な商品指定)

分かりやすい!同じレシピでも、使う商品が違うと味が変わっちゃいますもんね。

そういうこと!だからpackage-lock.jsonは、チーム開発や本番運用では絶対に必要なファイルなんだよ。