2021-11-16
npmパッケージのpackage-lock.jsonは無視されるということを知った
なんかどっかのGitHubのissueを見ている過程で、掲題の通りnpmパッケージの package-lock.json
は無視されるということを知った。
実際、classnamesのリポジトリには package-lock.json
が存在しているが、 node_modules
の中の classnames
を見てみると package-lock.json
が存在しない。
なもんで、同じパッケージAのver1.0.0を使用しているプロジェクトでも、パッケージAが依存しているパッケージBのバージョンが異なる事で挙動が変わることがありうる。
これ怖くね?って一瞬思ったけど、 package-lock.json
の無効化には確かにメリットがあるなと思った。
例えば、以下のような依存ツリーになっているとする
- packageA
- packageB
- packageC
- packageD
で、 packageD
に脆弱性が発見された際に、 package-lock.json
が有効だと packageA,B,C
の package-lock.json
が更新されないと実際に脆弱性が解決されない。
逆にデメリットとして、semverを無視したパッケージの更新をされると、それに依存しているパッケージがバコバコ不具合を起こすというのがありそう。
以下はpackage-lock.jsonのドキュメントの和訳。いつもどおりDeepLにかけて違和感あるとこを整えた程度だけど。
説明
package-lock.json
は npm
が node_modules
ツリーか package.json
を変更したときに自動的に生成されます。
これは、生成された正確なツリーを記述しており、その後のインストールでは、途中の依存関係の更新に関係なく、同一のツリーを生成することができます。
このファイルは、ソースリポジトリにコミットするためのもので、さまざまな目的をもっています。
- チームメイト、デプロイメント、継続的インテグレーションが全く同じ依存関係をインストールすることが保証されるような、依存関係ツリーの単一の表現を記述すること。
- ディレクトリ自体をコミットすることなく、
node_modules
の以前の状態に「タイムトラベル」できる機能を提供する。 - ソースコントロールの差分を読みやすくすることで、ツリーの変更の可視化を促進します。
npm
が以前にインストールされたパッケージの繰り返されるメタデータ解決をスキップできるようにすることで、インストールプロセスを最適化します。- npm v7では、パッケージツリーの全体像を把握するのに十分な情報がロックファイルに含まれているため、
package.json
ファイルを読む必要性が減り、大幅なパフォーマンスの改善が可能になりました。
package-lock.json
vs npm-shrinkwrap.json
この2つのファイルは同じフォーマットで、プロジェクトのルートで同じような機能を果たします。
違いは、package-lock.json
は公開することができず、ルートプロジェクト以外の場所で見つかった場合は無視されることです。
一方、npm-shrinkwrap.json
では公開が可能で、遭遇した地点から依存関係ツリーを定義していきます。CLIツールのデプロイや、本番用パッケージの作成に公開処理を使用する場合を除き、これは推奨されません。
プロジェクトのルートに package-lock.json
と npm-shrinkwrap.json
の両方が存在する場合、 npm-shrinkwrap.json
が優先され、package-lock.json
は無視されることになります。
隠しロックファイル
node_modules
フォルダを繰り返し処理するのを避けるため、v7 の npm では node_modules/.package-lock.json
にある「隠し」ロックファイルを使用します。これはツリーに関する情報を含んでおり、以下の条件を満たす場合に、 node_modules
階層全体を読み込む代わりに使用される。
- 参照するパッケージフォルダすべてが
node_modules
階層に存在する。 node_modules
階層に、ロックファイルに記載されていないパッケージフォルダが存在しない。- ファイルの更新時刻が、それが参照しているすべてのパッケージフォルダと少なくとも同じ時刻であること。
つまり、隠されたロックファイルは、パッケージツリーの最新の更新の一部として作成された場合にのみ、関連性があるのです。他のCLIが何らかの形でツリーを変更した場合、それが検出され、隠されたロックファイルは無視されます。
パッケージフォルダの更新時間に影響を与えないように、パッケージの内容を手動で変更することが可能であることに注意してください。例えば、node_modules/foo/lib/bar.js
にファイルを追加しても、node_modules/foo
の修正時刻はこの変更を反映しません。node_modules
にあるファイルを手動で編集している場合は、一般的に node_modules/.package-lock.json
にあるファイルを削除するのが良いでしょう。
隠しロックファイルは古いバージョンの npm
では無視されるため、「通常の」ロックファイルにある後方互換性の余裕はありません。つまり、lockfileVersion: 2
ではなく、lockfileVersion: 3
になっています。
古いロックファイルの取り扱い
パッケージインストール時に npm v6 以前のロックファイルを検出すると、node_modules
ツリーまたは (空の node_modules
ツリーまたは非常に古いロックファイル形式の場合) npm
レジストリから不足している情報を取得するために自動的に更新されます。
ファイルフォーマット
name
パッケージロックするパッケージの名前です。package.json
に書かれているものと一致します。
version
パッケージロックの対象となるパッケージのバージョン。package.json
に書かれているものと一致します。
lockfileVersion
この package-lock.json
を生成する際にセマンティクスを使用した、このドキュメントのバージョン番号を 1
から始まる整数値で指定します。
npm v7 でファイルフォーマットが大きく変わり、node_modules
や npm registry で探す必要があった情報が追跡できるようになったことに注意してください。npm v7 で生成されたロックファイルには lockfileVersion: 2.0
が含まれます。
- バージョンが提供されていない:npm v5より前のバージョンの「古い」shrinkwrapファイルです。
1
: npm v5, v6 で使用されるロックファイルのバージョンです。2
: npm v7 で使用されるロックファイルのバージョンで、v1 のロックファイルとの後方互換性があります。3
: npm v7 で使用されるロックファイルのバージョンで、後方互換性の余裕がないもの。これはnode_modules/.package-lock.json
にある隠しロックファイルに使われ、npm v6 のサポートが終了したら、将来のバージョンで使われる可能性が高いです。
npmは、たとえそれがサポートするように設計されていないバージョンであっても、常にロックファイルから可能な限りのデータを取得しようとします。
packages
パッケージの位置と、そのパッケージに関する情報を含むオブジェクトを対応付けるオブジェクト。
ルートプロジェクトは通常 ""
というキーで表示され、その他のパッケージはルートプロジェクトフォルダからの相対パスで表示されます。
パッケージディスクリプタは以下のフィールドを持つ。
version
:package.json
に記載されているバージョンresolved
: パッケージが実際に解決された場所。レジストリから取得したパッケージの場合、これは tarball への url になります。git 依存の場合、これはコミット時の sha を含む完全な git URL になります。リンク依存の場合は、リンク先の場所になります。registry.npmjs.org
はマジックバリューで、「現在設定されているレジストリ」を意味します。integrity
: この場所で解凍されたアーティファクトのsha512
またはsha1
標準サブリソース完全性文字列です。link
: シンボリックリンクであることを示すフラグ。これがある場合、リンク先もロックファイルに含まれるため、他のフィールドは指定しない。dev
,optional
,devOptional
: もしそのパッケージがdevDependencies
ツリーに厳密に属しているならば、dev
は真になります。もしそのパッケージが厳密にoptionalDependencies
ツリーの一部であれば、optional
が設定されます。もしそのパッケージがdev
依存であり、かつ、dev
でない依存のオプション依存であれば、devOptional
が設定されます(dev
依存のオプション依存は、dev
依存のオプション依存になります)。(dev
依存症のオプション依存症にはdev
とoptional
の両方が設定されます)。inBundle
: パッケージがバンドルされている依存関係であることを示すフラグ。hasInstallScript
: パッケージがプリインストール、インストール、またはポストインストールスクリプトを持っていることを示すフラグです。hasShrinkwrap
: パッケージがnpm-shrinkwrap.json
ファイルを持っていることを示すフラグ。bin
,license
,engines
,dependencies
,optionalDependencies
:package.json
のフィールド
dependencies
lockfileVersion: 1
を使用する npm
のバージョンをサポートするためのレガシーデータです。 パッケージ名と依存関係オブジェクトの対応付けです。オブジェクトの構造は厳密に階層化されているため、シンボリックリンクの依存関係を表現するのはやや困難な場合があります。
独自追記: 古いバージョンをサポートするための項目ということで、2022.6.7現在では知ってても意味ないと思うので省略