Jest で ESM が通らない時の解決フロー:type: module/transform/resolver を総点検

Jest で ESM モジュールを使ったプロジェクトで「SyntaxError: Cannot use import statement outside a module」や「TypeError: Unknown file extension ".js"」といったエラーに遭遇したことはありませんか?
このようなエラーは、Jest が ES Modules(ESM)を完全にはサポートしていないことに起因しています。しかし、適切な設定を行うことで、これらの問題は確実に解決できます。
この記事では、Jest で ESM が通らない問題を体系的に解決するための完全なフローをご紹介します。package.json
の type: "module"
設定から、transform 設定、resolver の調整まで、段階的にトラブルシューティングを進めていきましょう。
ESM エラーの診断と分類
Jest で ESM を使用する際に発生するエラーは、主に以下のパターンに分類されます。まずは、どのエラーに該当するかを正確に把握しましょう。
よくあるエラーパターン
Jest ESM 関連のエラーは、その原因に応じて明確なパターンに分かれています。以下の表で主要なエラーを整理してみました。
# | エラーコード | エラーメッセージ例 | 主な原因 |
---|---|---|---|
1 | SyntaxError | Cannot use import statement outside a module | package.json の type 設定不備 |
2 | TypeError | Unknown file extension ".js" for ESM | transform 設定の問題 |
3 | ERR_MODULE_NOT_FOUND | Cannot find module | moduleNameMapping 不備 |
4 | ReferenceError | require is not defined | CommonJS/ESM の混在問題 |
エラーメッセージから原因を特定する方法
エラーメッセージを読み解くことで、問題の所在を素早く特定できます。以下のフローチャートで診断を行ってみましょう。
mermaidflowchart TD
error[エラー発生] --> syntax{SyntaxError?}
syntax -->|Yes| import_error[import statement エラー]
syntax -->|No| type_error{TypeError?}
import_error --> check_type[package.json の type 確認]
type_error -->|Yes| extension_error[Unknown file extension]
type_error -->|No| module_error[Module not found]
extension_error --> check_transform[transform 設定確認]
module_error --> check_mapping[moduleNameMapping 確認]
check_type --> solution_type[type: module 設定]
check_transform --> solution_transform[transform 追加]
check_mapping --> solution_mapping[パス設定修正]
このフローに従って原因を特定することで、効率的に問題を解決できます。
図で理解できる要点:
- エラータイプごとに解決すべき設定項目が明確
- 診断フローに沿って段階的にチェックが可能
- 各エラーパターンに対応する設定箇所が一目瞭然
解決フローの全体像
Jest ESM 問題の解決は、以下の 4 つのステップで体系的に進めることが重要です。各ステップを順序立てて実行することで、確実に問題を解決できます。
Step 1: package.json の type: module 設定確認
最初に確認すべきは、プロジェクトが ESM を使用することを明示的に宣言しているかどうかです。
json{
"name": "your-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "jest"
}
}
type: "module"
の設定により、プロジェクト全体で ESM が有効になります。この設定がない場合、Jest は CommonJS として解釈するため、import 文でエラーが発生します。
Step 2: Jest 設定ファイルの transform 設定点検
Jest は標準では ESM をサポートしていないため、適切な transform 設定が必要です。以下の設定を確認しましょう。
javascript// jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts', '.tsx'],
globals: {
'ts-jest': {
useESM: true,
},
},
};
この設定により、TypeScript ファイルを ESM として正しく変換できます。
Step 3: moduleNameMapping と resolver の調整
外部パッケージや内部モジュールの解決には、moduleNameMapping の設定が重要です。
javascriptexport default {
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/$1',
},
resolver: '<rootDir>/jest.resolver.js',
};
パスエイリアスを使用している場合は、Jest がモジュールを正しく解決できるよう設定を追加します。
Step 4: 外部パッケージの ESM 対応確認
Node.js の標準モジュールや外部ライブラリが ESM に対応していない場合は、transformIgnorePatterns の調整が必要です。
javascriptexport default {
transformIgnorePatterns: [
'node_modules/(?!(es6-package|another-esm-package)/)',
],
};
この設定により、特定のパッケージのみ transform の対象に含めることができます。
以下の図は、Jest ESM 解決フローの全体像を示しています。
mermaidflowchart LR
start[プロジェクト開始] --> step1[Step1: type module設定]
step1 --> step2[Step2: transform設定]
step2 --> step3[Step3: resolver設定]
step3 --> step4[Step4: 外部パッケージ対応]
step4 --> test[テスト実行]
test --> success[成功]
test --> error[エラー]
error --> debug[デバッグ]
debug --> step1
このフローに従うことで、段階的かつ確実に問題を解決できます。
各解決手順の詳細実装
ここからは、各ステップの具体的な実装方法を詳しく見ていきましょう。実際のコードサンプルと共に解説します。
type: module 設定の最適化
package.json での ESM 宣言は、プロジェクト全体の モジュール解決方法を決定する重要な設定です。
json{
"name": "jest-esm-project",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
exports
フィールドを使用することで、ESM と CommonJS の両方をサポートできます。これにより、他のプロジェクトからの利用時にも互換性を保てます。
また、Jest 専用の設定も追加しておくと安全です。
json{
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",
"test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch"
}
}
--experimental-vm-modules
フラグにより、Node.js の ESM サポートを有効にします。
transform 設定のトラブルシューティング
TypeScript プロジェクトでの Jest ESM 対応には、ts-jest の ESM モードを使用します。
javascript// jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
// ESM として扱うファイル拡張子を指定
extensionsToTreatAsEsm: ['.ts', '.tsx', '.jsx'],
// ts-jest の ESM 設定
globals: {
'ts-jest': {
useESM: true,
tsconfig: {
module: 'es6',
target: 'es2020',
},
},
},
// テスト環境の設定
testEnvironment: 'node',
};
JavaScript のみのプロジェクトの場合は、Babel を使用します。
javascriptexport default {
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
// Babel 設定
transformIgnorePatterns: [
'node_modules/(?!(@babel|es6-module)/)',
],
};
対応する .babelrc.json の設定も必要です。
json{
"presets": [
[
"@babel/preset-env",
{
"targets": { "node": "current" },
"modules": false
}
]
],
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": { "node": "current" }
}
]
]
}
}
}
resolver 設定による依存関係解決
カスタム resolver を作成することで、複雑なモジュール解決ルールに対応できます。
javascript// jest.resolver.js
export default (request, options) => {
// パスエイリアスの解決
if (request.startsWith('@/')) {
return options.defaultResolver(
request.replace('@/', './src/'),
options
);
}
// 外部モジュールの ESM バージョンを優先
if (
!request.startsWith('.') &&
!request.startsWith('/')
) {
try {
return options.defaultResolver(
request + '/esm',
options
);
} catch {
// フォールバック
}
}
return options.defaultResolver(request, options);
};
このカスタム resolver により、プロジェクト特有のモジュール解決ルールを実装できます。
moduleNameMapping との組み合わせも効果的です。
javascriptexport default {
moduleNameMapping: {
// パスエイリアス
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/$1',
// 外部ライブラリの ESM バージョンを指定
'^lodash$': 'lodash-es',
'^react-router$': 'react-router/esm',
},
resolver: '<rootDir>/jest.resolver.js',
};
実践的なトラブルシューティング事例
実際のプロジェクトで頻繁に遭遇する問題パターンと、その具体的な解決方法をケース別に解説します。
ケース別解決方法
ケース 1: Next.js プロジェクトでの ESM エラー
Next.js プロジェクトでは、以下のような設定が必要です。
javascript// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts', '.tsx'],
globals: {
'ts-jest': {
useESM: true,
},
},
moduleNameMapping: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/pages/(.*)$': '<rootDir>/pages/$1',
},
testEnvironment: 'jsdom',
};
module.exports = createJestConfig(customJestConfig);
ケース 2: React + Vite プロジェクトでのテスト設定
javascript// jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
transform: {
'^.+\\.tsx?$': ['ts-jest', { useESM: true }],
'^.+\\.jsx?$': 'babel-jest',
},
extensionsToTreatAsEsm: ['.ts', '.tsx'],
};
ケース 3: Monorepo での設定
javascript// packages/shared/jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
moduleNameMapping: {
'^@shared/(.*)$': '<rootDir>/src/$1',
'^@utils/(.*)$': '<rootDir>/../utils/src/$1',
},
// 他のワークスペースパッケージを transform 対象に含める
transformIgnorePatterns: [
'node_modules/(?!(@your-org)/)',
],
};
設定ファイルの完全版サンプル
以下は、最も汎用的で安定した Jest ESM 設定の完全版です。
javascript// jest.config.js
export default {
// 基本設定
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
// ESM 対応設定
extensionsToTreatAsEsm: ['.ts', '.tsx', '.jsx'],
// ts-jest 設定
globals: {
'ts-jest': {
useESM: true,
tsconfig: {
module: 'es6',
target: 'es2020',
moduleResolution: 'node',
},
},
},
// モジュール解決設定
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/$1',
'^tests/(.*)$': '<rootDir>/tests/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|svg)$':
'<rootDir>/__mocks__/fileMock.js',
},
// Transform 設定
transform: {
'^.+\\.tsx?$': ['ts-jest', { useESM: true }],
'^.+\\.jsx?$': 'babel-jest',
},
// 外部モジュールの transform 設定
transformIgnorePatterns: [
'node_modules/(?!(es6-package|@babel|lodash-es)/)',
],
// テストファイルの設定
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{ts,tsx,js,jsx}',
'<rootDir>/src/**/*.{test,spec}.{ts,tsx,js,jsx}',
],
// セットアップファイル
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// カバレッジ設定
collectCoverageFrom: [
'src/**/*.{ts,tsx,js,jsx}',
'!src/**/*.d.ts',
'!src/**/index.{ts,tsx,js,jsx}',
],
// カスタム resolver
resolver: '<rootDir>/jest.resolver.js',
};
対応するセットアップファイルも作成します。
javascript// jest.setup.js
import { jest } from '@jest/globals';
// グローバルモックの設定
global.jest = jest;
// fetch のモック(Node.js 18未満の場合)
if (!global.fetch) {
global.fetch = jest.fn();
}
// その他の必要なセットアップ
beforeEach(() => {
jest.clearAllMocks();
});
この設定により、ほとんどの ESM 関連問題を解決できます。プロジェクトの要件に応じて、必要な部分をカスタマイズしてご利用ください。
まとめ
Jest で ESM が通らない問題は、体系的なアプローチにより確実に解決できます。
本記事で解説した 4 つのステップを順序立てて実行することで、複雑に見える ESM 問題も段階的に解決できることがお分かりいただけたでしょう。特に重要なポイントは以下の通りです。
解決の鍵となる設定項目:
- package.json の
type: "module"
宣言 - Jest 設定での transform と extensionsToTreatAsEsm
- moduleNameMapping によるパス解決
- 外部パッケージの transformIgnorePatterns 調整
エラーメッセージを正確に読み解き、適切な診断フローに従うことで、問題の根本原因を素早く特定できます。また、プロジェクトの特性(Next.js、React + Vite、Monorepo など)に応じた設定調整も重要です。
Jest ESM 対応は一度設定すれば安定して動作します。この記事の設定を参考に、ぜひ快適な ESM テスト環境を構築してください。
関連リンク
- article
Jest で ESM が通らない時の解決フロー:type: module/transform/resolver を総点検
- article
Jest アーキテクチャ超図解:ランナー・トランスフォーマ・環境・レポーターの関係を一望
- article
Jest で DOM 操作をテストする方法:document・window の扱い方まとめ
- article
【入門】Jest 初心者が最初に知っておくべきテスト設計の基本原則
- article
Jest でテストを書き続けるコツと開発文化への定着方法
- article
テストフレームワーク(Jest・Mocha)と Node.js
- article
【比較検証】Convex vs Firebase vs Supabase:リアルタイム性・整合性・学習コストの最適解
- article
【徹底比較】Preact vs React 2025:バンドル・FPS・メモリ・DX を総合評価
- article
GPT-5-Codex vs Claude Code / Cursor 徹底比較:得意領域・精度・開発速度の違いを検証
- article
Astro × Cloudflare Workers/Pages:エッジ配信で超高速なサイトを構築
- article
【2025 年版】Playwright vs Cypress vs Selenium 徹底比較:速度・安定性・学習コストの最適解
- article
Apollo を最短導入:Vite/Next.js/Remix での初期配線テンプレ集
- 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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来