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
を使う文法
- commonjs は
- もともと 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の設定) を使ってトランスパイルをしないということを知らなかった
- 最初は
useESM
やextensionsToTreatAsEsm
の設定がなぜ必要なのかわからなかった - jest の
preset
という設定項目は暗黙的にいろいろな設定が含まれていて鬼門な感じがする - commonjs と ESModule の相互運用性についてはまた今度