先に結論だけ
Jestで、依存先のPure ESMモジュールを読み込んでテストしたら
FAIL __test__/SphericalRotor.spec.ts
● Test suite failed to run
Cannot find module '@masatomakino/threejs-spherical-controls' from '__test__/SphericalRotor.spec.ts'
> 1 | import {
| ^
2 | SphericalController,
3 | SphericalParamType,
4 | } from "@masatomakino/threejs-spherical-controls";
at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
at Object.<anonymous> (__test__/SphericalRotor.spec.ts:1:1)
Resolver
でエラーが出ている場合
at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
依存パッケージのPackage.jsonを開いて
{
"type": "module",
"exports": {
".": {
"import": {
"types": "./esm/index.d.ts",
"default": "./esm/index.js"
},
+ "default": {
+ "types": "./esm/index.d.ts",
+ "default": "./esm/index.js"
+ }
}
}
}
exportsフィールドに"default"フィールドを追加してください。
はじめに
この記事は、Jest29におけるモジュール解決について記録、共有するためのものです。
想定する環境
この記事は、Jest v29.7およびts-jest v29.1を想定して書かれています。Jestのバージョンが異なる場合は、記事の内容がそのまま適用できないかもしれません。記事を読む前に、お手元の環境をご確認ください。
想定する読者
この記事は、すでにJestを利用しているユーザーに向けて書かれています。Jestの基本的な使い方については、公式ドキュメントをご参照ください。
JestにおけるESM対応
JestにおけるESM対応は実験段階に位置付けられています。Jest29でESMをテストするには以下の2つの方法があります。
-
--experimental-vm-modules
オプションを指定してESMを変換せずに実行する - transformerを利用して、ESMをCommonJSに変換して実行する
この記事では、後者の方法を紹介します。
CommonJSにトランスパイルする
ESMをCommonJSに変換する方法はts-jestのドキュメントで紹介されています。
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
transformIgnorePatterns: [`node_modules/(?!(@masatomakino/threejs-spherical-controls)/)`],
}
export default jestConfig
ts-jestをトランスフォーマーとして、TypeScriptやESMのJavaScriptをCommonJSに変換します。transformIgnorePatternsには、依存しているPure ESモジュールを指定します。?!(モジュールのパス)
のように指定すると、指定されたモジュールは変換の対象となります。
Jestのモジュール解決
Jestは独自のモジュール解決処理を実装しています。この処理は、Package.jsonのexports
フィールドを参照し、defaultフィールドを順に解決していくようです。
{
"type": "module",
"exports": {
".": {
"import": {
"types": "./esm/index.d.ts",
"default": "./esm/index.js"
}
}
}
}
したがって、このようにdefaultフォールバックがないexportsフィールドを持つモジュールは、解決に失敗します。
defaultフォールバックを追加すれば、この問題は解決します。
{
"type": "module",
"exports": {
".": {
"import": {
"types": "./esm/index.d.ts",
"default": "./esm/index.js"
},
+ "default": {
+ "types": "./esm/index.d.ts",
+ "default": "./esm/index.js"
+ }
}
}
}
importとdefaultフォールバックをフラットな階層においても解決に成功します。
{
"type": "module",
"exports": {
".": {
"types": "./esm/index.d.ts",
"import": "./esm/index.js",
"default": "./esm/index.js"
}
}
}
個人的な感想
node.jsの周辺ツールにおける、ESMサポートはまだ成熟していません。ツール同士で処理に差があります。どのようなツールで扱われても問題が出ないように、exportsフィールドのdefaultフォールバックは念入りに書き込んでおいた方が安全です。
以上、ありがとうございました。