先に結論だけ
例として、このTypeScriptファイルのテスト実行時に
▼問題となるimport
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
以下のシンタックスエラーが発生しているなら
SyntaxError: Cannot use import statement outside a module
jest.config.jsでtransformIgnorePatterns
とtransform
フィールドを追加して
▼jest.config.js
module.exports = {
preset: "ts-jest",
transformIgnorePatterns: ["/node_modules/(?!three/examples/)"],
transform: {
"node_modules/three/examples/.+.(j|t)sx?$": "ts-jest",
},
testEnvironment: "node", // or jest-environment-jsdom
};
tsconfig.jsonにallowJs : true
を追加します。rootDirは指定してはいけません。
▼tsconfig.json
{
"compilerOptions": {
- "rootDir": "./src",
+ "allowJs": true,
}
}
はじめに
この記事は、Jestの実行時に発生する変換エラーについての情報を解説、共有するためのものです。
対象とする環境
この記事では、以下の環境を前提としています。
% sw_vers
ProductName: macOS
ProductVersion: 12.6.1
BuildVersion: 21G217
% node --version
v18.12.1
% npm --version
8.19.2
▼package.json
"devDependencies": {
"jest": "^29.3.1",
"three": "^0.148.0",
"ts-jest": "^29.0.3"
}
対象となるパッケージのメジャーバージョンが変わると、記事の内容がそのまま適用できないかもしれません。記事を読む前に、お手元の環境をご確認ください。
対象とする読者
- Jestを使ったことがある
- Jestを使っているプロジェクトが、パッケージの更新でテストに失敗した
Jestを使っているユーザーを対象としているため、この記事ではツール自体の解説やインストールガイドは扱いません。ご承知ください。
問題
Jestでテストしているプロジェクトのパッケージを更新すると、以下のエラーが発生してテストが失敗しました。
SyntaxError: Cannot use import statement outside a module
ログの抜粋
● Test suite failed to run
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.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
package.jsonを巻き戻すとテストが成功するため、パッケージ更新がエラーの原因のようです。
three.jsのCHANGELOGを読んでいくと、importしているクラスがES6に移行していました。このES6への移行と、Jestの設定の不整合が問題の原因でした。
原因
この問題はテストにimportされるファイルが、CommonJS形式に変換されないため起きます。どのように問題が発生するのか、順を追って解説します。
Jestとトランスフォーマー
Jestはテスト時にnode.jsでサポートされていない言語をJavaScriptに変換します。対象となる言語はJSX、TypeScript、Vueテンプレートなどです。
この変換処理を担当するのがトランスフォーマーです。Jestはデフォルトでbabel-jestを使用します。ts-jestはTypeScriptトランスフォーマーです。
Jestはnode_modules
以下のファイルを変換しない
Jestの標準設定ではnode_modules
と.pnp
以下のモジュールを変換せず、配布された形式のまま実行します。
node_modules
にES Moduleのみ対応のパッケージがある場合
node_modules
にトランスパイルされていないコードが含まれている場合、Jestはコードを理解できずに構文エラーが発生します。
Sometimes it happens (especially in React Native or TypeScript projects) that 3rd party modules are published as untranspiled code. Since all files inside node_modules are not transformed by default, Jest will not understand the code in these modules, resulting in syntax errors. To overcome this, you may use transformIgnorePatterns to allow transpiling such modules. You'll find a good example of this use case in React Native Guide.
時々、サードパーティモジュールがトランスパイルされていないコードとして公開されていることがあります(特にReact NativeやTypeScriptプロジェクトにおいて)。node_modules 内のすべてのファイルはデフォルトでトランスフォームされないため、Jestはこれらのモジュール内のコードを理解できず、結果として構文エラーが発生します。これを克服するために、transformIgnorePatternsを使用して、このようなモジュールのトランスパイルを許可できます。React Native Guideに、この使用例の良い例があります。
この問題は頻繁に発生するようで、公式ドキュメントにも記載がありました。
対処
この問題はnode_modules
以下のファイルが変換されないため発生します。解決のため、Jestに問題となるファイルを変換するように指示します。
jest.config.js
jest.config.js
に2つのフィールドを追加します。
-
transformIgnorePatterns
フィールド -
transform
フィールド
transformIgnorePatterns
transformIgnorePatternsフィールドのデフォルト値は以下の通りです。
Default: ["/node_modules/", "\\.pnp\\.[^\\\/]+$"]
これはnpmのnode_modules
かyarnのpnp
ディレクトリを すべて変換しない という指定です。transformIgnorePatterns
フィールドを追加し、値を以下のように書き換えます。
▼jest.config.js
module.exports = {
transformIgnorePatterns: ["/node_modules/(?!three/examples/)"],
};
この正規表現パターンは
-
node_modules
ディレクトリ以下は変換しない - しかし
node_modules/three/examples
ディレクトリは 除外から除外する → 変換する
という意味になります。
?!
は否定先読みアサーションという正規表現です。 条件に当てはまる場合はマッチしない という否定表現です。
二重の否定になっているので直感的に理解しにくいですが、この否定表現でnode_modules/three/examples
ディレクトリを変換対象に指定します。
transform
つぎにnode_modules/three/examples
ディレクトリを変換するトランスフォーマーを指定します。
▼jest.config.js
module.exports = {
preset: "ts-jest",
transform: {
"node_modules/three/examples/.+.(j|t)sx?$": "ts-jest",
},
};
jest.config.jsにtransform
フィールドを追加します。フィールドはkey-valueが対となるオブジェクトで、それぞれ
- key : 変換対象ファイルの正規表現
- value : トランスフォーマーの名前
を指定します。今回はts-jestでnode_modules/three/examples
以下のファイルを変換します。
tsconfig.js
ts-jestはtsconfig.json
の設定に従ってファイルを変換します。JavaScriptファイルを変換できるよう、tsconfig.jsにallowJs
フィールドを追加します。また、rootDirは指定してはいけません。node_modulesがコンパイル対象外になるためです。
▼tsconfig.json
{
"compilerOptions": {
- "rootDir": "./src",
+ "allowJs": true,
}
}
これでnode_modules/three/examples
以下のファイルがTypeScriptのトランスパイル設定にしたがって変換され、テストが正常に稼働します。
今後の予定
Jestは、現在ES modulesへの実験的サポートを始めています。
Jest 30以降では、トランスフォーマーを使用せずES modulesをimportしたテストが実行できるようになるかもしれません。Jestのメジャーバージョンが更新されたら、ドキュメントを再読してください。
以上、ありがとうございました。
参考記事