定義
Jest でテストを走らせるのに、ある npm パッケージが最新方式(ESM)でのみ配布されていて import がエラーを出して読み込めないとき、そのパッケージのコードを自分のレポ内の TS ファイルにコピーしておき、テストがそのコピーを代わりに使うようにする 回避策だ。
まず用語を解くと:ESM(ECMAScript Modules)は import/export を使う最新の JavaScript モジュール方式、CJS(CommonJS)は require を使う旧方式で、Jest の既定環境は CJS だ。vendoring(ベンダリング)は外部パッケージのコードを自分のプロジェクト内へコピーして入れること、shim(シム)は原本の代わりに差し込む薄い代替コードを指す。moduleNameMapper は「この import 名が出てきたら実際にはこのファイルを読め」と Jest に知らせる経路置換の設定だ。
整理すると Jest ESM vendor shim は、Jest(ts-jest — TypeScript を Jest で走らせる道具、CommonJS preset)のテストで ESM 専用の npm パッケージ を直接 import せず、パッケージのソース(または薄い wrapper)をレポ内部の TypeScript ファイルへ vendoring したうえで、moduleNameMapper で import 経路をそのファイルへ迂回させる技法だ。
なぜ必要か
最新の npm パッケージはますます ESM("type": "module")単一ビルドでのみ配布される。ところが Jest の既定パイプラインは node_modules 内のコードを CJS へ変換(transform)せずそのまま実行しようとするので、ESM 構文に出会うと SyntaxError: Cannot use import statement outside a module(モジュールの外で import できない)が出る。
transformIgnorePatterns(どのパッケージは変換して読めと指定する Jest 設定)に該当パッケージを追加する方法もあるが、pnpm/yarn がパッケージをインストールするネストした経路・バージョン別の経路が環境ごとに違うので、CI とローカルの間で通ったり通らなかったり(flaky)しやすい。vendor shim はその経路問題を根本からなくす。
動作原理
- 失敗する import
from 'esm-only-pkg'を確認。 __tests__/vendor/esm-only-pkg.tsまたはsrc/ordering/vendor/に TS shim を作成(必要な API だけ export)。jest.config:
- ts-jest が shim を transform → CJS テストランタイムで require 可能。
- vendored な upstream コードは lint/tsc の問題があれば ファイル単位 で suppress + 差し替え経路のコメント。
| アプローチ | 長所 | 短所 |
|---|---|---|
| transformIgnorePatterns | upstream そのまま | nested path fragile |
| Jest experimental ESM | idiomatic import | 設定・速度の負担 |
| vendor shim | 安定・予測可能 | sync・ライセンス責任 |
実務での適用
- shim は テスト/バンドルが実際に使う surface だけ を export — パッケージ全体の fork を最小化する。
- pre-commit の turbo lint が vendored ファイルまで検査すると、無関係な upstream lint がコミットを塞ぐことがある → ファイルヘッダーの抑制または lint ignore path のポリシー。
isolatedModules: true(ts-jest)は 型チェックを省略 —pnpm testが green の後、tsc --noEmitを別途確認する。
トレードオフ
- vendor shim は安定的だが、upstream 更新時に 手動 sync が必要。
@ts-nocheckの乱用は型安全の幻想 — 新規コードは typed wrapper で包む。
使ってはいけない場合
- パッケージが CJS dual build を提供し、Jest が既に安定して import している場合。
- shim の代わりにプロジェクト全体を Vitest ESM へ移す migration が進行中のとき(重複投資)。
よくある間違い
- transformIgnorePatterns の regex だけ変えても pnpm
.pnpm/経路で失敗し続ける。 - test green = ship — vendor
@ts-nocheckの下の型 hole を放置。 - shim なしで test 内に dynamic
import()— Jest mock のタイミング問題。
関連概念
- fractional-index-ordering — ESM-only な ordering ライブラリを Jest で使うときのよくある動機