pnpmでJestのtransformIgnorePatternsを設定する際の注意点
このブログの開発のパッケージ管理にはpnpmを使用しています。テストコードを導入するにあたり、単体テストにはJestを使用することにしました。このときpnpmの仕様でtransformIgnorePatternsに設定に手間取ったので、ログを残します。
TLDR
pnpmを使用している場合、JestのtransformIgnorePatternsを以下のように設定することで、ESMパッケージを正しくトランスパイルできます:
// some-esm-packageをトランスパイルの対象にしたい
transformIgnorePatterns: ['/node_modules/(?!.pnpm|some-esm-package)']
pnpmの仕様
npmで依存するパッケージを追加すると、node_modules直下にパッケージのディレクトリが追加されます。このとき、依存するパッケージがさらに依存しているパッケージもnode_modules直下に追加されます。
node_modules
├─ package_a // 追加したいパッケージ
└─ package_b // package_aが依存しているから自動的に追加されたパッケージ
このとき開発対象のプロジェクトでは、package_aからだけでなくpackage_bからもモジュールをimportできてしまいます。pnpmはこれを問題として指摘しており、pnpmでは、依存パッケージをnode_modules/.pnpmに配置し、必要に応じてエイリアスを作成します。この仕組みにより、依存関係が明確になり、パッケージの重複を防ぐことができます。また、ディスク使用量を削減し、インストール速度を向上させる利点があります。
node_modules
├─ .pnpm
│ ├─ package_a
│ │ └─ node_modules
│ │ └─ package_b // .pnpm/package_bを参照する
│ └─ package_b
└─ package_a // .pnpm/package_aを参照する
JestのtransformIgnorePatterns
JestはCommonJSで書かれており、ESMAScriptで書かれたコードをテストする際にはCommonJSにトランスパイルされます。ただし、デフォルトでnode_modules配下はトランスパイルの対象から除外されています。つまり、node_modules配下にECMAScriptで書かれたモジュールが存在する場合はエラーが起きます。これを回避するために設定ファイルでトランスパイルの対象から除外するリストtransformIgnorePatternsを上書きする必要があります。Jestはこのリストの文字列をRegExpオブジェクトのtestメソッドを使用してパスが一致するか判定し、一致する場合はトランスパイルしません。
// /node_modules/some-esm-package はトランスパイルして欲しい
// /node_modules配下の他のディレクトリはトランスパイルしないで欲しい
transformIgnorePatterns: ['/node_modules/(?!some-esm-package)']
pnpmの仕様を加味したJestのtransformIgnorePatterns
pnpmを使用してESMで書かれたパッケージsome-esm-packageを追加したとき、Jestでトランスパイルして欲しいディレクトリのパスはnode_modules/.pnpm/some-esm-package@1.2.3/node_modules/some-esm-packageです。node_modules配下をトランスパイルの対象から除外しながら、このディレクトリだけはトランスパイルするような設定が必要です。
間違った設定
次のパスではsome-esm-packageがトランスパイルされずエラーが起きます
const pathToIgnore = '/node_modules/(?!.pnpm/some-esm-package)'
const regex = new RegExp(pathToIgnore)
const dirPath =
'/node_modules/.pnpm/some-esm-package@1.2.3/node_modules/some-esm-package'
// ------------------------------------ ------------------------------
// ここはfalseだが ここでtrueになる
regex.test(dirPath) // true
正しい設定
次のパスではsome-esm-packageがトランスパイルされます。
const pathToIgnore = '/node_modules/(?!.pnpm|some-esm-package)'
const regex = new RegExp(pathToIgnore)
const dirPath =
'/node_modules/.pnpm/some-esm-package@1.2.3/node_modules/some-esm-package'
// ------------------------------------ ------------------------------
// ここはfalse ここもfalse
regex.test(dirPath) // false
参考
- pnpmのメリット | pnpm
pnpmの設計思想や利点について解説している公式ドキュメント
- transformIgnorePatterns - Jestの設定 - Jest
transformIgnorePatternsの設定方法に関する公式ドキュメント。pnpmでの設定方法にも触れられている。
- Josh Bickley-Wallace - Jest Transforms With PNPM
- 同じエラーにハマったというブログ