package-lock.jsonとyarn.lockの存在理由

4 min read読了の目安(約3600字

始め

以前一人でコーディングしてた時に別に要らないと思ってpackage-lock.jsonを削除したことあります。しかし最近それがとんでもなく恐ろしいことだったと気づきました。ということで、投稿させていただきます。

1. package-lock.json、yarn.lock

1-1. 正確なバージョンを記録

npmやyarnを使うと、作成した覚えがないのにいつの間にかpackage-lock.json又はyarn.lockというファイルがディレクトリに入ってました。別に触ることがないから気にしてなかったし、削除したことすらあります。この子たちは何をやっているのでしょう?

package-lock.jsonとyarn.lockは、プロジェクトが依存してるパッケージの正確なバージョンを記録してるファイルです。npmならpackage-lock.json、yarnならyarn.lockになるだけでやってることは同じです。

1-2. サンプルコード

// package-lock.jsonの例
 "dependencies": {
   "@babel/code-frame": {
     "version": "7.8.3",
     "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
     "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
     "requires": {
       "@babel/highlight": "^7.8.3"
     }
   },
}
// yarn.lockの例
"@babel/code-frame@7.8.3", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
  version "7.8.3"
  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
  integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
  dependencies:
    "@babel/highlight" "^7.8.3"

これでlockファイルが何をやっているかはわかりました。ここで「package.jsonにもバージョン情報書いてあるしそれでいいんじゃない?」と思うかもしれませんが*(私は思いました)*、よくないということを説明していきます。

2. package.json

2-1. バージョンの範囲を記録

まず私が課題でやってたプロジェクトのpackage.jsonの一部を持ってきました。

{
  "name": "my-app",
  "version": "0.1.0",
  "dependencies": {
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "typescript": "~3.7.2"
  }
}

よく見たら、バージョンの前に^や~などの記号がついていることに気づきます。これはバージョンの範囲を表す記号です。つまり、package.jsonには正確なバージョンではなく、バージョンの範囲(version range)を記録してるということです。

この記号についても簡単に紹介したいです。が、せっかくなのでまずはその前にバージョンの表記法についても話します。

2-2. SemVer

SemVerはSemantic Versioning、文字通り意味のあるバージョン表記法です。

  • [MAJOR , MINOR, PATCH]
  1. MAJOR : 前のバージョンと互換生がないAPIの変更の時に増加
  2. MINOR:前のバージョンと互換性がありつつ機能の変更、追加の時に増加
  3. PATCH:バーグ修正の時に増加

という原則に従ってバージョンを表記しています。例えば16.14.0というバージョンなら、16がMAJOR、14がMINOR、0がPATCHの部分でしょう。

バージョンの数字の意味がわかりましたので、このままバージョンの範囲の記号について説明します。

2-3. チルダ表記 ( ~ )

MINORが明記されているならPATCHの更新を許容、そうではない場合はMINORの変更を許容

  • ~1.2.3 : MINORが明記されてるからPATCHの更新許容(1.2.3以上1.3.0未満)
  • ~1 : MINORが明記されてないからMINORの更新許容(1.0.0以上2.0.0未満)

2-4. キャレット表記 ( ^ )

一番左側にある、ゼロでないバージョニングは変えない (それ以下があがることは許容)

  • ^1.2.3:一番左側にある、ゼロでない要素がMAJORだからMINOR、PATCHの更新可能(1.2.3以上2.0.0未満)
  • ^0.2.3:一番左側にある、ゼロでない要素はMINORだからPATCHの更新可能(0.2.3以上0.3.0未満)
  • ^0.0.3:一番左側にある、ゼロでない要素はPATCHだからアップデート不可能

3. package.jsonだけだったら?

3-1. 問題

package.jsonにはパッケージバージョンの範囲が記録されてることについて話しました。これの何がまずいかというと、インストール時点によってパッケージのバージョンが違くなることです。

例えば、あるプロジェクトのpackage.jsonに"react": "^16.8.2"と書いてあるとしましょう。これは上でみたように16.8.2以上17.0.0の範囲を表します。エンジニアAはこのバージョンのreactを使って開発をしています。ある程度時間が経ってエンジニアBがプロジェクトに合流、npm iyarnコマンドでパッケージをインストールしますが、その時点ではreactの最新バージョンが16.8.7に上がっててそれがインストールされます。また結構時間が経ってチームに入ったエンジニアCが、当時reactの最新バージョンの16.9.2をインストールします。

  • エンジニアA:16.8.2バージョンのreactで開発
  • エンジニアB:16.8.7バージョンのreactで開発
  • エンジニアC:16.9.2バージョンのreactで開発

こうして同じプロジェクトのエンジニア同士でも違うバージョンをインストールしてしまうことになります。このままだったらエンジニアCが実装した機能が他のエンジニアのPCのでは動かなかったり、エンジニアAのPCでは通るテストが他のエンジニアのPCでは通らなかったりの大問題があります。

3-2. 解決

上記の問題を解決するためにpackage-lock.jsonとyarn.lockが存在します(長いのでまとめてlockファイルと呼びます)。lockファイルが生成されると、npm iyarnコマンドを実行してもパッケージの最新バージョンをインストールしません。代わりにlockファイルに明記されてるバージョンでインストールされますので、インストール時点関係無しでいつも同じバージョンがインストールされることを保証できます。

このlockファイルをプロジェクトのgitリポジトリにプッシュしておいたら、他のエンジニアもそれを共有してもらえます。こうしてチームメンバー同士に同じバージョンのパッケージが持てるようになるなりました。

終わり

ということで、1人開発なら関係ないけどチーム開発ならlockファイルを大事にするべきだということを学びました。わかりやすくかけたか日本語間違ってないか怪しいですが、読んでくださってありがとうございます(>_<)。