Jest を Yarn PnP で動かす:ゼロ‐node_modules 時代の設定レシピ

JavaScript のプロジェクトでテストツールとして広く使われている Jest ですが、Yarn PnP(Plug'n'Play)環境で動作させるには少し特別な設定が必要です。この記事では、従来の node_modules
を使わない Yarn PnP で Jest を快適に動かす設定方法を詳しく解説していきます。
背景
Yarn PnP とは何か
Yarn PnP(Plug'n'Play)は、従来の node_modules
フォルダを生成せず、パッケージの依存関係を .pnp.cjs
ファイルで管理する仕組みです。この仕組みによって、インストール速度の向上やディスク容量の節約が実現できます。
通常の Node.js プロジェクトでは、パッケージをインストールすると node_modules
フォルダに大量のファイルが展開されますが、PnP ではこのステップを省略し、必要なパッケージを直接 Yarn のキャッシュから読み込みます。
mermaidflowchart TB
subgraph traditional["従来の node_modules 方式"]
inst1["yarn install"] --> nm1["node_modules 展開"]
nm1 --> app1["アプリケーション実行"]
end
subgraph pnp["Yarn PnP 方式"]
inst2["yarn install"] --> pnpfile[".pnp.cjs 生成"]
pnpfile --> cache["Yarn キャッシュから<br/>直接読み込み"]
cache --> app2["アプリケーション実行"]
end
この図が示すように、PnP 方式では中間の展開ステップを省略することで、インストール時間を大幅に短縮できます。
PnP のメリット
Yarn PnP には以下のようなメリットがあります。
# | 項目 | 説明 |
---|---|---|
1 | インストール速度 | node_modules への展開が不要なため、パッケージインストールが高速 |
2 | ディスク容量 | 重複したパッケージを保存しないため、ディスク容量を節約できる |
3 | 依存関係の厳密性 | package.json に記載されていない依存関係へのアクセスを防ぐことができる |
4 | 再現性 | .pnp.cjs ファイルによって、依存関係の解決が確定的になる |
特に大規模なモノレポ構成では、これらのメリットが顕著に現れるでしょう。
課題
Jest と PnP の互換性問題
Jest は元々 node_modules
の存在を前提として設計されているため、Yarn PnP 環境ではそのままでは動作しません。具体的には以下のような問題が発生します。
モジュール解決の失敗
Jest がテストファイルや設定ファイルを読み込む際、通常の Node.js のモジュール解決アルゴリズムを使用します。しかし PnP 環境では、モジュールは .pnp.cjs
ファイルを通じて解決される必要があるため、標準的な方法ではモジュールが見つかりません。
mermaidflowchart LR
jest["Jest テストランナー"] -->|モジュール要求| node["Node.js 標準<br/>モジュール解決"]
node -->|探索| nm["node_modules を探す"]
nm -->|見つからない| error["❌ モジュール未検出"]
style error fill:#ff9999
このフローが示すように、Jest が node_modules
を探しても存在しないため、テスト実行が失敗してしまいます。
トランスフォーマーの問題
Jest は TypeScript や JSX などのファイルを変換するために、トランスフォーマー(例:babel-jest
、ts-jest
)を使用します。PnP 環境では、これらのトランスフォーマーも正しく解決できない場合があります。
主なエラーメッセージ
PnP 環境で Jest を実行すると、以下のようなエラーが表示されることがあります。
typescript// エラー例 1: モジュール解決エラー
Error: Cannot find module '@jest/globals'
Require stack:
- /path/to/test.spec.ts
typescript// エラー例 2: トランスフォーマー解決エラー
Error: Jest: Failed to parse the TypeScript config file
Cannot find module 'ts-jest'
これらのエラーは、Jest が PnP のモジュール解決機構を理解していないために発生します。
解決策
必要なパッケージのインストール
Jest を Yarn PnP 環境で動作させるには、専用のパッケージをインストールする必要があります。
基本パッケージ
まず、Jest 本体と PnP 対応のための @yarnpkg/pnpify
をインストールしましょう。
bash# Jest と関連パッケージをインストール
yarn add -D jest @yarnpkg/pnpify
このコマンドで、Jest の実行に必要な基本パッケージが開発用の依存関係として追加されます。
TypeScript を使用する場合
TypeScript プロジェクトでは、さらに ts-jest
と型定義ファイルが必要です。
bash# TypeScript 用のパッケージを追加
yarn add -D ts-jest @types/jest typescript
ts-jest
は TypeScript ファイルを Jest で実行可能な形式に変換するトランスフォーマーです。
Jest 設定ファイルの作成
次に、PnP 環境に対応した Jest の設定ファイルを作成します。プロジェクトルートに jest.config.js
を配置しましょう。
JavaScript プロジェクト向け設定
JavaScript プロジェクトの場合、以下の設定で Jest を PnP 環境で動作させることができます。
javascript// jest.config.js
// PnP 環境でモジュール解決を行うための設定
module.exports = {
// テストファイルのパターンを指定
testMatch: [
'**/__tests__/**/*.js',
'**/*.test.js',
'**/*.spec.js',
],
// PnP のモジュールローダーを使用
resolver: require.resolve('jest-pnp-resolver'),
};
この設定により、Jest は PnP のモジュール解決機構を使用するようになります。
TypeScript プロジェクト向け設定
TypeScript を使用している場合は、トランスフォーマーの設定も追加する必要があります。
javascript// jest.config.js
// TypeScript と PnP を組み合わせた設定
module.exports = {
// テストファイルのパターン
testMatch: [
'**/__tests__/**/*.ts',
'**/__tests__/**/*.tsx',
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.ts',
'**/*.spec.tsx',
],
// PnP のモジュールローダーを使用
resolver: require.resolve('jest-pnp-resolver'),
// TypeScript ファイルの変換設定
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
transform
プロパティで、拡張子が .ts
または .tsx
のファイルを ts-jest
で変換するように指定しています。
ts-jest の詳細設定
TypeScript の型チェックやトランスパイル設定を細かく制御したい場合は、globals
セクションで ts-jest の設定を追加できます。
javascript// jest.config.js の globals セクション
module.exports = {
// 前述の設定に加えて...
globals: {
'ts-jest': {
// tsconfig.json を明示的に指定
tsconfig: {
// テスト実行時の TypeScript コンパイラオプション
esModuleInterop: true,
allowSyntheticDefaultImports: true,
},
},
},
};
この設定により、テスト実行時の TypeScript コンパイラの動作を細かく制御できます。
jest-pnp-resolver のインストール
PnP 環境で Jest のモジュール解決を正しく行うには、jest-pnp-resolver
が必要です。
bash# PnP リゾルバーをインストール
yarn add -D jest-pnp-resolver
このパッケージは、Jest のモジュール解決処理を PnP 対応に変換する役割を果たします。
package.json のスクリプト設定
テストを実行するためのスクリプトを package.json
に追加しましょう。
json{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
これらのスクリプトにより、コマンドラインから簡単にテストを実行できます。
yarn test
: 全テストを実行yarn test:watch
: ファイル変更を監視して自動実行yarn test:coverage
: カバレッジレポートを生成
.yarnrc.yml の設定確認
Yarn PnP が正しく有効化されていることを確認します。プロジェクトルートの .yarnrc.yml
ファイルを確認しましょう。
yaml# .yarnrc.yml
# PnP モードを有効化
nodeLinker: pnp
# SDK ファイルの生成(エディタ連携用)
pnpMode: loose
nodeLinker: pnp
が設定されていることで、Yarn は PnP モードで動作します。
具体例
サンプルプロジェクトの構築
実際に Yarn PnP と Jest を組み合わせたプロジェクトを構築してみましょう。
プロジェクト初期化
新しいプロジェクトを作成し、Yarn PnP を有効化します。
bash# プロジェクトディレクトリを作成
mkdir jest-pnp-example && cd jest-pnp-example
# Yarn プロジェクトを初期化
yarn init -y
# PnP モードを有効化
yarn config set nodeLinker pnp
これでプロジェクトの基本構造ができました。
必要なパッケージのインストール
次に、Jest と関連パッケージをインストールします。
bash# Jest と PnP 関連パッケージをインストール
yarn add -D jest @yarnpkg/pnpify jest-pnp-resolver
# TypeScript を使う場合はさらに追加
yarn add -D typescript ts-jest @types/jest @types/node
インストールが完了すると、.pnp.cjs
ファイルが生成されます。
テストファイルの作成
実際にテストを書いてみましょう。まず、テスト対象の関数を作成します。
javascript// src/calculator.js
// シンプルな計算機関数
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
// ゼロ除算のチェック
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = { add, subtract, multiply, divide };
次に、この関数をテストするファイルを作成します。
javascript// src/calculator.test.js
// Jest を使った単体テスト
const {
add,
subtract,
multiply,
divide,
} = require('./calculator');
describe('Calculator functions', () => {
// 足し算のテスト
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
// 引き算のテスト
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
// 掛け算のテスト
test('multiplies 4 * 5 to equal 20', () => {
expect(multiply(4, 5)).toBe(20);
});
// 割り算のテスト
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
// エラーケースのテスト
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow(
'Cannot divide by zero'
);
});
});
これらのテストケースで、基本的な計算機能とエラーハンドリングを検証できます。
TypeScript での実装例
TypeScript を使用する場合の実装例も見てみましょう。
typescript// src/calculator.ts
// TypeScript で型安全な計算機を実装
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
対応するテストファイルも TypeScript で記述します。
typescript// src/calculator.test.ts
// TypeScript でのテスト実装
import {
add,
subtract,
multiply,
divide,
} from './calculator';
describe('Calculator functions with TypeScript', () => {
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
test('multiplies 4 * 5 to equal 20', () => {
expect(multiply(4, 5)).toBe(20);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow(
'Cannot divide by zero'
);
});
});
TypeScript を使うことで、型チェックによるバグの早期発見が可能になります。
テストの実行
設定が完了したら、テストを実行してみましょう。
bash# テストを実行
yarn test
成功すると、以下のような出力が表示されます。
plaintext PASS src/calculator.test.js
Calculator functions
✓ adds 1 + 2 to equal 3 (2 ms)
✓ subtracts 5 - 3 to equal 2
✓ multiplies 4 * 5 to equal 20 (1 ms)
✓ divides 10 / 2 to equal 5
✓ throws error when dividing by zero (1 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.234 s
このように、PnP 環境でも問題なく Jest が動作していることが確認できます。
カバレッジレポートの生成
コードカバレッジを確認したい場合は、以下のコマンドを実行します。
bash# カバレッジレポートを生成
yarn test:coverage
実行結果として、カバレッジの詳細が表示されます。
plaintext----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
calculator.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
カバレッジレポートは coverage
ディレクトリに HTML 形式でも出力され、ブラウザで詳細を確認できます。
トラブルシューティング
PnP 環境で Jest を使用する際によくある問題と解決方法を紹介します。
モジュール解決エラーの対処
もし以下のようなエラーが発生した場合:
plaintextError: Cannot find module 'jest-pnp-resolver'
Require stack:
- /path/to/jest.config.js
これは jest-pnp-resolver
が正しくインストールされていない可能性があります。
bash# パッケージを再インストール
yarn add -D jest-pnp-resolver
# Yarn のキャッシュをクリア
yarn cache clean
# 依存関係を再インストール
yarn install
これらのコマンドで依存関係を整理することで、エラーが解消されるでしょう。
エディタ連携の設定
VSCode などのエディタで TypeScript の型チェックを有効にするには、SDK ファイルを生成する必要があります。
bash# SDK ファイルを生成(VSCode 用)
yarn dlx @yarnpkg/sdks vscode
このコマンドで、.yarn/sdks
ディレクトリに VSCode 用の設定ファイルが生成されます。
VSCode の設定で TypeScript のバージョンを選択する際、ワークスペース版を選ぶことで PnP 環境での型チェックが正しく機能します。
パフォーマンス最適化
大規模なプロジェクトでテストが遅い場合は、Jest の設定を調整してパフォーマンスを改善できます。
javascript// jest.config.js
module.exports = {
// 前述の設定に加えて...
// 並列実行のワーカー数を調整
maxWorkers: '50%',
// テストファイルのキャッシュを有効化
cache: true,
cacheDirectory: '.jest-cache',
// 不要なファイルを除外
testPathIgnorePatterns: ['/node_modules/', '/.yarn/'],
};
これらの設定により、テスト実行速度が向上する場合があります。
まとめ
この記事では、Yarn PnP 環境で Jest を動作させるための設定方法を解説しました。重要なポイントをまとめます。
設定のポイント
# | 項目 | 内容 |
---|---|---|
1 | PnP リゾルバー | jest-pnp-resolver をインストールして jest.config.js で指定 |
2 | TypeScript 対応 | ts-jest を使用してトランスフォーマーを設定 |
3 | モジュール解決 | .pnp.cjs を通じた依存関係の解決を有効化 |
4 | エディタ連携 | SDK ファイルを生成して型チェックを有効化 |
Yarn PnP は従来の node_modules
方式と比べて高速かつ効率的ですが、Jest のような既存ツールとの連携には追加の設定が必要です。しかし、一度正しく設定すれば、従来と同じようにテストを実行できるようになります。
今後の展望
Yarn PnP は今後さらに普及していくと予想されます。特に大規模なモノレポや CI/CD パイプラインでは、インストール時間の短縮が大きなメリットとなるでしょう。
Jest の公式チームも PnP 対応を進めており、将来的にはより少ない設定で動作するようになる可能性があります。現時点では jest-pnp-resolver
を使った設定が必要ですが、この記事で紹介した方法を使えば、すぐに PnP 環境でテストを始められますね。
Yarn PnP と Jest を組み合わせることで、モダンな JavaScript/TypeScript プロジェクトの開発体験をさらに向上させることができるでしょう。
関連リンク
- article
Jest を Yarn PnP で動かす:ゼロ‐node_modules 時代の設定レシピ
- article
Jest の TS 変換速度を検証:ts-jest vs babel-jest vs swc-jest vs esbuild-jest
- article
Jest で ESM が通らない時の解決フロー:type: module/transform/resolver を総点検
- article
Jest アーキテクチャ超図解:ランナー・トランスフォーマ・環境・レポーターの関係を一望
- article
Jest で DOM 操作をテストする方法:document・window の扱い方まとめ
- article
【入門】Jest 初心者が最初に知っておくべきテスト設計の基本原則
- article
Web Components の API 設計原則:属性 vs プロパティ vs メソッドの境界線
- article
Jest を Yarn PnP で動かす:ゼロ‐node_modules 時代の設定レシピ
- article
GitHub Copilot を macOS で最短導入:VS Code・Neovim・JetBrains の横断設定
- article
Vue.js を macOS + yarn で最短セットアップ:ESLint/Prettier/TS/パスエイリアス
- article
Tailwind CSS を macOS で最短導入:Yarn PnP・PostCSS・ESLint 連携レシピ
- article
GitHub Actions を macOS ランナーで使いこなす:Xcode/コード署名/キーチェーン設定
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来