これは、なにをしたくて書いたもの?
少し前に、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との組み合わせでけっこう使われているものなのでしょうか?
ひとまず、せっかく学んだので押さえておこうと思います。
参考)