チーム全体でNode.jsバージョンを強制する方法 — Yarn Berryプラグイン活用記

2026-03-01 hit count image

.nvmrc、package.jsonのenginesフィールド、そしてYarn Berryカスタムプラグインを組み合わせて、 ローカル開発環境とCI環境の両方でNode.jsバージョンを強制する方法を紹介します。

environment

はじめに

チームプロジェクトで「自分のPCでは動くんだけど?」という言葉を聞いたことはありませんか?この問題の最も一般的な原因の一つが、Node.jsバージョンの不一致です。

開発者ごとにローカルにインストールされたNode.jsバージョンが異なると、依存関係のインストール結果が変わったり、ビルドが失敗したり、ランタイムで微妙なバグが発生したりする可能性があります。特にモノレポ環境では複数のアプリが同じNode.jsランタイムを共有するため、バージョンの統一がさらに重要になります。

この記事では、.nvmrcpackage.jsonenginesフィールド、そしてYarn Berryカスタムプラグインを組み合わせて、ローカル開発環境とCI環境の両方でNode.jsバージョンを強制する方法を紹介します。

なぜNode.jsバージョンを固定すべきなのか?

1. セキュリティ脆弱性への対応

Node.jsは定期的にセキュリティパッチをリリースしています。特定のバージョンで発見された脆弱性がチーム内の一部の開発者の環境にのみパッチされていると、開発・ステージング・プロダクション間の動作が異なる可能性があります。

2. バージョンによるビルド成果物の違い

同じソースコードでも、Node.jsバージョンによってビルド成果物が異なることがあります。ネイティブモジュールのコンパイル結果、cryptoモジュールの動作、fs APIの変更などが代表的な例です。

3. 依存関係の互換性

package-lock.jsonyarn.lockが同じでも、Node.jsバージョンが異なるとオプショナルな依存関係(optional dependencies)やプラットフォーム別パッケージのインストール結果が変わることがあります。

バージョン固定のための3つのレイヤー

一つの方法だけに頼るのではなく、複数のレイヤーを組み合わせることで、より確実にバージョンを強制できます。

レイヤー1:.nvmrc — バージョンの単一の信頼できる情報源(Single Source of Truth)

.nvmrcファイルはプロジェクトルートに配置され、そのプロジェクトで使用するNode.jsバージョンを明示します。

24.13.0

開発者はnvm useコマンドでこのバージョンに切り替えることができます:

$ nvm use
Found '/path/to/project/.nvmrc' with version <24.13.0>
Now using node v24.13.0 (npm v10.x.x)

Tip: シェル設定(.zshrc.bashrc)に自動切り替えスクリプトを追加すると、プロジェクトディレクトリに入った時に自動的に正しいNode.jsバージョンに切り替わります。

nvm自動切り替えスクリプトの設定

毎回nvm useを手動で入力するのは面倒で忘れやすいです。シェル設定ファイルに以下のスクリプトを追加すると、cdでディレクトリを移動する際に.nvmrcファイルを検知して自動的にNode.jsバージョンを切り替えてくれます。

Bash (~/.bashrc):

cdnvm() {
  command cd "$@" || return $?
  nvm_path="$(nvm_find_up .nvmrc | command tr -d '\n')"

  # .nvmrcファイルがないディレクトリに移動した場合、デフォルトバージョンに復元
  if [[ ! $nvm_path = *[^[:space:]]* ]]; then
    declare default_version
    default_version="$(nvm version default)"

    if [[ $default_version == "N/A" ]]; then
      nvm alias default node
      default_version=$(nvm version default)
    fi

    if [[ $(nvm current) != "$default_version" ]]; then
      nvm use default
    fi
  elif [[ -s "${nvm_path}/.nvmrc" && -r "${nvm_path}/.nvmrc" ]]; then
    declare nvm_version
    nvm_version=$(<"${nvm_path}/.nvmrc")

    declare locally_resolved_nvm_version
    locally_resolved_nvm_version=$(nvm ls --no-colors "$nvm_version" | command tail -1 | command tr -d '\->*' | command tr -d '[:space:]')

    if [[ "$locally_resolved_nvm_version" == "N/A" ]]; then
      nvm install "$nvm_version"
    elif [[ $(nvm current) != "$locally_resolved_nvm_version" ]]; then
      nvm use "$nvm_version"
    fi
  fi
}

alias cd='cdnvm'
cdnvm "$PWD" || exit

Zsh (~/.zshrc):

autoload -U add-zsh-hook

load-nvmrc() {
  local nvmrc_path
  nvmrc_path="$(nvm_find_nvmrc)"

  if [ -n "$nvmrc_path" ]; then
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")

    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install
    elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
      nvm use
    fi
  elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ] && [ "$(nvm version)" != "$(nvm version default)" ]; then
    echo "Reverting to nvm default version"
    nvm use default
  fi
}

add-zsh-hook chpwd load-nvmrc
load-nvmrc

上記のスクリプトを設定すると、次のように動作します:

  1. .nvmrcがあるプロジェクトディレクトリに移動すると、自動的にそのバージョンに切り替わります。
  2. そのバージョンがインストールされていない場合、自動的にnvm installを実行します。
  3. .nvmrcがないディレクトリに移動すると、nvmのデフォルト(default)バージョンに復元されます。

しかし、.nvmrcだけでは強制力がありません。自動切り替えスクリプトを設定していない開発者や、nvm useを実行し忘れた場合は、依然として異なるバージョンのNode.jsで作業することになります。

レイヤー2:package.jsonenginesフィールド

package.jsonenginesフィールドを追加して、許可されるNode.jsバージョンを宣言できます。

{
  "engines": {
    "node": "24.13.0"
  },
  "engineStrict": true
}

npmを使用する場合、プロジェクトルートの.npmrcファイルにengine-strictオプションを設定すると、npm install時にバージョンが合わない場合エラーが発生します:

# .npmrc
engine-strict=true

この設定がなければ、enginesフィールドは警告(warning)のみを出力してインストールが続行されます。engine-strict=trueを設定してはじめてエラーとして処理され、インストールが中断されます。

参考: package.jsonengineStrictフィールドはnpm v3から**非推奨(deprecated)**になっています。代わりに.npmrcファイルでengine-strict=trueを設定するのが正しい方法です。

しかし、Yarn Berry(v2以上)ではenginesフィールドをデフォルトでは検証しません。.npmrcの設定もYarn Berryには適用されません。これがまさに次のレイヤーが必要な理由です。

レイヤー3:Yarn Berryカスタムプラグイン — 本当の強制

Yarn Berryはプラグインシステムを提供しており、validateProjectフックを通じてyarn installなどのコマンド実行前にカスタム検証ロジックを挿入できます。

プラグインコード

.yarn/plugins/plugin-check-node.js:

module.exports = {
  name: 'plugin-check-node',
  factory: () => ({
    hooks: {
      validateProject(project) {
        const fs = require('fs');
        const path = require('path');

        // Yarn Berryのproject.cwdはPortablePath(POSIX形式)を返します。
        // Windowsではドライブレターの前の"/"を削除する必要があります。
        let cwd = project.cwd;
        if (process.platform === 'win32' && /^\/[A-Za-z]:/.test(cwd)) {
          cwd = cwd.slice(1);
        }
        const nvmrcPath = path.join(cwd, '.nvmrc');
        let required;
        try {
          required = fs.readFileSync(nvmrcPath, 'utf8').trim();
        } catch (error) {
          if (error && error.code === 'ENOENT') {
            throw new Error(
              `\n\x1b[31mUnable to determine required Node version.\x1b[0m\n` +
                `The .nvmrc file was not found at: ${nvmrcPath}\n\n` +
                `Please create a .nvmrc file with the required Node version,\n` +
                `or ensure you are running the command in the correct project directory.\n`
            );
          }
          throw new Error(
            `\n\x1b[31mUnable to read required Node version from .nvmrc.\x1b[0m\n` +
              `Path: ${nvmrcPath}\n` +
              `Underlying error: ${error && error.message ? error.message : String(error)}\n`
          );
        }
        const current = process.versions.node;

        if (current !== required) {
          throw new Error(
            `\n\x1b[31mNode version mismatch!\x1b[0m\n` +
              `Required: ${required}\n` +
              `Current:  ${current}\n\n` +
              `Please run: nvm use\n`
          );
        }
      },
    },
  }),
};

プラグインが行うこと

  1. .nvmrcファイルから必要なNode.jsバージョンを読み取ります。
  2. 現在実行中のNode.jsバージョン(process.versions.node)と比較します。
  3. バージョンが一致しない場合、エラーを発生させてコマンドの実行を中断します。
  4. Windows環境でのパス互換性も処理します。

プラグインの登録

.yarnrc.ymlにプラグインを登録します:

nodeLinker: node-modules

plugins:
  - .yarn/plugins/plugin-check-node.js

動作結果

正しくないNode.jsバージョンでyarn installを実行すると、次のようなエラーが出力されます:

Node version mismatch!
Required: 24.13.0
Current:  20.11.0

Please run: nvm use

このエラーはyarn installだけでなく、Yarnを通じて実行されるすべてのコマンド(yarn buildyarn devなど)で発生します。つまり、間違ったバージョンでは一切作業ができなくなります

CI環境でのバージョン統一

ローカル開発環境だけでなく、CI(Continuous Integration)環境でも同一のNode.jsバージョンを使用する必要があります。

GitHub Actionsでは、actions/setup-nodenode-version-fileオプションを通じて.nvmrcファイルを直接参照できます。.nvmrcに記録されたバージョン(例:24.13.0)をsetup-nodeアクションが読み取り、該当バージョンのNode.jsを自動的にインストールするため、ワークフローファイルにバージョンをハードコードする必要がありません:

steps:
  - name: Setup node
    uses: actions/setup-node@v4
    with:
      node-version-file: '.nvmrc'

node-version-fileオプションは.nvmrc以外にも.node-version.tool-versionsなどのファイル形式をサポートしています。重要なのは、node-versionではなくnode-version-fileを使用することで、バージョン番号をワークフローYAMLに直接記述しないという点です。これにより、.nvmrcファイルがローカル開発環境とCI環境の両方の**単一の信頼できる情報源(Single Source of Truth)**の役割を果たします。

  • 単一の信頼できる情報源(Single Source of Truth):特定のデータや設定値をただ一つのソースからのみ管理する原則です。複数の場所に同じ値を重複して記述すると更新時に漏れが発生しやすいですが、単一の情報源を設ければ一箇所を修正するだけでそれを参照するすべての場所に自動的に反映されます。ここでは.nvmrcファイルがNode.jsバージョンの単一の信頼できる情報源の役割を果たしています。

実際のプロジェクトでは、この設定を再利用可能なComposite Actionとして分離し、すべてのワークフローで一貫して使用できます。

# .github/actions/install_dependencies/action.yml
name: 'Install Dependencies'
runs:
  using: 'composite'
  steps:
    - name: Setup node
      uses: actions/setup-node@v4
      with:
        node-version-file: '.nvmrc'
    - name: Enable Yarn
      shell: bash
      run: corepack enable
    - name: Install dependencies
      shell: bash
      run: yarn install --frozen-lockfile

これにより、Node.jsバージョンを更新する際に.nvmrcファイル一つだけ修正すれば、ローカルとCI環境の両方に自動的に反映されます。

GitHub Actionsでのワークフロー設計についてさらに知りたい方は、Jest実行Actionのパフォーマンス改善モノレポでブランチ別ファイル変更範囲を制限するGitHub Actionsワークフローも参考にしてみてください。

バージョン更新プロセス

Node.jsバージョンを更新する必要がある場合(例:セキュリティパッチ)、以下のファイルを修正する必要があります。

ファイル役割
.nvmrcローカル開発者 + CIのNode.jsバージョン指定(単一の信頼できる情報源)
package.jsonenginesnpmユーザーのための追加セーフガード

核心は**.nvmrcファイル一つだけ修正すればよい**という点です。プラグインとCI設定はすべて.nvmrcを参照するため、バージョン管理が非常にシンプルになります。

全体構造の概要

全体構造の概要

重要なポイント

  1. .nvmrcを単一の信頼できる情報源に — すべてのツールがこのファイルを参照するように設計します。
  2. Yarn Berryプラグインで強制力を確保nvm useを忘れても、間違ったバージョンでは作業が不可能になります。
  3. CIでも同じソースを参照node-version-fileオプションで.nvmrcを直接読み取ります。
  4. Windows互換性を考慮 — プラグインでYarn BerryのPortablePathをネイティブパスに変換します。
  5. バージョン更新はファイル一つだけ.nvmrcの修正一回でローカルとCIの両方が更新されます。

参考資料

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS