これは、なにをしたくて書いたもの?
少し前に、Node.jsでECMAScript Modulesを試してみました。
Node.jsでECMAScript Modulesを試す - CLOVER🍀
前回はいきなりTypeScriptでやるとわからなくなるだろうから素のJavaScript(Node.js)でECMAScript Modulesを扱ったのですが、今回は
TypeScriptで扱ってみます。
参照しているドキュメントは、TypeScript 5.5時点のものです。
Node.jsのモジュールシステム
少し復習的に。Node.jsで扱えるモジュールシステムにはECMAScript ModulesとCommonJS Modulesの2つがあります。
Modules: Packages | Node.js v20.17.0 Documentation
どういう違いがあるのかはこちらにある程度まとめて書いたのですが、デフォルトはCommonJS Modulesで扱うのでECMAScript Modulesとして
扱うには明示的にファイルの拡張子を.mjs
にするか、package.json
のtype
フィールドの値をmodule
にするという話でした。
※だいぶ端折ってます
Node.jsでECMAScript Modulesを試す - CLOVER🍀
そして、ECMAScript Modulesを使うとモジュールのインポートをimport
文またはimport()
式で行うようになり、インポートするモジュールの
指定に拡張子まで含める必要があったりしました。
TypeScriptとECMAScript Modules
TypeScriptでのモジュールの話は、こちらのモジュールリファレンスを見ることになります。
TypeScript: Documentation - Modules - Introduction
モジュールの出力形式は、こちらに書かれています。
Modules - Theory / The module output format
まず注意しておくことは、ホスト(TypeScriptによりトランスパイルされた結果を実行する環境)がどのようなモジュール形式を期待して
いるかということですね。それを踏まえたうえで、TypeScriptによるファイルの出力形式を調整します。
In any project, the first question about modules we need to answer is what kinds of modules the host expects, so TypeScript can set its output format for each file to match.
ポイントになるのはtsconfig.json
のmodule
オプションです。
Intro to the TSConfig Reference / Modules / Module - module
まずはこちらに書かれている箇条書きを見るのがよいでしょう。
Modules - Theory / The module output format
module
オプションで設定できる値はこちらです。
module のオプション |
意味 |
---|---|
node16 |
特定の相互運用性と検出ルールにしたがって、ECMAScript ModulesとCommonJS Modulesを並行してサポートするNode.js 16以上のモジュールシステムを反映する |
nodenext |
現在の値はnode16 と同じだが、Node.jsのモジュールシステムが進化するにつれて最新のNode.jsバージョンを反映する動的なターゲットになる |
es2015 |
JavaScriptモジュールにimport およびexport が導入された最初のECMAScript 2015言語仕様を反映している |
es2020 |
es2015 にimport.meta とexport * as ns from "mod" を追加したもの |
es2022 |
es2020 にトップレベルのawait のサポートを追加したもの |
esnext |
現在はes2022 と同じだが、最新のECMAScript仕様と今後の仕様バージョンに含まれることが期待されるモジュール関連のステージ3+提案を反映した動的なターゲット |
commonjs 、system 、amd 、umd |
各モジュールシステム向けの出力を生成する。新規プロジェクトには推奨されない |
また、ここに載っていない値としてTypeScript 5.4で追加されたpreserve
があります。
Intro to the TSConfig Reference / Modules / Module - module / preserve
これは文単位でexport
、import
の形式が保持されるオプションのようです。
ところで、Node.jsで使うことを考えるとnode16
およびnodenext
と、esnext
やes2022
、あるいはcommonjs
のどれを指定すればよいか
ですが、これはこちらに書かれています。
Modules - Theory / The module output format
Node.jsで実行する場合は、module
オプションにesnext
やcommonjs
を指定するのは誤りです。
Node.js’s rules for module format detection and interoperability make it incorrect to specify module as esnext or commonjs for projects that run in Node.js, even if all files emitted by tsc are ESM or CJS, respectively.
Node.jsで実行する場合は、node16
およびnodenext
のみが正しいmodule
の設定です。
The only correct module settings for projects that intend to run in Node.js are node16 and nodenext.
そして、module
オプションにnode16
またはnodenext
を設定すると、暗黙的に他のオプションにも影響を与えます。
Modules - Reference / The module compiler option / node16, nodenext / Implied and enforced options
いずれも暗黙的に、そして強制的に設定されるようです。
module
オプションにnode16
を指定した場合moduleResolution
オプションにnode16
が設定されるtarget
オプションにes2022
が設定されるesModuleInterop
オプションにtrue
が設定される
module
オプションにnodenext
を指定した場合moduleResolution
オプションにnodenext
が設定されるtarget
オプションにesnext
が設定されるesModuleInterop
オプションにtrue
が設定される
esModuleInterop
オプションをtrue
にすると、TypeScriptを使っている時にECMAScript ModulesからCommonJS Modulesを読み込めるように
なります。
moduleResolution
オプションについては後述します。
モジュール形式の検出
こちらでは、モジュール形式の検出ルールとTypeSccriptによる出力フォーマットのルールが書かれています。
Modules - Theory / The module output format / Module format detection
ざっくり言うと、こんな感じでしょうか。
- ファイル拡張子が
.mts
の場合は、.mjs
(ECMAScript Modules)にトランスパイルされる - ファイル拡張子が
.cts
の場合は、.cjs
(Common Modules)にトランスパイルされる - ファイル拡張子が
.ts
の場合は、.js
にトランスパイルされる- この時、もっとも近い親ディレクトリ階層にある
package.json
のtype
フィールドがmodule
でなければCommonJS Modulesにトランスパイルされる package.json
のtype
フィールドがmodule
の場合はECMAScript Modulesにトランスパイルされる
- この時、もっとも近い親ディレクトリ階層にある
JavaScriptの時の拡張子のルールを知っておくと、およそ予想できる挙動ではないかなと思います。
Module resolution
次は、Module resolutionについて少し触れておきます。
Modules - Theory / Module resolution
tsconfig.json
のmoduleResolution
オプションのことです。モジュールの解決戦略ですね。
これもやっぱり、ホスト側の事情に影響を受けます。
Modules - Theory / Module resolution / Module resolution is host-defined
moduleResolution
オプションで指定できる値はこちらです。
moduleResolution のオプション |
意味 |
---|---|
classic |
TypeScriptの最も古いモジュール解決モードで、module がcommonjs 、node16 、nodenext 以外の値に設定されている場合のデフォルト値。さまざまなRequireJS構成に対するベストエフォートでの解決を提供するもの。TypeScript 6.0で廃止される予定であり、新規プロジェクトでは使用しないこと |
node10 |
以前はnode と呼ばれていた値で、module オプションがcommonjs の場合のデフォルト値。Node.js 12より前であれば良いオプションだが、現在のNode.jsはECMAScript Modulesのサポートがあるため良い選択ではない。新規プロジェクトでは使用しないこと |
node16 |
module オプションにnode16 を指定した時に対応する値。Node.js 12以降はECMAScript ModulesとCommonJS Modulesの両方をサポートしているため、モジュール形式の検出ルールに従い動作が決定する |
nodenext |
現在はnode16 と同一で、module オプションにnodenext を指定した場合のデフォルト値。これは新しいNode.jsがモジュール解決機能を追加する度に、それをサポートすることを見据えたモード |
bundler |
Node.js 12でnpmパッケージをインポートするためのいくつかのモジュール解決機能(package.json のexports とimports フィールド)が追加され、多くのバンドラーはECMAScript Modulesのより厳格なルールを採用することなくこれらの機能を採用した。このモードはバンドラーをターゲットとする基本アルゴリズムを提供する。デフォルトではpackage.json のexports とimports をサポートしているが、これらを無視するように構成することもできる。このモードを使うにはmodule をesnext に設定する必要がある |
つまり、Node.jsの場合はnode16
またはnodenext
を指定すると、package.json
のtype
フィールドの指定にしたがってモジュールの
解決方法が変わるということですね。
ドキュメントを見るのはこれくらいにして、あとは試していってみましょう。
環境
今回の環境はこちら。
$ node --version v20.16.0 $ npm --version 10.8.1
お題
前回、ふつうにJavaScriptで書いたこちらをTypeScriptに置き換えていくことを目指します。
Node.jsでECMAScript Modulesを試す - CLOVER🍀
Node.jsプロジェクトをTypeScript × ECMAScript Modulesとして作成する
Node.jsプロジェクトを作成。
$ npm init -y Wrote to /path/to/hello-esm/package.json: { "name": "hello-esm", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "" }
type
フィールドをmodule
にしてECMAScript Modulesにします。
package.json
{ "name": "hello-esm", "version": "1.0.0", "main": "index.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "" }
TypeScript、それからPrettierをインストール。
$ npm i -D typescript $ npm i -D @types/node@v20 $ npm i -D prettier
tsconfig.json
はこのようにしました。
tsconfig.json
{ "compilerOptions": { "target": "esnext", "module": "nodenext", "moduleResolution": "nodenext", "lib": ["esnext"], "baseUrl": "./src", "outDir": "dist", "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true }, "include": [ "src" ] }
module
はnodenext
にして、target
とmoduleResolution
は暗黙の値を明示的に書いた感じです。
"target": "esnext", "module": "nodenext", "moduleResolution": "nodenext",
Prettierの設定。
.prettierrc.json
{ "singleQuote": true, "printWidth": 120 }
この時点でのscripts
およびdevDependencies
。
"scripts": { "build": "tsc --project .", "build:watch": "tsc --project . --watch", "format": "prettier --write src test" }, ... "devDependencies": { "@types/node": "^20.14.8", "prettier": "^3.3.3", "typescript": "^5.5.4" }
ソースコードを書いていきます。
拡張子を.mts
にして、明示的にECMAScript Modulesとして書いたもの。トランスパイル後は.mjs
になります。
src/calc.mts
export function plus(a: number, b: number): number { return a + b; } export function minus(a: number, b: number): number { return a - b; }
拡張子は.ts
にして、トランスパイル後のモジュールシステムの解決はtsconfig.json
のmoduleResolution
経由でpackage.json
のtype
に
任せたもの。今回はtype
がmodule
なのでECMAScript Modulesとして扱われることになります。
src/calc-resolve-type.ts
export function plusAsResolveModuleType(a: number, b: number): number { return a + b; } export function minusAsResolveModuleType(a: number, b: number): number { return a - b; }
拡張子を.cts
にして、明示的にCommonJS Modulesとして書いたもの。トランスパイル後は.cjs
になります。
src/calc-cjs.cts
export function plusAsCjs(a: number, b: number): number { return a + b; } export function minusAsCjs(a: number, b: number): number { return a - b; }
JavaScriptで書いていた時と違って、構文上はCommonJSになることはわかりませんけどね。
これらを呼び出すスクリプト。
src/run.ts
import { plus, minus } from './calc.mjs'; import { plusAsResolveModuleType, minusAsResolveModuleType } from './calc-resolve-type.js'; import { plusAsCjs, minusAsCjs } from './calc-cjs.cjs'; console.log('use ECMAScript Modules(mts -> mjs) source'); console.log(` plus(1, 2) = ${plus(1, 2)}`); console.log(` minus(5, 2) = ${minus(5, 3)}`); const calc = await import('./calc.mjs'); console.log('use ECMAScript Modules(mts -> mjs) source as Dynamic import'); console.log(` plus(1, 2) = ${calc.plus(1, 2)}`); console.log(` minus(5, 2) = ${calc.minus(5, 3)}`); console.log('use ECMAScript Modules(ts type module) source'); console.log(` plusAsResolveModuleType(1, 2) = ${plusAsResolveModuleType(1, 2)}`); console.log(` minusAsResolveModuleType(5, 2) = ${minusAsResolveModuleType(5, 3)}`); console.log('use CommonJS Modules(cts -> cjs) source'); console.log(` plusAsCjs(1, 2) = ${plusAsCjs(1, 2)}`); console.log(` minusAdCjs(5, 2) = ${minusAsCjs(5, 3)}`);
ポイントは、import
する時のいずれも拡張子はトランスパイル後のもの(.mjs
、.js
、.cjs
)になっていることです。
.mts
、.ts
、.cts
ではありません。
import { plus, minus } from './calc.mjs'; import { plusAsResolveModuleType, minusAsResolveModuleType } from './calc-resolve-type.js'; import { plusAsCjs, minusAsCjs } from './calc-cjs.cjs'; ... const calc = await import('./calc.mjs'); ...
ビルド。
$ npm run build # tsc --project .
結果。
$ ll dist 合計 24 drwxrwxr-x 2 xxxxx xxxxx 4096 8月 10 22:14 ./ drwxrwxr-x 7 xxxxx xxxxx 4096 8月 10 22:14 ../ -rw-rw-r-- 1 xxxxx xxxxx 236 8月 10 22:14 calc-cjs.cjs -rw-rw-r-- 1 xxxxx xxxxx 137 8月 10 22:14 calc-resolve-type.js -rw-rw-r-- 1 xxxxx xxxxx 99 8月 10 22:14 calc.mjs -rw-rw-r-- 1 xxxxx xxxxx 973 8月 10 22:14 run.js
.mts
は.mjs
に、.cts
は.cjs
になっています。
中身を見てみましょう。
ECMAScript Modulesnにトランスパイルされた2ファイル。
dist/calc.mjs
export function plus(a, b) { return a + b; } export function minus(a, b) { return a - b; }
こちらはpackage.json
のtype
がmodule
かつtsconfig.json
のmodule
がnodenext
になっているので、ECMAScript Modulesになっています。
dist/calc-resolve-type.js
export function plusAsResolveModuleType(a, b) { return a + b; } export function minusAsResolveModuleType(a, b) { return a - b; }
CommonJS Modulesとしてトランスパイルされたもの。
dist/calc-cjs.cjs
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.plusAsCjs = plusAsCjs; exports.minusAsCjs = minusAsCjs; function plusAsCjs(a, b) { return a + b; } function minusAsCjs(a, b) { return a - b; }
dist/run.js
import { plus, minus } from './calc.mjs'; import { plusAsResolveModuleType, minusAsResolveModuleType } from './calc-resolve-type.js'; import { plusAsCjs, minusAsCjs } from './calc-cjs.cjs'; console.log('use ECMAScript Modules(mts -> mjs) source'); console.log(` plus(1, 2) = ${plus(1, 2)}`); console.log(` minus(5, 2) = ${minus(5, 3)}`); const calc = await import('./calc.mjs'); console.log('use ECMAScript Modules(mts -> mjs) source as Dynamic import'); console.log(` plus(1, 2) = ${calc.plus(1, 2)}`); console.log(` minus(5, 2) = ${calc.minus(5, 3)}`); console.log('use ECMAScript Modules(ts type module) source'); console.log(` plusAsResolveModuleType(1, 2) = ${plusAsResolveModuleType(1, 2)}`); console.log(` minusAsResolveModuleType(5, 2) = ${minusAsResolveModuleType(5, 3)}`); console.log('use CommonJS Modules(cts -> cjs) source'); console.log(` plusAsCjs(1, 2) = ${plusAsCjs(1, 2)}`); console.log(` minusAdCjs(5, 2) = ${minusAsCjs(5, 3)}`);
実行してみます。
$ node dist/run.js use ECMAScript Modules(mts -> mjs) source plus(1, 2) = 3 minus(5, 2) = 2 use ECMAScript Modules(mts -> mjs) source as Dynamic import plus(1, 2) = 3 minus(5, 2) = 2 use ECMAScript Modules(ts type module) source plusAsResolveModuleType(1, 2) = 3 minusAsResolveModuleType(5, 2) = 2 use CommonJS Modules(cts -> cjs) source plusAsCjs(1, 2) = 3 minusAdCjs(5, 2) = 2
OKですね。
Jest+ts-jestでテストを書く
最後はテストです。
ふだんはTypeScript+Jestの時はesbuild-jestを使っているのですが、ちょっと大変な感じがしたので今回はオーソドックスにts-jestに
しておきました…。
Jestおよびts-jestのインストール。
$ npm i -D jest @types/jest $ npm i -D ts-jest
バージョンはこちら。
"devDependencies": { "@types/jest": "^29.5.12", "@types/node": "^20.14.8", "jest": "^29.7.0", "prettier": "^3.3.3", "ts-jest": "^29.2.4", "typescript": "^5.5.4" }
scripts
はこうしておきました。
"scripts": { "build": "tsc --project .", "build:watch": "tsc --project . --watch", "test": "jest", "format": "prettier --write src test" },
Jestの設定ファイルを生成。
$ npm init jest
jest.config.mjs
const config = { collectCoverage: true, coverageDirectory: "coverage", coverageProvider: "v8", testMatch: [ "**/__tests__/**/*.?([cm])[jt]s?(x)", "**/?(*.)+(spec|test).?([cm])[tj]s?(x)" ], testEnvironment: "node", extensionsToTreatAsEsm: [".ts"], moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1", }, transform: { "^.+\\.m?[tj]sx?$": [ "ts-jest", { useESM: true } ] }, }; export default config;
このあたりは
extensionsToTreatAsEsm: [".ts"], moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1", }, transform: { "^.+\\.m?[tj]sx?$": [ "ts-jest", { useESM: true } ] },
こちらを見て設定。
JestのECMAScript Modulesに関する記述も参考に。
まずはテストを素直に書いてみます。
test/calc.test.ts
import { plus, minus } from '../src/calc.mjs'; import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.js'; import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cjs'; test('use ECMAScript Modules(mts -> mjs) source', async () => { expect(plus(1, 2)).toStrictEqual(3); expect(minus(5, 2)).toStrictEqual(3); }); test('use ECMAScript Modules(mts -> mjs) source as Dynamic import', async () => { const calc = await import('../src/calc.mjs'); expect(calc.plus(1, 2)).toStrictEqual(3); expect(calc.minus(5, 2)).toStrictEqual(3); }); test('use ECMAScript Modules(js type module) source', async () => { expect(plusAsResolveModuleType(1, 2)).toStrictEqual(3); expect(minusAsResolveModuleType(5, 2)).toStrictEqual(3); }); test('use CommonJS Modules(cts -> cjs) source', async () => { expect(plusAsCjs(1, 2)).toStrictEqual(3); expect(minusAsCjs(5, 2)).toStrictEqual(3); });
テストを実行。
$ npm test
すると、import
しようとしているモジュールがないと怒られます。
> hello-esm@1.0.0 test > jest FAIL test/calc.test.ts ● Test suite failed to run Cannot find module '../src/calc.mjs' from 'test/calc.test.ts' > 1 | import { plus, minus } from '../src/calc.mjs'; | ^ 2 | import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.js'; 3 | import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cjs'; 4 | at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11) at Object.<anonymous> (test/calc.test.ts:1:1)
どうやら、拡張子を.mts
や.ts
などにした方がよさそうです。
import { plus, minus } from '../src/calc.mts'; import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.ts'; import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cts'; ... const calc = await import('../src/calc.mts');
再度テストを実行すると、その拡張子は受け付けていないからallowImportingTsExtensions
を有効にしてね、と怒られました。
> hello-esm@1.0.0 test > jest FAIL test/calc.test.ts ● Test suite failed to run test/calc.test.ts:1:29 - error TS5097: An import path can only end with a '.mts' extension when 'allowImportingTsExtensions' is enabled. 1 import { plus, minus } from '../src/calc.mts'; ~~~~~~~~~~~~~~~~~ test/calc.test.ts:2:67 - error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled. 2 import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.ts'; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test/calc.test.ts:3:39 - error TS5097: An import path can only end with a '.cts' extension when 'allowImportingTsExtensions' is enabled. 3 import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cts'; ~~~~~~~~~~~~~~~~~~~~~ test/calc.test.ts:11:29 - error TS5097: An import path can only end with a '.mts' extension when 'allowImportingTsExtensions' is enabled. 11 const calc = await import('../src/calc.mts'); ~~~~~~~~~~~~~~~~~
こちらのことですね。allowImportingTsExtensions
をtrue
にすることで、拡張子が.ts
、.mts
、.cts
であっても指定できるようになります。
もとのtsconfig.json
に書くのも微妙だったので、tsconfig.json
を継承したファイルを作成。
tsconfig.test.json
{ "extends": "./tsconfig", "compilerOptions": { "baseUrl": "./", "noEmit": true, "allowImportingTsExtensions": true }, "include": [ "src", "test" ] }
こちらをts-jestに指定するようにしました。
transform: { "^.+\\.m?[tj]sx?$": [ "ts-jest", { tsconfig: "tsconfig.test.json", useESM: true } ] },
再度テストを実行すると、CommoJS Modulesだけが読み込みに失敗します…。
Details: /path/to/src/calc-cjs.cts:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export function plusAsCjs(a: number, b: number): number { ^^^^^^ SyntaxError: Unexpected token 'export' 1 | import { plus, minus } from '../src/calc.mts'; 2 | import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.ts'; > 3 | import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cts'; | ^ 4 | 5 | test('use ECMAScript Modules(mts -> mjs) source', async () => { 6 | expect(plus(1, 2)).toStrictEqual(3); at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14) at Object.<anonymous> (test/calc.test.ts:3:1)
今回はこちらは諦めることにしました…。
test/calc.test.ts
import { plus, minus } from '../src/calc.mts'; import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type.ts'; // import { plusAsCjs, minusAsCjs } from '../src/calc-cjs.cts'; test('use ECMAScript Modules(mts -> mjs) source', async () => { expect(plus(1, 2)).toStrictEqual(3); expect(minus(5, 2)).toStrictEqual(3); }); test('use ECMAScript Modules(mts -> mjs) source as Dynamic import', async () => { const calc = await import('../src/calc.mts'); expect(calc.plus(1, 2)).toStrictEqual(3); expect(calc.minus(5, 2)).toStrictEqual(3); }); test('use ECMAScript Modules(js type module) source', async () => { expect(plusAsResolveModuleType(1, 2)).toStrictEqual(3); expect(minusAsResolveModuleType(5, 2)).toStrictEqual(3); }); /* test('use CommonJS Modules(cts -> cjs) source', async () => { expect(plusAsCjs(1, 2)).toStrictEqual(3); expect(minusAsCjs(5, 2)).toStrictEqual(3); }); */
これでテストがOKになりました。
$ npm test > hello-esm@1.0.0 test > jest PASS test/calc.test.ts ✓ use ECMAScript Modules(mts -> mjs) source (4 ms) ✓ use ECMAScript Modules(mts -> mjs) source as Dynamic import (1 ms) ✓ use ECMAScript Modules(js type module) source ----------------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------------------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | calc-resolve-type.ts | 100 | 100 | 100 | 100 | calc.mts | 100 | 100 | 100 | 100 | ----------------------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 3.551 s Ran all test suites.
そういえばこの構成だと、JavaScriptで書いていた時にJestでECMAScript Modulesを使う際にNode.jsにフラグを指定していたのですが、
これは要りませんでしたね。
"scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", ... },
今回はjestコマンドをそのまま実行しました。
"scripts": { "build": "tsc --project .", "build:watch": "tsc --project . --watch", "test": "jest", "format": "prettier --write src test" },
ところで、設定がいろいろとめんどうな感じだったのでものは試しにとimport
時に拡張子を外してみたら
import { plus, minus } from '../src/calc';
怒られますよね、やっぱり…。
FAIL test/calc.test.ts ● Test suite failed to run test/calc.test.ts:1:29 - error TS2307: Cannot find module '../src/calc' or its corresponding type declarations. 1 import { plus, minus } from '../src/calc'; ~~~~~~~~~~~~~
でも、拡張子が.ts
のファイルをimport
する場合なら外してもいいみたいです…(省略して怒られたのは.mts
のファイル)。
import { plusAsResolveModuleType, minusAsResolveModuleType } from '../src/calc-resolve-type';
おわりに
TypeScriptとNode.jsでECMAScript Modulesを試してみました。
先にNode.js(JavaScript)のみでECMAScript Modulesを試していたので理解が早かったですが、tsconfig.json
のmodule
をどう指定するのが
よいかをちゃんと見れてよかったですね。
あと、Jestでけっこう苦労したのですが…どうなんでしょう、ECMAScript Modulesとの組み合わせでけっこう使われているものなのでしょうか?
ひとまず、せっかく学んだので押さえておこうと思います。
参考)