1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

~/.npmとは何か ― GitHub Actionsの`cache: "npm"`から深掘りしてみた

1
Posted at

背景

  • GitHub ActionsでCI/CDの設定をしていたとき、actions/setup-node@v4cache: "npm"という設定があった
  • 「これ何をキャッシュしてるのだろう」と気になったので調べてみた

今回のワークフロー

調べるきっかけになったのは下記のymlファイル

name: GitHub Actions Demo

on: push

jobs:
  build:
    name: build application
    runs-on: ubuntu-latest

    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "npm" # ← これが何をしているのか
      - name: Install dependencies
        run: npm install
      - name: Build application
        run: npm run build
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-artifact
          path: dist

Node.jsをセットアップしてアプリケーションをビルドする、シンプルなワークフロー

ポイントはcache: "npm"の部分。これが何をキャッシュしているかを理解するには、まず~/.npmディレクトリを知る必要がある

~/.npmとは何か

npmがパッケージをダウンロードしたときに、そのtarball(圧縮ファイル)を一時的に保存しておく場所

macOS/Linuxでは~/.npm、Windowsでは%AppData%/npm-cacheに配置される

ディレクトリ構造

~/.npm/
└── _cacache/
    ├── content-v2/    # 実際のキャッシュデータ(パッケージのtarball等)
    │   └── sha512/    # SHA-512ハッシュで管理
    ├── index-v5/      # パッケージ名・バージョン → コンテンツのマッピング
    └── tmp/           # 一時ファイル

~/.npmとnode_modulesの違い

ここで混同しやすいのが~/.npmnode_modulesの関係

~/.npm(キャッシュ) node_modules(インストール先)
場所 グローバル(ユーザーごとに1つ) プロジェクトごと
中身 圧縮されたtarball + インデックス 展開済みのJSファイル等
用途 再ダウンロードを防ぐ Node.jsのrequire()/importが参照する
プロジェクト間で共有 される されない
削除しても大丈夫? OK(次回再ダウンロードされる) OK(npm installで再作成される)

~/.npmはブラウザのキャッシュのようなものとイメージすると良いかもしれない

~/.npmにキャッシュが残る流れ

npm install axiosを実行した場合の流れを具体的に追ってみる

1. パッケージのメタデータを取得

npmがレジストリ(https://registry.npmjs.org/axios)に問い合わせて、利用可能なバージョン情報や依存関係を取得する

2. バージョン解決

semverの範囲指定に基づいて、インストールするバージョンを決定する。axiosの依存パッケージ(follow-redirects等)も同様に解決される

3. キャッシュの確認

各パッケージについて、~/.npm/_cacache/内を確認する

  • index-v5/でパッケージのSHA-512ハッシュを検索
  • 対応するコンテンツがcontent-v2/に存在すれば → ダウンロード不要
  • 存在しなければ → ダウンロードが必要

4. ダウンロード(キャッシュミスの場合のみ)

レジストリからtarball(例: axios-1.7.9.tgz)をダウンロードする

5. キャッシュに保存

ダウンロードしたtarballを~/.npm/_cacache/に保存する

  • コンテンツのSHA-512ハッシュを計算
  • content-v2/sha512/<ハッシュ先頭2文字>/<次の2文字>/<完全なハッシュ> に保存
  • index-v5/にインデックスエントリを作成
  • 書き込み後に整合性を検証

6. node_modulesに展開

キャッシュから(もしくはダウンロードしたものから)tarballを./node_modules/axios/に展開する

7. プロジェクトファイルの更新

package.jsonpackage-lock.jsonが更新される

GitHub Actionsのcache: "npm"が何をしているか

ここまでの知識を踏まえて、本題に戻る

actions/setup-node@v4cache: "npm"は、内部でactions/cacheを使って**~/.npmディレクトリ全体をGitHubのキャッシュストレージに保存・復元している**

初回実行時(キャッシュなし)

  1. setup-nodeが実行される → キャッシュが見つからない
  2. npm installが全パッケージをレジストリからダウンロード
  3. ダウンロードされたtarballが~/.npm/_cacache/に保存される
  4. ジョブ終了後~/.npm全体がGitHubのキャッシュストレージに保存される

2回目以降(キャッシュあり)

  1. setup-nodeが実行される → package-lock.jsonのハッシュをキーにキャッシュを検索、ヒット
  2. GitHubのキャッシュストレージから~/.npmが復元される
  3. npm installが実行される → ~/.npmにtarballがあるのでダウンロードをスキップ
  4. tarballがnode_modules/に展開される

キャッシュキーの仕組み

キャッシュのキーはpackage-lock.jsonのハッシュ値から生成される

  • lockfileが同じ → 同じキー → キャッシュヒット
  • lockfileが変わった(依存パッケージの更新等) → 別のキー → キャッシュミス(再ダウンロード)

なぜnode_modulesではなく~/.npmをキャッシュするのか

node_modulesを直接キャッシュすれば良くね?と思うが、~/.npmをキャッシュする理由がある

  • node_modulesにはネイティブモジュール(C++でコンパイルされたバイナリ等)が含まれることがあり、OS・Node.jsバージョンが変わると動かない可能性がある
  • ~/.npmはtarball(圧縮された元データ)なので、環境に依存しないらしい

感想

  • CI/CDの設定は「動けばOK」になりがちだったが、別のツールの理解になるなぁ

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?