参考
- TypeScript で"moduleResolution": "Node"は使わないほうがいい
- tsconfig.jsonのよく使いそうなオプションを理解する
- TypeScript 4.7 と Native Node.js ESM
- TypeScriptのmoduleSuffixesについて考えて納得した
Node.jsのモジュール解決
Node.jsにはcommonJSとESMの2通りのモジュール解決が存在する。 ESMはNode12からサポートされた。
ESMとCJSの相互読み込み
ESMとCJSを混在させる場合は以下の通り。
読み込み | Static import import {} from "foo" |
Dynamic importimport(path).then |
require() |
---|---|---|---|
ESM から ESM を読み込む |
OK | OK | NG |
CJS から CJS を読み込む |
NG | NG | OK |
ESM から CJS を読み込む |
OK | NG | NG |
CJS から ESM を読み込む |
NG | OK | NG |
package.json type
フィールド
Node>=12で使える。プロジェクト内のモジュール解決方法を指定する。 package.jsonはディレクトリごとに配置できるので、ディレクトリ単位での解決も可能。
module
Node.jsでモジュール解決する時、importされたファイルにもっとも近いESMファイルが使われる。
commonjs
Node.jsでモジュール解決する時、importされたファイルにもっとも近いCommonJSファイルが使われる。
tsconfig.json module
フィールド
吐き出したJSのモジュール読み込みを変更するオプション。
import { foo } from "./foo.js";
foo();
// module: CommonJS
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const foo_js_1 = require("./foo.js");
(0, foo_js_1.foo)();
// module: UMD
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./foo.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const foo_js_1 = require("./foo.js");
(0, foo_js_1.foo)();
});
// module: Node16
import { foo } from "./foo.js";
foo();
// module: ES2022
import { foo } from "./foo.js";
foo();
tsconfig.json moduleResolution
Node/Node10
mainがエントリポイントになる。 外部パッケージのtypeやexportsは無視される。
Node16
package.jsonのtypeによってCommonJSかESMが使用され、exportsがエントリポイントになる。
"exports": {
".": {
"require": "./commonjs/index.cjs",
"import": "./esm/index.mjs"
}
}
// type:CommonJSなら: index.cjsが読み込まれる
// type:ES2022なら: index.mjsが読み込まれる
import { foo } from "package";
Bundler
インポート文の拡張子補完が行われる。つまり以下の文はfoo.tsが読み込まれる。 exportsがエントリポイントになる。
import { value } from "./foo";
拡張子の出力
トランスパイル後のコードのimportに拡張子を含めたい場合は、以下のように書く。TSのモジュール解決上は拡張子を無視していずれもfoo.tsを探しに行く。(foo.jsがあっても無視される)
import { foo } from "./foo.js";
import { foo } from "./foo.cjs";
import { foo } from "./foo.mjs";
トリプルスラッシュコメントの型参照はresolution-modeオプションで区別する。
/// <reference types="foo" resolution-mode="require" />
/// <reference types="bar" resolution-mode="import" />