ebisen blog..

pnpmJesttransformIgnorePatternsを設定する際の注意点

pnpmJest正規表現

このブログの開発のパッケージ管理には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を参照する

JesttransformIgnorePatterns

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の仕様を加味したJesttransformIgnorePatterns

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

参考