JestからVitestへの移行とESModules対応の本質
はじめに
こんにちは、mk0812です。近年、JavaScriptライブラリやフレームワークの多くがESModules(ESM)を採用するようになり、テスト環境にもその対応が求められています。本記事では、Jestでは困難だったESM対応の課題と、それを解決するVitestの仕組みについて、内部構造まで踏み込んで解説します。
この記事は下記の記事を見て、その文章(引用)を深掘りしたいなあと思った次第です。
[引用]
今回問題になった内容ですが、新規に作成したいテストがesModulesで実装されているライブラリに依存していたため、現状のjestの設定ではテストできませんでした。そこでいくつかの手段を検討した上でvitestに移行するのが一番早くて合理的と考えたため、vitestに移行しました。
Jestで直面するESModulesの壁
JestはCommonJS(CJS)ベースで長く発展してきたテストフレームワークです。しかし、以下のようなESM関連の課題があります。
-
import
文が使われたESMライブラリ(例:nanoid@4
,node-fetch@3
)を読み込むとエラー -
jest.mock
がESMモジュールには対応していない -
ts-jest
やbabel-jest
のようなトランスパイルレイヤーが必要で設定が煩雑
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
このような制約のため(設定を追加すればテスト可能だが面倒)、新しいライブラリを使ったテスト作成が難しくなります。
Vitest + vite-node のアプローチ
Vitestは、Viteとesbuildを活用して、高速・ESMネイティブなテスト環境を提供しています。特に、内部的に vite-node
を使ってテストファイルを実行しているのが大きな特徴です。
🔧 変換フェーズ
Vitestはテストファイルを実行する前に、Viteの transformWithEsbuild
を呼び出し、TypeScriptやJSXをJavaScriptにトランスパイルします。
import { transformWithEsbuild } from 'vite'
const result = await transformWithEsbuild(code, id, {
loader: 'ts',
target: 'esnext',
format: 'esm',
})
この処理によって、TSファイルやESMモジュールをNode.jsで即時実行可能なESM形式に変換します。
🚀 実行フェーズ:vite-node の仮想import
VitestはViteベースのモジュールローダー「vite-node」を使って、変換済みのコードをNode.js上で import()
のように実行します。
// vite-node の擬似処理
const mod = new Function('exports', 'require', 'module', '__filename', '__dirname', code)
これにより、バンドル不要で即時テスト実行が可能になっています。依存解決・トランスパイル・実行がすべて1つのパイプラインで完結します。
Jest vs Vitest 比較表
項目 | Jest | Vitest |
---|---|---|
ESM対応 | 部分的/不安定 | ✅ ネイティブ対応 |
モック |
jest.mock 依存、ESM未対応 |
vi.mock はESMでも対応 |
トランスパイル | Babel/ts-jest | esbuild(高速) |
実行基盤 | CommonJS + Babel | Vite + vite-node(ESM) |
パフォーマンス | 比較的遅い | 非常に高速 |
まとめ
Vitestは、モダンなJavaScript開発環境にフィットするテストランナーです。ESModules化が進む現在のnpmエコシステムにおいて、Jestの制約を回避したいケースではVitestが有力な選択肢となります。
- ✅
esbuild
による高速な変換 - ✅ Viteと連携したモジュール解決
- ✅
vite-node
による柔軟かつモダンな実行モデル
今後、テスト環境の選定や移行を検討する際には、「ESM対応力」という観点からVitestを中心に据える価値が高まるでしょう。
Discussion