🕌
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