0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【TypeScript】tsconfig.json の moduleResolution の役割を理解する

Posted at

はじめに

TypeScript を使っていて「モジュールのインポートがうまくいかない...」「パスの解決エラーが出る...」と困ったことはありませんか?

特に tsconfig.jsonmoduleResolution 設定は、モジュールの解決方法を決定する重要な設定ですが、その挙動の違いが分かりにくいですよね。

この記事では、実際のサンプルコードを使いながら、moduleResolution の各設定値の役割と動作の違いを整理します。

開発環境

開発環境は以下の通りです。

  • Windows11
  • TypeScript 5.9.2
  • Node.js 22.18.0
  • npm 11.5.2

結論

moduleResolution は「TypeScript がモジュールをどのように探すか」を決定する設定です。

設定値 説明 主な用途
node10 (旧: node) Node.js の従来の解決方式 CommonJS プロジェクト
node16 Node.js 12+ の ESM/CJS 両対応 現代的な Node.js プロジェクト
nodenext 最新の Node.js 解決方式 推奨設定
bundler bundler 前提の解決方式 Vite、webpack などを使う場合

moduleResolution とは?

moduleResolution は、TypeScript がインポート文を解析する際に「どのファイルを読み込むべきか」を決定するアルゴリズムを指定します。

例えば、以下のようなインポート文があるとき:

import { add } from "./math";
import express from "express";

TypeScript は以下を決定する必要があります:

  • "./math" → どのファイルを指すのか?(math.ts? math.tsx? math/index.ts?)
  • "express"node_modules のどこを見るのか?
  • 拡張子の省略は許可されるのか?

この解決方法を決めるのが moduleResolution です。

プロジェクトの準備

以下のディレクトリ構成で動作を確認します。

project/
├── src/
│   ├── utils/
│   │   ├── math.ts
│   │   └── index.ts
│   ├── helpers/
│   │   └── logger.mjs
│   └── main.ts
├── package.json
└── tsconfig.json

サンプルコード

src/utils/math.ts

export const add = (a: number, b: number): number => a + b;
export const multiply = (a: number, b: number): number => a * b;

src/utils/index.ts

export { add, multiply } from "./math";
export const version = "1.0.0";

src/helpers/logger.mjs

export const log = (message: string) => {
  console.log(`[LOG] ${message}`);
};

src/main.ts

// ケース1: 拡張子なしの相対パス
import { add } from "./utils/math";

// ケース2: ディレクトリインポート
import { version } from "./utils";

// ケース3: .mjs ファイルのインポート
import { log } from "./helpers/logger.mjs";

// ケース4: node_modules からのインポート
import express from "express";

log(`Version: ${version}`);
console.log(add(2, 3));

1. moduleResolution: "node10" (旧: "node")

従来の Node.js スタイルのモジュール解決方式です。

設定

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "node10"
  }
}

動作の特徴

拡張子の自動補完

import { add } from "./utils/math" は以下の順序でファイルを探します:

  1. ./utils/math.ts
  2. ./utils/math.tsx
  3. ./utils/math.d.ts

拡張子を省略しても TypeScript が自動的に補完してくれます。

ディレクトリインデックス解決

import { version } from "./utils" は以下の順序でファイルを探します:

  1. ./utils.ts
  2. ./utils.tsx
  3. ./utils.d.ts
  4. ./utils/index.ts
  5. ./utils/index.tsx
  6. ./utils/index.d.ts

package.json の types フィールド

node_modules 内のパッケージは package.jsontypes または typings フィールドを参照します。

{
  "name": "express",
  "main": "index.js",
  "types": "index.d.ts"
}

制限事項

  • .mjs ファイルのインポート時に拡張子の記述が必要
  • ESM と CJS の区別が曖昧
  • package.jsonexports フィールドに非対応

2. moduleResolution: "node16" / "nodenext"

Node.js 12+ の ESM サポートに対応した解決方式です。

設定

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}

Note: node16nodenext はほぼ同じですが、nodenext は将来的な Node.js の変更に追従します。

動作の特徴

package.json による ESM/CJS の判定

プロジェクトのルートまたは最寄りの package.json を確認し、"type" フィールドで解決方式を決定します。

package.json

{
  "type": "module"
}
  • "type": "module" → ESM として扱う
  • "type": "commonjs" または未指定 → CJS として扱う

相対インポートでの拡張子必須

ESM モードでは、相対インポートに拡張子が必須になります。

// ❌ エラー: 拡張子がない
import { add } from "./utils/math";

// ✅ OK: 拡張子を明示
import { add } from "./utils/math.js";

重要: TypeScript のコードでも .js 拡張子を書きます(.ts ではありません)。これは、コンパイル後の JavaScript ファイルを指すためです。

ディレクトリインデックスの非サポート

ESM モードでは、ディレクトリインポートが使えません。

// ❌ エラー
import { version } from "./utils";

// ✅ OK: index.js を明示
import { version } from "./utils/index.js";

package.json の exports フィールド対応

node_modules 内のパッケージは exports フィールドを尊重します。

{
  "name": "my-package",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "types": "./dist/utils.d.ts"
    }
  }
}

実際のコード例

package.json

{
  "type": "module",
  "dependencies": {
    "express": "^4.18.0"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "outDir": "./dist"
  }
}

src/main.ts

// 拡張子を明示(.js を記述)
import { add, multiply } from "./utils/math.js";
import { version } from "./utils/index.js";
import { log } from "./helpers/logger.mjs";

log(`Version: ${version}`);
console.log(add(2, 3));
console.log(multiply(4, 5));

コンパイル結果

dist/main.js

import { add, multiply } from "./utils/math.js";
import { version } from "./utils/index.js";
import { log } from "./helpers/logger.mjs";
log(`Version: ${version}`);
console.log(add(2, 3));
console.log(multiply(4, 5));

拡張子がそのまま保持され、Node.js の ESM として正しく動作します。

3. moduleResolution: "bundler"

Vite、webpack、esbuild などのバンドラーを使用することを前提とした解決方式です。

設定

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

動作の特徴

拡張子の省略が可能

nodenext と異なり、ESM モードでも拡張子を省略できます。

// ✅ OK: 拡張子なしでも動作
import { add } from "./utils/math";
import { version } from "./utils";

これは、バンドラーが自動的に拡張子を解決してくれるためです。

ディレクトリインデックスのサポート

// ✅ OK: ディレクトリインポートが可能
import { version } from "./utils";

./utils/index.ts が自動的に解決されます。

package.json の exports 対応

nodenext と同様に、package.jsonexports フィールドを尊重します。

TypeScript 固有の拡張子

.ts.tsx のインポートも許可されます(バンドラーが処理するため)。

// ✅ OK: バンドラー使用時のみ
import { add } from "./utils/math.ts";

いつ使うべきか

  • Vite、webpack、Rollup などのバンドラーを使用する場合
  • フロントエンド開発(React、Vue など)
  • バンドラーが拡張子やパスの解決を行う環境

実際のコード例

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM"]
  }
}

src/main.ts

// 拡張子省略OK
import { add, multiply } from "./utils/math";
import { version } from "./utils";

console.log(`Version: ${version}`);
console.log(add(2, 3));
console.log(multiply(4, 5));

// DOM操作もOK
document.getElementById("app")!.textContent = `Result: ${add(10, 20)}`;

この設定は、Vite などのバンドラーが適切にファイルを解決してくれることを前提としています。

各設定値の比較

特徴 node10 node16/nodenext bundler
拡張子省略 ✅ 可能 ❌ 不可(ESMモード) ✅ 可能
ディレクトリインデックス ✅ 可能 ❌ 不可(ESMモード) ✅ 可能
package.json "type" 認識 ❌ なし ✅ あり ✅ あり
package.json "exports" ❌ 非対応 ✅ 対応 ✅ 対応
.mjs/.cjs の区別 △ 限定的 ✅ 完全対応 ✅ 完全対応
用途 従来のNode.js 現代的なNode.js バンドラー使用時

実践的な使い分け

Node.js バックエンド開発

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}

現代的な Node.js プロジェクトでは nodenext が推奨されます。

フロントエンド開発(Vite/webpack使用)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM"]
  }
}

バンドラーを使用する場合は bundler が便利です。

レガシープロジェクト

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "node10"
  }
}

既存の CommonJS プロジェクトでは node10 を維持することも選択肢です。

よくあるエラーと対処法

エラー1: "Cannot find module './math' or its corresponding type declarations"

原因: nodenext では拡張子が必須です。

// ❌ エラー
import { add } from "./math";

// ✅ 修正
import { add } from "./math.js";

エラー2: "Directory import './utils' is not supported"

原因: nodenext ではディレクトリインポートが使えません。

// ❌ エラー
import { version } from "./utils";

// ✅ 修正
import { version } from "./utils/index.js";

エラー3: "The current file is a CommonJS module"

原因: package.json"type" 設定とファイル拡張子の不一致。

対処法:

  • ESM を使いたい場合: package.json"type": "module" を追加
  • CJS を使いたい場合: module: "commonjs"moduleResolution: "node10" を使用

まとめ

moduleResolution は TypeScript のモジュール解決方法を決定する重要な設定です。

  • node10(旧: node): 従来の Node.js スタイル。拡張子省略やディレクトリインデックスが可能ですが、現代的な ESM の機能には非対応
  • node16/nodenext: Node.js の ESM/CJS を正確にサポート。拡張子の明示が必要ですが、package.jsonexports などに対応し、現代的な Node.js 開発に最適です
  • bundler: バンドラー使用を前提とした設定。拡張子省略が可能で、フロントエンド開発に便利

プロジェクトの実行環境(Node.js 直接実行 or バンドラー経由)に合わせて、適切な moduleResolution を選択することで、TypeScript のモジュール解決をスムーズに行えます。


参考資料:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?