created: 2023-03-11T08:11:06.772Z

Jest encountered an unexpected token

jestでテストを実行しようとすると発生するとても有名なエラー。

Jest encountered an unexpected token

Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

今回はは2023年3月時点の公式ドキュメントの案内にしたがって対応する。

注意

この問題は普遍的なものではなく JavaScript という言語の標準的な文法が commonjs から ESModule に移り変わる過渡期であるために発生している。

近い未来では解決方法が変わっていたり、そもそもこの問題にあたらなくなるはずなので、公式のドキュメントに紹介されている方法を参照するほうがよい。

前提知識

  • .ts ファイルを jest でテストしようとするとき、ts-jest でトランスパイルすることになる
  • ただし、 ts-jest はアプリケーションのコードは面倒をみてくれるが、 node_modules 配下はトランスパイルしてくれない
  • node_modules 配下のだいたいのnpmパッケージは commonjs 形式のビルドが提供されているので問題ないが、先進的なやつだと ESModule 形式でビルドしかないものもある
    • commonjs は require/exports を使う文法
    • ESModule は import/export を使う文法
  • もともと nodejs では commonjs がデフォルトの文法なので ESModule 形式でビルドされているものは、素の nodejs では実行できず、SyntaxError となる

2023年3月時点の解決方法

方針

公式ドキュメントでは ESModules のファイルを commonjs にトランスパイルするのではなく、nodejs が ESModules のファイルを読めるような設定をして、そのランタイム上で jest を動かす方針をとっていた。

なのでまず jest の実行コマンドに nodejs の --experimental-vm-modules オプションをつけている。

  "scripts": {
    "test": "node --experimental-vm-modules node_modules/.bin/jest",
  }

--experimental-vm-modules は nodejs(のvm) が ESModule を実行できるようにするためのオプション。これを設定して、jest コマンドを ESModule を実行できる nodejs 上で実行させるようにしている。

jest.config.js

ts-jest の設定項目に useESM を追加する。これによって ts-jest が ts ファイルを commonjs ではなく、ESModules にトランスパイルしてくれることになる。

".ts": ["ts-jest", { "useESM": true}]

ts ファイルが ESModule にトランスパイルされるようになったところで、 jest でも extensionsToTreatAsEsm を指定して、tsファイル(のトランスパイル結果)が ESModule 形式のファイルとみなされるようにする。

extensionsToTreatAsEsm は「このファイルは commonjs ではなく ESModules として読み込ませる」という設定。

"extensionsToTreatAsEsm": [".ts"]

また、preset の設定も ESModule にトランスパイルされるものを指定する必要がある。

デフォルトの preset: "ts-jest" は commonjs にコンパイルすることになっているので、これを ts-jest/presets/default-esm にする。

  preset: "ts-jest/presets/default-esm",

ts-jest/presets/default or ts-jest

TypeScript files (.ts, .tsx) will be transformed by ts-jest to CommonJS syntax, leaving JavaScript files (.js, jsx) as-is.

tsconfig.json

最後に module の設定が commonjs になっていないことを確認する。この設定も ts ファイルを ESModule 形式にトランスパイルするためのもの。

"compilerOptions": {
    "module": "ESNext"
    ...
}

所感

  • ts-jest はデフォルトで tsc (tsconfigの設定) を使ってトランスパイルをしないということを知らなかった
  • 最初は useESMextensionsToTreatAsEsm の設定がなぜ必要なのかわからなかった
  • jest の preset という設定項目は暗黙的にいろいろな設定が含まれていて鬼門な感じがする
  • commonjs と ESModule の相互運用性についてはまた今度
世界史を変えた薬 (講談社現代新書)
[ad] 世界史を変えた薬 (講談社現代新書)
佐藤 健太郎 (新書)