🕌

amplify.ymlにおけるnode_modulesのキャッシュと.npmのキャッシュ

に公開

はじめに

amplify.ymlにcacheとして

 cache:
    paths:
      - ~/.npm/**/*

 cache:
    paths:
      - node_modules/**/*

とかく方法があって、両者の違いを調査。

結論から言うと、
上のほうがクリーンで簡単に安全なキャッシュ設計ができる。下のほうが高速だが、適切なキャッシュキー設計が必須。設定を間違えると~/.npmキャッシュよりも危険になる

~/.npm/の場合

場所: ユーザーのホームディレクトリ

package-lock.jsonの変更がある場合の挙動

  • npmキャッシュから必要なパッケージを取得
  • 新しい依存関係に従ってnode_modulesを再構築
  • 確実に最新の依存関係でインストールが完了

package-lock.jsonの変更がない場合の挙動

  • キャッシュから圧縮されたパッケージファイルを取得
  • npm installが実行される(毎回)
  • キャッシュされたパッケージを展開してnode_modulesを構築
  • 依存関係の整合性チェックが実行される
  • 数十秒〜数分で完了(ダウンロード時間は短縮)

node_modulesの場合

場所: プロジェクトルート直下

package-lock.jsonの変更がある場合の挙動

  • package-lock.jsonのハッシュ値が変更を検知
  • 新しいキャッシュキーが生成される
  • キャッシュミスが発生し、npm installが実行される
  • 新しいnode_modulesが構築される
  • 最新の依存関係でインストールが完了

package-lock.jsonの変更がない場合の挙動

  • package-lock.jsonのハッシュ値が同じなのでキャッシュヒット
  • 既存のnode_modulesディレクトリがそのまま復元される
  • npm installはスキップされる(またはnpm ci --offlineで高速チェック)
  • アプリケーションが即座に利用可能
  • 数秒で完了

注意点
※npm installをスキップしている
※キャッシュキーがpackage-lock.jsonが変更されたら必ず新しいキーになることを担保しなければならない。これがうまくいかないとpackage-lock.jsonが変更されたのに古いnode_modulesを使用してしまい、依存関係の不整合が発生する。
※適切なキャッシュキー例: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
※不適切なキー例: ${{ runner.os }}-node-modules(固定キー)

node_modulesをキャッシュしていて、package-lock.jsonに応じてキー更新がされないとどうなる?

シナリオ: 他の開発者が@vue/compiler-sfcを更新、package-lock.jsonを更新した。自分の手元でnpm installしてみる。

before:

node_modules/
├── nuxt/
└── @vue/
    └── compiler-sfc@3.4.0/  # 古いバージョン

after:

node_modules/
├── nuxt/
└── @vue/
    └── compiler-sfc@3.4.15/  # 古いバージョン

nuxtは@vue/compiler-sfc@3.4.15を期待
実際には既存のnode_modulesディレクトリをそのまま参照してしまい、@vue/compiler-sfc@3.4.0が存在
ランタイムエラーや型エラーが発生

比較表

パフォーマンス比較(package-lock.json変更なし)

キャッシュ方式 実行時間 ネットワーク使用 CPU使用率
node_modules 5〜15秒 なし 低い
~/.npm 30秒〜3分 レジストリ確認あり 高め

それぞれに保存されるデータ

node_modules/

  • 展開済みの実際のパッケージファイル
  • JavaScript/TypeScriptファイル
  • 実行可能ファイル(.bin/ディレクトリ)
  • そのまま使用可能な状態

~/.npm/

  • 圧縮されたパッケージファイルのキャッシュ
  • メタデータ(パッケージ情報、バージョン情報)
  • 展開処理が必要

Discussion