メインコンテンツまでスキップ

モジュールとtsconfig

この章では、TypeScriptのモジュールシステムとプロジェクト設定について学びます。

この章で学ぶこと

  • モジュールシステム(import/export)の仕組み
  • 様々なエクスポート/インポートパターン
  • 再エクスポートとバレルファイル
  • tsconfig.jsonの基本と設定方法
  • 重要なcompilerOptions
備考

モジュールシステムを理解することで、コードを機能ごとに分割し、再利用可能で保守しやすいプロジェクト構成が可能になります。

モジュールとは

モジュールは、コードを機能ごとに分割し、再利用可能な単位にまとめたものです。

モジュールの利点:

  • コードの整理と構造化
  • 名前空間の分離(変数の衝突を防ぐ)
  • 再利用性の向上
  • 保守性の向上

TypeScriptでのモジュール:

  • exportキーワードで外部に公開
  • importキーワードで他のモジュールを読み込み
  • ファイルにimportまたはexportがあればモジュールとして扱われます

エクスポート(export)

// user.ts - 名前付きエクスポート

// interfaceをエクスポート: 外部からUser型を使えるようにする
export interface User {
id: number;
name: string;
email: string;
}

// classをエクスポート: 外部からUserServiceクラスを使えるようにする
export class UserService {
// private: このクラス内部からのみアクセス可能
private users: User[] = [];

// ユーザーを追加するメソッド
addUser(user: User): void {
this.users.push(user);
}

// IDでユーザーを検索するメソッド
getUserById(id: number): User | undefined {
// find: 条件に一致する最初の要素を返す
return this.users.find(u => u.id === id);
}
}

// 関数をエクスポート
export function formatUserName(user: User): string {
return `${user.name} (ID: ${user.id})`;
}

// 定数のエクスポート
export const MAX_USERS = 100;

コード解説:

  • exportキーワードで外部に公開
  • interface、class、function、定数などをエクスポート可能
  • 複数の要素を同じファイルからエクスポートできます

インポート(import)

// main.ts - 名前付きインポート

// 特定の要素をインポート
// { } で囲んでインポートする要素を指定
import { User, UserService } from "./user";

// UserServiceクラスのインスタンスを作成
const service = new UserService();

// User型を使ってオブジェクトを作成
const user: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com"
};

// メソッドを呼び出してユーザーを追加
service.addUser(user);

コード解説:

  • {}で囲んでインポートする要素を指定
  • fromの後にモジュールのパスを指定(拡張子は省略可能)
  • 相対パス(./, ../)または絶対パスを使用

様々なエクスポートパターン

// ===== エクスポートパターン =====

// パターン1: 個別にエクスポート(宣言と同時にexport)
export class Product {}
export interface ProductData {}

// パターン2: 最後にまとめてエクスポート
class Order {}
interface OrderData {}
const TAX_RATE = 0.1;

// 複数の要素を一度にエクスポート
export { Order, OrderData, TAX_RATE };

// パターン3: エイリアス(別名)を使ってエクスポート
class InternalUser {}
// InternalUserをUserという名前でエクスポート
export { InternalUser as User };

// パターン4: デフォルトエクスポート(1ファイルに1つのみ)
// このモジュールの「主役」となる要素に使用
export default class Config {
apiUrl: string = "https://api.example.com";
}

様々なインポートパターン

// ===== インポートパターン =====

// パターン1: 特定の要素をインポート
import { Product, ProductData } from "./product";

// パターン2: エイリアス(別名)を使ってインポート
// 名前の衝突を避けたり、分かりやすい名前に変更
import { Order as PurchaseOrder } from "./order";

// パターン3: すべてをインポート(名前空間として)
// * as で全てのエクスポートをオブジェクトとして取得
import * as ProductModule from "./product";
const product = new ProductModule.Product();

// パターン4: デフォルトエクスポートのインポート
// {}不要、任意の名前を付けられる
import Config from "./config";

// パターン5: デフォルトと名前付きを同時にインポート
import Config, { Helper, Utility } from "./config";

// パターン6: 型のみをインポート(TypeScript 3.8以降)
// 型情報のみ、コンパイル後のJavaScriptには含まれない
import type { User } from "./user";

コード解説:

  • 名前付きエクスポートは{}が必要
  • デフォルトエクスポートは{}不要で、任意の名前を付けられます
  • typeキーワードで型のみインポート(コンパイル後のコードに含まれません)

推奨事項:

  • 名前付きエクスポートを優先: IDEの補完が効きやすく、リファクタリングも容易
  • デフォルトエクスポートは、そのファイルの主要な機能が1つの場合のみ使用

再エクスポート(バレルエクスポート)

複数のモジュールを1つのエントリーポイントから公開する方法です。

// models/index.ts - バレルファイル

// 複数のモジュールを1つのファイルから公開
// 特定の要素を選んで再エクスポート
export { User, UserService } from "./user";
export { Product, ProductService } from "./product";
export { Order, OrderService } from "./order";

// または一括で再エクスポート
// 対象モジュールの全てのエクスポートを再エクスポート
export * from "./user";
export * from "./product";
export * from "./order";
// main.ts - バレルファイルから一括インポート

import { User, Product, Order } from "./models";
// ./models/user, ./models/product, ./models/orderと個別にインポートする必要がない

バレルファイルの利点:

  • インポート文の簡略化
  • モジュール構成の変更が容易
  • 公開するAPIを制御しやすい

モジュールとスクリプトの違い

// module.ts - モジュール(import/exportがある)
export function greet() {
console.log("Hello from module!");
}

// このファイルのトップレベル変数は外部から見えない(モジュールスコープ)
const secret = "module secret";
// script.ts - スクリプト(import/exportがない)
function greet() {
console.log("Hello from script!");
}

// このファイルのトップレベル変数はグローバルスコープ
// 他のファイルから見えてしまう可能性がある
const globalVar = "accessible everywhere";

違いのまとめ:

特徴モジュールスクリプト
import/exportありなし
スコープ独自のスコープグローバルスコープ
推奨度推奨非推奨

モジュールの方がより安全で保守性が高いです。

tsconfig.json - コンパイラ設定

tsconfig.jsonの作成

# tsconfig.jsonを自動生成(コメント付きのデフォルト設定が生成される)
tsc --init

基本的な構造

{
"compilerOptions": {
// コンパイラオプション(TypeScriptの振る舞いを設定)
},
"include": [
"src/**/*" // コンパイル対象のファイル(srcフォルダ以下全て)
],
"exclude": [
"node_modules", // 除外するフォルダ
"**/*.spec.ts" // 除外するファイルパターン(テストファイル)
]
}

重要なcompilerOptions

基本オプション

{
"compilerOptions": {
// ターゲットJavaScriptバージョン
// コンパイル後のJavaScriptがどのバージョンになるか
"target": "ES2020",

// モジュールシステム
// ESNext: ES Modules(import/export)
// commonjs: Node.js用(require/module.exports)
"module": "ESNext",

// ライブラリファイル(使用する型定義)
// ES2020: ES2020の標準ライブラリ
// DOM: ブラウザAPIの型定義
"lib": ["ES2020", "DOM"],

// 出力ディレクトリ(コンパイル後のファイルの保存先)
"outDir": "./dist",

// ルートディレクトリ(ソースファイルの場所)
"rootDir": "./src",

// ソースマップの生成(デバッグ用)
// .mapファイルが生成され、デバッガでTypeScriptコードを表示できる
"sourceMap": true,

// コメントの削除(出力ファイルからコメントを除去)
"removeComments": true,

// エラー時に出力しない(型エラーがあればJSファイルを生成しない)
"noEmitOnError": true
}
}

各オプションの説明:

オプション説明推奨値
targetコンパイル後のJSバージョンES2020以上
moduleモジュールシステムの形式ESNext
outDir出力先ディレクトリ./dist
rootDirソースのルート./src
sourceMapデバッグ用マップ生成true

型チェックオプション(strict関連)

{
"compilerOptions": {
// すべてのstrictオプションを有効化(推奨)
// これを有効にすると、以下のオプションがすべて有効になる
"strict": true,

// 以下は"strict": trueで自動的に有効になるオプション:
// "noImplicitAny": true, // 暗黙のany型を禁止
// "strictNullChecks": true, // nullチェックを厳格化
// "strictFunctionTypes": true, // 関数型の厳格なチェック
// "strictBindCallApply": true, // bind/call/applyの厳格なチェック
// "strictPropertyInitialization": true, // クラスプロパティの初期化チェック
// "noImplicitThis": true, // thisの暗黙の型を禁止
// "alwaysStrict": true, // "use strict"を出力

// 追加のチェック(strictに含まれないため個別に設定)
"noUnusedLocals": true, // 未使用のローカル変数を警告
"noUnusedParameters": true, // 未使用のパラメータを警告
"noImplicitReturns": true, // 暗黙的なreturnを禁止
"noFallthroughCasesInSwitch": true // switch文のfall-throughを禁止
}
}
警告

"strict": trueを有効にすることを推奨します。型安全性が向上し、バグの早期発見につながります。

モジュール解決オプション

{
"compilerOptions": {
// モジュール解決方法
// "bundler": Vite / esbuild / Webpack などのバンドラー使用時(推奨)
// "nodenext": Node.js で直接実行する場合
// "node": レガシー互換用(新規プロジェクトでは非推奨)
"moduleResolution": "bundler",

// ベースURL(絶対パスインポート用)
"baseUrl": "./",

// パスマッピング(パスエイリアス)
// 長いパスを短いエイリアスで置き換え
"paths": {
"@/*": ["src/*"], // @/xxx → src/xxx
"@components/*": ["src/components/*"], // @components/xxx → src/components/xxx
"@utils/*": ["src/utils/*"] // @utils/xxx → src/utils/xxx
},

// ES ModulesとCommonJSの相互運用を改善
"esModuleInterop": true,

// デフォルトインポートを許可
"allowSyntheticDefaultImports": true
}
}
備考

moduleResolution の選び方(2026年)

用途典型的な環境
"bundler"Vite / esbuild / Webpack 等のバンドラーを使う SPA・ライブラリReact / Vue / Svelte プロジェクト
"nodenext"Node.js で直接実行する CLI・サーバーExpress / NestJS / CLI ツール
"node16"Node.js 16+ 互換の動作(nodenext と似るが固定)互換性が必要なライブラリ
"node"レガシー互換用。新規では選ばない古いプロジェクトからの移行時のみ

"bundler" は最も制約が緩く、.ts / .tsx 拡張子を含む import を許容します(allowImportingTsExtensions: true と組み合わせ)。Node.js 直接実行では拡張子と "type": "module" の指定が厳格になるため "nodenext" を選びます。

// pathsを使用したインポート例

// 通常のパス(長くなりがち)
import { Button } from "../../../components/Button";

// パスエイリアスを使用(短く分かりやすい)
import { Button } from "@components/Button"; // src/components/Button
import { formatDate } from "@utils/date"; // src/utils/date

実践的なtsconfig.json例

{
"compilerOptions": {
// 基本設定
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],

// 出力設定
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"removeComments": true,
"noEmitOnError": true,

// 型チェック(厳格モード)
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,

// モジュール解決
"moduleResolution": "bundler",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,

// モダンな TypeScript 設定(TS 5.x)
"verbatimModuleSyntax": true, // import/export の解釈をそのまま保持(TS 5.0+)
"erasableSyntaxOnly": true, // 型以外の TS 固有構文を禁止(TS 5.8+)
"isolatedModules": true, // 1ファイル単位でコンパイル可能にする
"allowImportingTsExtensions": true, // .ts / .tsx 拡張子付きの import を許可

// その他
"skipLibCheck": true, // ライブラリの型チェックをスキップ(高速化)
"forceConsistentCasingInFileNames": true // ファイル名の大文字小文字を厳格にチェック
},

// コンパイル対象
"include": [
"src/**/*"
],

// 除外ファイル
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts",
"**/*.test.ts"
]
}

モダンな tsconfig 設定(TS 5.x)

2026年時点で新規プロジェクトに推奨されるコンパイラオプションを解説します。

verbatimModuleSyntax(TS 5.0+)

import/export を書いた通りに出力するオプションです。型のみを import する場合は import type { ... } を明示的に書く必要があり、これにより「型だけをインポートしているか、ランタイムで値も必要か」がコードから一目で分かります。

// verbatimModuleSyntax: true では、型のみのインポートは明示する必要がある

// ❌ エラー: User は型として使われているだけ
import { User, fetchUser } from './user'

// ✅ OK: 型インポートを明示
import type { User } from './user'
import { fetchUser } from './user'

// または inline type import
import { type User, fetchUser } from './user'

erasableSyntaxOnly(TS 5.8+)

型注釈だけを消せば JavaScript として動く、すなわち消しゴムで消せる型のみを許容するオプションです。enum / namespace / constructor parameter properties など、実行時コードを生成する TypeScript 固有の構文を禁止します。

// erasableSyntaxOnly: true では以下がエラーになる

// ❌ enum はランタイムコード(オブジェクト)を生成する
enum Color { Red, Green, Blue }

// ❌ constructor parameter properties は field 代入を自動生成
class User {
constructor(public name: string) {}
}

// ✅ これらは明示的に書く
const Color = { Red: 0, Green: 1, Blue: 2 } as const
type Color = typeof Color[keyof typeof Color]

class User {
name: string
constructor(name: string) {
this.name = name
}
}
備考

erasableSyntaxOnly は、Node.js が .ts ファイルを直接実行するときの type stripping (型注釈だけを取り除く方式。Node.js 22.18 以降 / 24 ではフラグ不要でデフォルト有効) の制約と揃えるためのオプションです。enum などの非 erasable 構文は Node.js 直接実行では ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX エラーになります。なお Bun / Deno は type stripping ではなくフルトランスパイルで TypeScript を実行するため、enum も動作します。それでも erasableSyntaxOnly に合わせておけば、Node.js / Bun / Deno のどのランタイムでも同じコードがそのまま動作します。

import attributes(TS 5.3+)

JSON や他のモジュールタイプを import する際に、属性を付けて明示するための構文です。以前の assert { type: "json" } は廃止され、with { type: "json" } が標準になりました。

// TS 5.3+ の正式構文
import data from './config.json' with { type: 'json' }

watchモード

# ファイル変更を監視してリアルタイムコンパイル
tsc --watch

# または短縮形
tsc -w

watchモードの動作:

  1. ファイルの変更を自動検知
  2. 変更されたファイルを即座に再コンパイル
  3. エラーがあればコンソールに表示

開発時にはwatchモードを起動しておくと、コードを書きながらリアルタイムで型チェックの結果を確認できます。

試してみよう: tsconfig.jsonを最適化しよう ★★

以下の要件を満たすtsconfig.jsonを作成してください。

要件:

  1. ES2020をターゲットにする
  2. strictモードを有効にする
  3. @/というパスエイリアスでsrc/を参照できるようにする
  4. 出力先をdist/に設定する
  5. ソースマップを有効にする
  6. 未使用の変数・パラメータを警告する
ヒント
  1. target"ES2020"に設定
  2. stricttrueに設定
  3. baseUrlpathsを設定してパスエイリアスを有効化
  4. outDir"./dist"に設定
  5. sourceMaptrueに設定
  6. noUnusedLocalsnoUnusedParameterstrueに設定
回答と解説
{
"compilerOptions": {
// ターゲットをES2020に設定
"target": "ES2020",

// モジュールシステム(ES Modules)
"module": "ESNext",
"moduleResolution": "bundler",

// 出力先ディレクトリ
"outDir": "./dist",
"rootDir": "./src",

// ソースマップを有効化(デバッグ用)
"sourceMap": true,

// strictモードを有効化(型安全性を最大化)
"strict": true,

// 未使用コードの警告
"noUnusedLocals": true,
"noUnusedParameters": true,

// パスエイリアスの設定
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},

// その他の推奨設定
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

解説:

  • strict: trueで全ての厳格な型チェックが有効になります
  • パスエイリアス@/*により、import { xxx } from "@/components/xxx"のような記述が可能になります
  • sourceMap: trueにより、ブラウザやVSCodeのデバッガでTypeScriptコードを直接デバッグできます
  • noUnusedLocalsnoUnusedParametersにより、使われていない変数やパラメータがあると警告が表示されます

まとめ

この章で学んだこと:

  • モジュールシステム: import/exportでコードを分割・再利用
  • 様々なエクスポートパターン: 名前付き、デフォルト、再エクスポート
  • 様々なインポートパターン: 名前付き、エイリアス、名前空間、型のみ
  • バレルファイル: 複数モジュールを1つのエントリーポイントから公開
  • tsconfig.json: TypeScriptプロジェクトの設定ファイル
  • 重要なcompilerOptions: target、strict、paths、sourceMapなど

次の章では、ESLint、Prettier、Jestなどの開発ツールチェーンについて学びます。

初学者がつまずきやすいポイント

警告

よくある間違い

❌ 相対パスと絶対パスを混同する

// ❌ パスエイリアス設定なしで@を使う
import { User } from "@/types/user"; // Error: モジュールが見つからない

// ✅ 相対パスを使うか、pathsを設定
import { User } from "./types/user"; // 相対パス

// または tsconfig.json で設定
// "paths": { "@/*": ["src/*"] }

原因: @/などのパスエイリアスはtsconfig.jsonpaths設定が必要です。 解決策: 相対パスを使うか、baseUrlpathsを設定しましょう。

❌ デフォルトエクスポートと名前付きエクスポートを混同する

// user.ts
export default class User {}
export const MAX_USERS = 100;

// ❌ デフォルトエクスポートを{}でインポート
import { User } from "./user"; // Error: Userは名前付きエクスポートではない

// ✅ デフォルトは{}不要
import User from "./user";
// または名前付きと一緒に
import User, { MAX_USERS } from "./user";

原因: デフォルトエクスポートは{}なしでインポートします。 解決策: デフォルトはimport Xxx、名前付きはimport { Xxx }で使い分けましょう。

❌ moduleとmoduleResolutionを混同する

{
"compilerOptions": {
// module: 出力するモジュール形式
"module": "ESNext",

// moduleResolution: モジュールの解決方法(検索アルゴリズム)
"moduleResolution": "bundler"
}
}

原因: moduleは出力形式、moduleResolutionはモジュール検索方法を指定します。 解決策: 現代的なプロジェクトでは"moduleResolution": "bundler"(バンドラー使用時)または"node16"/"nodenext"(Node.js直接実行時)を推奨します。

❌ strict: trueの効果を理解していない

// strictがfalseの場合、以下が許容される
let name; // 暗黙のany
const user = { name: "太郎" };
user.age; // 存在しないプロパティへのアクセス

// strict: trueにすると、これらがエラーになる

原因: strict: trueは複数の厳格なチェックをまとめて有効にします。 解決策: 新規プロジェクトでは必ずstrict: trueを設定し、型安全性を最大化しましょう。

❌ バレルファイルで循環参照を作る

// models/index.ts(バレルファイル)
export * from "./user";
export * from "./order";

// models/user.ts
import { Order } from "./index"; // バレル経由でインポート
// これがorder.tsからuserをインポートしていると循環参照に!

原因: バレルファイル経由でのインポートが循環参照を引き起こすことがあります。 解決策: 同一モジュール内は直接インポートし、バレルは外部向けAPIとして使いましょう。


次に読む