Zenn
🔍

tsconfig.jsonについて調べてみた

2025/02/07に公開
16

TypeScriptの設定ファイルtsconfig.jsonに関する調査メモ

target

  • 出力するJavaScriptのバージョンを指定
  • デフォルトは、ES5
  • 適切に設定することで
    • コンパイル時間が短縮される
    • ランタイム時の実行効率が向上する
    • 開発時の型チェックが強化される

targetのオプションごとの挙動の違い

targetオプションごとの出力コードの違いを比較

  • TypeScriptコード(元のコード)
const fetchData = async (url: string): Promise<{ data: string }> => {
  const response = await fetch(url);
  return { data: await response.text() };
};
  • JavaScriptコード(出力コード)

target: "ES5"の場合

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());
    });
};

var fetchData = function (url) {
    return __awaiter(this, void 0, void 0, function () {
        var response, text;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    return [4 /*yield*/, fetch(url)];
                case 1:
                    response = _a.sent();
                    return [4 /*yield*/, response.text()];
                case 2:
                    text = _a.sent();
                    return [2 /*return*/, { data: text }];
            }
        });
    });
};

target: "ES2015"の場合

const fetchData = async (url) => {
  const response = await fetch(url);
  return { data: await response.text() };
};

target: "ES5" → async/awaitがPromiseベースのコードに変換
target: "ES2015" → async/await がそのまま利用可能

→ ES2015にコンパイルすると、

  • コード量が減り、出力ファイルサイズが小さくなる → コンパイル時間が短縮される
  • 最新の構文を利用できるため、JITコンパイラによる最適化が効きやすくなる → ランタイム時の実行効率が向上する

targetオプションごとのlibの違いを比較

target を変更すると、型チェックで使用されるlibのデフォルト値も変わる
target: "ES5"の場合、libのデフォルト値はlib: ["es5", "dom", "dom.iterable"]になる
→ Promiseはlibに含まれていないため、以下のコードは型エラーが発生する

const promise: Promise<string> = new Promise(resolve => resolve("Hello")); // エラー: 'Promise' が見つかりません

targetを"ES2015"の場合、libのデフォルト値はlib: ["es2015", "dom", "dom.iterable"]になる
→ Promiseはes2015に含まれているため、型エラーは発生しない

最適なtargetの選び方

出力されたJavaScriptコードの実行環境に応じて選択

  • ブラウザ上で実行する場合

    • サポート対象の最も古いブラウザバージョンに合わせる
    • 例: Chrome 最新版 / Safari 最新版 / Firefox 最新版 → target: "ES2022"対応表を参照)
  • Node.js上で実行する場合

lib

  • 型チェック時に参照する標準ライブラリを指定
  • targetによってlibのデフォルト値が設定されるが、libを明示的に設定することで、環境に適した型定義を選択できる

libのオプションごとの挙動の違い

"target": "ES2022",
"lib": ["ES2022"] // "target": "ES2022"の時のデフォルト値

の場合、ES2022にfetchは含まれていないため、以下のコードで型エラーが発生する

fetch("https://example.com"); // Cannot find name 'fetch'.

ブラウザAPI(fetchやdocumentなど)を使用する場合は、libにDOMを追加する必要がある

"target": "ES2022",
"lib": ["ES2022", "DOM"]

の場合、fetchはDOMに含まれているため、型エラーは発生しない

最適なlibの選び方

出力されるJavaScriptコードの実行環境に応じて選択

  • ブラウザ上で実行する場合

    • fetchやdocumentの型を使えるように"DOM"や"DOM.Iterable"を設定する
    • 例:
    "target": "ES2022",
    "lib": ["ES2022", "dom", "dom.iterable"]
    
  • Node.js上で実行する場合

    • target"ES2022"の場合でも、なるべく最新のJavaScript仕様を対応するために"ES2024"等を設定する
      • "ESNext"なら常に最新のJavaScript仕様を反映できるが、TypeScriptのアップデートによる予期しない変更のリスクがあるため、基本的に避けるのが良さそう
      • "ES2024"を指定した場合、それ以降のAPIが型チェック上は使用可能となるため、実行時エラーを防ぐために確認し、必要ならポリフィルを導入する
    • 例:
    "target": "ES2022",
    "lib": ["ES2024"]
    

module

  • 出力されるJavaScriptコードのモジュールシステムを指定
  • デフォルトは
    • target: "ES5" の場合 → CommonJS
    • 上記以外 → ES6
  • 適切に設定しないと、環境によってimport/requireの互換性エラーが発生
    • ESModules環境でrequireを使う → SyntaxError: Cannot use require in ES module scope
    • CommonJS環境でimportを使う → SyntaxError: Cannot use import statement outside a module

moduleのオプションごとの挙動の違い

  • TypeScriptコード(元のコード)
// module.ts
export function greet(name: string) {
  return `Hello, ${name}!`;
}

// index.ts
import { greet } from "./module";

console.log(greet("Alice"));
  • JavaScriptコード(出力コード)

"module": "CommonJS" の場合

// module.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.greet = void 0;
function greet(name) {
  return `Hello, ${name}!`;
}
exports.greet = greet;

// index.js
"use strict";
const { greet } = require("./module");
console.log(greet("Alice"));

"module": "esnext"/"nodenext" の場合

// module.js
export function greet(name) {
  return `Hello, ${name}!`;
}

// index.js
import { greet } from "./module.js";
console.log(greet("Alice"));

最適なmoduleの選び方

  • "esnext"/"nodenext"(JavaScriptコードがESMで出力されるもの)を選択
    • ESMにすることで、コードの再利用や管理が効率的に行える
  • ブラウザ上で実行する場合 → esnext
    • 最新のECMAScript機能を使い、モダンブラウザでimport/exportをそのまま利用できる
  • Node.js上で実行する場合 → nodenext
    • Node.jsで、ESMとCommonJSの両方のモジュール形式を使うことができ、どちらの形式でも正常に動作するように処理できる

moduleResolution

  • コンパイル時に、特定のファイル種別をどの順序で見つけるかを指定
  • デフォルトは
    • module: "node16"/"nodenext" の場合 → "node16"/"nodenext"
    • 上記以外 → node
  • 適切に設定しないと、ファイルや型が正しく見つからず、ビルドや実行時にエラーが発生
    • Cannot find module 'xxx'
    • Cannot find module 'xxx' or its corresponding type declarations.
    • TypeError: xxx is not a function

moduleResolutionのオプションごとの挙動の違い

TypeScriptコード(元のコード)

/project
  ├── src/
  │   ├── index.ts
  │   ├── utils.ts
  ├── node_modules/
  │   ├── some-package/
  │   │   ├── package.json
  │   │   ├── index.js
  │   │   ├── index.mjs
  │   │   ├── index.cjs
src/index.ts
src/utils.ts
export const helper = () => console.log("Helper function");
node_modules/some-package/package.json
{
  "main": "./index.js",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  }
}

moduleResolution: "node"の場合

  • モジュールの読み込みの仕組み

    • node_modules を探索
    • package.json の "main" フィールドを参照
    • .d.ts ファイルがあれば型定義として利用
    • import時に拡張子の明示は不要(.ts → .js → .d.ts の順で検索)
  • どのファイルが読み込まれるか

src/index.ts
import { helper } from "./utils"; // utils.tsを参照
import { someFunction } from "some-package"; // package.jsonの"main"からindex.jsを参照
  • 発生しうるエラー
src/index.ts
// TypeError: someFunction is not a function
import { someFunction } from "some-package"; // CommonJSをESMとして読み込むと発生
// "main": "./index.js" を参照
// index.jsがmodule.exports = {} の場合、ESMのimportと互換性がなくエラー

moduleResolution: "node16" / "nodenext"の場合

  • モジュールの読み込みの仕組み

    • package.jsonの"exports"を考慮
    • 拡張子が必須(例: import "./utils.mjs")
    • 拡張子を考慮して .mjs, .cjs, .ts, .js を選択
  • どのファイルが読み込まれるか

import { helper } from "./utils.mjs"; // utils.mjsを参照
import { someFunction } from "some-package"; // package.jsonの"main"からindex.mjsを参照
const { someFunction } = require("some-package"); // package.jsonの"main"からindex.cjsを参照
  • 発生しうるエラー
// Cannot find module './utils'
import { helper } from "./utils"; // 拡張子が必須なので、"./utils.mjs" や "./utils.ts" にする必要がある

moduleResolution: "bundler"の場合

  • モジュールの読み込みの仕組み(バンドラーに依存)

    • TypeScriptは型情報のみを処理し、どのファイルを使うかは判断しない
    • 相対パスのimportは.ts, .js, .mjs などをバンドラーが探す
    • node_modulesの探索やファイルの選択はバンドラーが行う
    • node_modulesのimportはバンドラーがpackage.jsonの"exports"やmainFieldsを参照
  • どのファイルが読み込まれるか(Viteの場合)

import { helper } from "./utils"; // .ts, .js, .mjsなどをesbuildが判別して参照
import { someFunction } from "some-package"; // esbuildがpackage.jsonの"exports"やmainFieldsを参照
  • どのファイルが読み込まれるか(Webpackの場合)
import { helper } from "./utils"; // .ts, .js, .mjsなどをresolve.extensionsの設定に従って選択
import { someFunction } from "some-package"; // package.jsonの"exports"やmainFieldsを参照

最適なmoduleResolutionの選び方

出力されたJavaScriptコードの実行環境に応じて選択

  • ブラウザ上で実行する場合(Vite、Webpackなどのバンドラーを使用) → bundler
    → バンドラーがESM、CommonJSの違いを吸収する
  • Node.js上で実行する場合 → node16 or nodenext
    → ESM / CommonJS の両方を扱える

まとめ

出力されたJavaScriptコードが

  • ブラウザ上で実行される場合
{
  "target": "ES2023", // 主に最新の主要ブラウザ(Chrome、Edge、Firefox、Safariなど)を想定している場合
  "lib": ["ES2023", "DOM", "DOM.Iterable"], // ブラウザAPIの型を参照可能
  "module": "esnext", // 最新のモジュールシステムを使用
  "moduleResolution": "bundler" // バンドラーに最適化されたモジュール解決方式を採用
}
  • Node.js上で実行される場合
{
  "target": "ES2023", // Node.js22を対象にしている場合
  "lib": ["ES2024"], // ES2024の標準ライブラリの型を参照可能
  "module": "nodenext", // Node.jsのESM/CJS 両対応のモジュールシステムを使用
  "moduleResolution": "nodenext" // Node.jsのモジュール解決方式を採用
}

参考資料

https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

16

Discussion

ログインするとコメントできます