$ ryokkkke.com/TypeScript/tsconfig.json

module

概要

https://www.typescriptlang.org/tsconfig#module

module: "ESNEXT"

出力する JS のモジュールの仕組みとして何を使用するかを指定します。

指定できる値は(v4.0.3 現在)

  • CommonJStargetES3 もしくは ES5 の場合にデフォルト値)
  • ES6/ES2015targetES6 以上の場合にデフォルト値)
  • ES2020
  • None
  • UMD
  • AMD
  • System
  • ESNext

になります。

それぞれの値を指定したときにどういったコードが吐き出されるか実際に見たい場合は、公式のドキュメントをご覧ください。

値の方針

  • 基本的には未指定で OK。
    • targetが適切に設定されていれば、CommonJSもしくはES2015(ES Modules)のどちらか適切な方が使用される。
  • v4.0.3 現在、Dynamic Import (動的インポート)を使用する場合、
    • 実行環境が ES2020 に対応している場合はES2020を指定する。
    • そうでない場合は、CommonJSを指定する。
    • 詳しくは後述。
  • 実行環境が ES2020 をサポートしていることを保障できるのであれば、ES2020を指定する。
    • 新しい仕様が出るたびに同じ考え方で指定していく。
  • 何らかの事情でそれ以外のモジュールの仕組みを使用したい場合に、該当する値を指定する。

デフォルト値の意味

デフォルト値について要約すると「ES6 よりも前はCommonJS、ES6 以降はES6です。
ES6/ES2015っていうのは、 ES Modules のことです)

つまり、ECMAScript の仕様として ES6 からは公式にモジュールの仕組み(ES Modules)が存在するので、「実行環境が ES6 以上なら、非公式の CommonJS じゃなくて公式の ES Modules 使おうね」という意味です。

ES2015ES6)とES2020の違い

どちらも ES Modules に変わりはないのですが、ES2020 からモジュール関連の新しい仕組みがいくつか策定されました。
moduleES2020にすれば、それらの機能が Polyfill コードなしで使用できます。

新しい機能というのは Dynamic Import のことです。
(本当は ES2020 からimport.metaも仕様に入ってるはずなんですが、実際にコンパイルしてみるとimport.metaは v4.0.3 ではまだESNEXTに含まれるようです。)

Dynamic Import を使ったコードのコンパイル結果を比べてみましょう。

// ES2020ではトップレベルawaitが使えますが、今回はそれよりも前の仕様にもコンパイルするので統一のため関数で囲いました
(async () => {
  const { Hoge } = await import("./hoge"); // Dynamic Import

  console.log(Hoge);
})();

上記のような新しい仕様を使った TS コードがあったとして、コンパイル結果は以下。

module 未指定

{
  "target": "ES2015"
  // "module"は未指定なのでデフォルト値の "ES2015"
}
コンパイル結果
src/index.ts:2:26 - error TS1323: Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.

2   const { Hoge } = await import("./hoge"); // Dynamic Import
                           ~~~~~~~~~~~~~~~~

Found 1 error.

エラーです。
前述の通りmoduleを指定しない場合、デフォルト値は(今回はtargetES2015なので)ES2015になります。

Dynamic Import は ES2020 の ES Modules の新しい仕様であって ES2015 の古い ES Modules の仕様では表現できないので、エラーになります。

module: ES2020

{
  "target": "ES2015",
  "module": "ES2020"
}
コンパイル結果
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
(() => __awaiter(void 0, void 0, void 0, function* () {
    const { Hoge } = yield import("./hoge"); // Dynamic Import
    console.log(Hoge);
}))();

正常にコンパイルされました。
注意が必要なのは、targetES2015でも、moduleES2020の場合、モジュール部分のコンパイル結果はES2020に準拠したコードになるということです。

上記のコンパイル結果を見るとわかりますが、awaitは ES2015 に準拠したコードに書き換わっていますが、Dynamic Import 部分はそのままになっています。

module: CommonJS

{
  "target": "ES2015",
  "module": "CommonJS"
}
コンパイル結果
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
(() => __awaiter(void 0, void 0, void 0, function* () {
    const { Hoge } = yield Promise.resolve().then(() => __importStar(require("./hoge"))); // Dynamic Import
    console.log(Hoge);
}))();

正常にコンパイルされました。
こちらは見ての通り、Dynamic Import 部分が Pollyfill コードに置き換わっています。

  • v4.0.3 現在、Dynamic Import (動的インポート)を使用する場合、
    • 実行環境が ES2020 に対応している場合はES2020を指定する。
    • そうでない場合は、CommonJSを指定する。

つまり、v4.0.3 現在、ES2020 に対応していない環境で Dynamic Import を使用する場合はmoduleCommonJSを指定する必要があるということになります。