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
- 同じエラーにハマったというブログ