😊

JestからVitestへの移行とESModules対応の本質

に公開

はじめに

こんにちは、mk0812です。近年、JavaScriptライブラリやフレームワークの多くがESModules(ESM)を採用するようになり、テスト環境にもその対応が求められています。本記事では、Jestでは困難だったESM対応の課題と、それを解決するVitestの仕組みについて、内部構造まで踏み込んで解説します。

この記事は下記の記事を見て、その文章(引用)を深掘りしたいなあと思った次第です。
https://tech.high-link.co.jp/entry/Jest-Vitest-transfer

[引用]

今回問題になった内容ですが、新規に作成したいテストがesModulesで実装されているライブラリに依存していたため、現状のjestの設定ではテストできませんでした。そこでいくつかの手段を検討した上でvitestに移行するのが一番早くて合理的と考えたため、vitestに移行しました。


Jestで直面するESModulesの壁

JestはCommonJS(CJS)ベースで長く発展してきたテストフレームワークです。しかし、以下のようなESM関連の課題があります。

  • import 文が使われたESMライブラリ(例:nanoid@4, node-fetch@3)を読み込むとエラー
  • jest.mock がESMモジュールには対応していない
  • ts-jestbabel-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