Theme
SD MILIEU

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,Cpackage-lock.json が更新されないと実際に脆弱性が解決されない。

逆にデメリットとして、semverを無視したパッケージの更新をされると、それに依存しているパッケージがバコバコ不具合を起こすというのがありそう。

以下はpackage-lock.jsonのドキュメントの和訳。いつもどおりDeepLにかけて違和感あるとこを整えた程度だけど。


説明

package-lock.jsonnpmnode_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.jsonnpm-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 依存症のオプション依存症には devoptional の両方が設定されます)。
  • inBundle: パッケージがバンドルされている依存関係であることを示すフラグ。
  • hasInstallScript: パッケージがプリインストール、インストール、またはポストインストールスクリプトを持っていることを示すフラグです。
  • hasShrinkwrap: パッケージが npm-shrinkwrap.json ファイルを持っていることを示すフラグ。
  • bin, license, engines, dependencies, optionalDependencies: package.json のフィールド

dependencies

lockfileVersion: 1 を使用する npm のバージョンをサポートするためのレガシーデータです。 パッケージ名と依存関係オブジェクトの対応付けです。オブジェクトの構造は厳密に階層化されているため、シンボリックリンクの依存関係を表現するのはやや困難な場合があります。

独自追記: 古いバージョンをサポートするための項目ということで、2022.6.7現在では知ってても意味ないと思うので省略