TypeScriptのモジュールシステム
モジュールはコードを整理するための基本単位です。TypeScript は ES モジュールと CommonJS モジュールをサポートしており、さらにそれらに加えて型をエクスポートする機能も備えています。
1. ESモジュールの基礎
(1) エクスポートの命名
TYPESCRIPT
// utils.ts —— Name Export
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export const PI = 3.14159;
TYPESCRIPT
// main.ts —— Import by Name
import { add, multiply, PI } from "./utils";
console.log(add(1, 2)); // 3
console.log(multiply(3, 4)); // 12
console.log(PI); // 3.14159
(2) デフォルトのエクスポート
TYPESCRIPT
// logger.ts —— Default Export
export default class Logger {
constructor(private prefix: string) {}
log(message: string): void {
console.log(`[${this.prefix}] ${message}`);
}
}
TYPESCRIPT
// main.ts —— Default Import(No curly braces needed,Custom Name)
import Logger from "./logger";
// That's fine, too.:import MyLogger from "./logger";
let logger = new Logger("APP");
logger.log("App Launch"); // [APP] App Launch
(3) デフォルトのエクスポートと名前付きエクスポートの両方の使用
TYPESCRIPT
// api.ts
export default class ApiClient {
constructor(private baseUrl: string) {}
async get(path: string): Promise<any> {
// ...
}
}
export enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
TYPESCRIPT
// main.ts
import ApiClient, { HttpMethod } from "./api";
let client = new ApiClient("https://api.example.com");
let method: HttpMethod = HttpMethod.GET;
▶ 例:モジュラー式電卓
TYPESCRIPT
// calculator/operations.ts
export function add(a: number, b: number): number { return a + b; }
export function subtract(a: number, b: number): number { return a - b; }
export function multiply(a: number, b: number): number { return a * b; }
export function divide(a: number, b: number): number {
if (b === 0) throw new Error("The divisor cannot be zero.");
return a / b;
}
TYPESCRIPT
// calculator/index.ts
export { add, subtract, multiply, divide } from "./operations";
export type { Operation } from "./types";
// Default Export——Calculator Category
import * as ops from "./operations";
export default class Calculator {
compute(op: string, a: number, b: number): number {
switch (op) {
case "+": return ops.add(a, b);
case "-": return ops.subtract(a, b);
case "*": return ops.multiply(a, b);
case "/": return ops.divide(a, b);
default: throw new Error(`Unknown Operation:${op}`);
}
}
}
2. 型のエクスポート
TypeScript では、型を個別にエクスポートすることができます。これは、JavaScript のモジュールシステムには備わっていない機能です:
(1) type 修飾子
TYPESCRIPT
// types.ts
export interface User {
id: number;
name: string;
email: string;
}
export type UserId = number;
export type UserRole = "admin" | "editor" | "viewer";
// Use export type to explicitly mark"Export Types Only"
export type { User as UserType };
(2) インポート時の「型」と「値」の区別
TYPESCRIPT
// main.ts
import { type User, type UserRole, createUser } from "./types";
// ↑ type Modifier Notation"Import Types Only"——It will be erased after compilation.
// Equivalent Old Syntax
// import { User, UserRole } from "./types"; // May result in runtime imports
// Recommended New Syntax——Clearly Distinguish Between Type Import and Value Import
import type { User, UserRole } from "./types";
import { createUser } from "./types";
💡 なぜこの区別をするのか?
import type インポートされた型はコンパイル後に完全に消去されるため、実行時に require や import が呼び出されることはない。これは、型のみを使用するシナリオ(型アノテーションやインターフェースなど)において極めて重要であり、不要なモジュールの読み込みを防ぐことができる。
(3) インライン型のインポート
TYPESCRIPT
// Mixed Import——Values and Types
import { createUser, type User, type UserRole } from "./types";
// createUser is the value——Requirements for runtime
// User and UserRole is a type——Compile-Time Erasure
3. 再エクスポートとバケットファイル
(1) 再輸出
TYPESCRIPT
// Re-exporting members of one module from another module
export { User, UserId } from "./user-types";
export { Product, ProductId } from "./product-types";
export { Order, OrderId } from "./order-types";
(2) バレルファイル
index.ts ディレクトリのエントリポイントとして、すべての公開APIを再エクスポートします:
TYPESCRIPT
// models/index.ts —— Bucket files
export { User, UserId } from "./user";
export { Product, ProductId } from "./product";
export { Order, OrderId } from "./order";
export type { CreateUser, UpdateUser } from "./user";
export type { CreateProduct, UpdateProduct } from "./product";
TYPESCRIPT
// Import directly from the directory when using it
import { User, Product, type CreateUser } from "./models";
// without needing to know which specific file it is in
💡 メリット: インポートパスを簡素化し、内部のファイル構造を隠蔽し、公開APIを制御できる。デメリット:不要なモジュールがインポートされる可能性がある(ツリーシェイクによってコードが完全に最適化されない場合がある)。
4. モジュールの解析戦略
TypeScript は、import "./utils" を実際のファイルにどのように解決すべきかを知る必要があります。これは、モジュール解決戦略によって決定されます。
(1) 2つの構文解析戦略
| 戦略 | 目的 | 説明 |
|---|---|---|
classic |
旧バージョンとの互換性 | まず .ts を検索し、次に .d.ts を検索 |
node (おすすめ) |
最新のTSプロジェクト | Node.jsの解析ロジックをシミュレート |
(2) ノード解析戦略の検索順序
TEXT
import { X } from "./utils"
検索順序:
./utils.ts./utils.tsx./utils.d.ts./utils/package.json内のtypesフィールド./utils/index.ts./utils/index.d.ts
(3) node_modules での検索
TEXT
import _ from "lodash"
検索順序:
./node_modules/lodash.ts(存在しません)./node_modules/lodash/package.json→types/typingsフィールド./node_modules/lodash/index.d.ts./node_modules/@types/lodash/index.d.ts../node_modules/→../../node_modules/… を検索する
5. パスマッピング(Path Mapping)
大規模なプロジェクトでは、長い相対パスを避けるためにパスエイリアスを使用します:
(1) tsconfig.json の設定
JSON
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@utils/*": ["src/utils/*"],
"@models/*": ["src/models/*"],
"@components/*": ["src/components/*"]
}
}
}
(2) パスエイリアスの使用
TYPESCRIPT
// No aliases——Relative paths are prone to errors
import { User } from "../../../models/user";
import { formatDate } from "../../utils/date";
// Has an alias——Clear and concise
import { User } from "@models/user";
import { formatDate } from "@utils/date";
⚠️ 注意: パスエイリアスはコンパイル時のマッピングに過ぎず、実行時(Node.js/ブラウザ)では
@models のようなパスは認識されません。実行時のパス置換を行うには、ビルドツール(Webpackのresolve.alias、Viteのresolve.alias、またはtsc-aliasなど)を使用する必要があります。
6. CommonJS との相互運用性
(1) CommonJS モジュール
TYPESCRIPT
// Use CommonJS style export
// math.cjs
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
module.exports = { add, multiply };
(2) TSでのCommonJSのインポート
TYPESCRIPT
// esModuleInterop: false(Default)
import * as math from "./math.cjs";
math.add(1, 2);
// esModuleInterop: true(Recommendations)
import math from "./math.cjs"; // ✅ A More Natural Way to Introduce It
math.add(1, 2);
(3) TSからのCommonJSエクスポートを許可する
TYPESCRIPT
// Use export = syntax to export CommonJS Style
class Calculator {
add(a: number, b: number): number { return a + b; }
}
export = Calculator;
// Use the following when importing: import = require
import Calculator = require("./calculator");
let calc = new Calculator();
💡 推奨事項: すべての新規プロジェクトでは ES モジュール (
import/export) を使用し、レガシーな CommonJS パッケージとの互換性を維持するために esModuleInterop: true を有効にしてください。export = および import = は、CommonJS との厳密な互換性が求められる場合にのみ使用してください。
❓ よくある質問
Q
import typeと通常のimportの違いは何ですか?A
import type この方法でインポートされた型は、コンパイル後に完全に消去され、実行時のモジュール読み込みは発生しません。通常の import ステートメントは値と型の両方をインポートし、コンパイル後に require() 呼び出しが発生します。型(インターフェースや型エイリアスなど)のみを使用する場合は、不要な実行時のインポートを避けるために、必ず import type を使用してください。Q バケットファイル(index.ts)を使用すべきですか?
A ライブラリや公開APIでは、インポートを簡素化し、エクスポートされるインターフェースを管理するために推奨されます。内部向けアプリケーションの場合、状況によります。小規模なプロジェクトでは必ずしも必要ではありませんが、大規模なプロジェクトでは構成上の利点があります。主な欠点は、ツリーシェーキングに影響を与える可能性があることですが、最新のバンドラーではこの問題に対処できます。
Q 実行時にパスエイリアスはどのように機能するのですか?
A TypeScriptによって生成されたJSコードでは、依然としてエイリアスパス(
@models/userなど)が使用されていますが、これらは実行時には認識されません。パスの置換はビルドツールによって処理される必要があります。Webpackではresolve.aliasが、Viteではresolve.aliasが使用され、純粋なtscコンパイルの場合はtsc-aliasのようなツールによる後処理が必要となります。Q ESモジュールとCommonJS、どちらを使うべきですか?
A 新規プロジェクトでは、ESモジュールのみを使用してください (
import/export)。CommonJSはNode.jsのレガシーなモジュールシステムであり、段階的に廃止されつつあります。TypeScriptのesModuleInteropオプションを使用すれば、CommonJSパッケージをシームレスに利用できます。ブラウザやDenoではESモジュールのみがサポートされています。📖 まとめ
- ESモジュールでは、
export/importが組織コードとして使用されます。中括弧は名前付きエクスポートに使用されますが、デフォルトのエクスポートには使用されません。 import typeインポート型のみ—コンパイル後に消去。実行時のモジュール読み込みは行われないindex.tsバケットファイルは、インポートパスを簡略化するために、そのディレクトリ内のパブリック API を再エクスポートしています- モジュール解析戦略
node(推奨) Node.js の検索ロジックをシミュレートします - パスマッピング (
baseUrl+paths) は、長い相対パスをエイリアスに置き換えます。これを利用するには、これをサポートするビルドツールが必要です。 esModuleInterop: trueESモジュールとCommonJS間のシームレスな相互運用性を実現する
📝 練習問題
- 基本演習(難易度 ⭐):3つのモジュールファイル、
math.ts(加算・減算をエクスポート)、string-utils.ts(大文字化・逆転をエクスポート)、index.ts(バケットファイルを再エクスポート)を作成してください。これらをmain.tsにインポートして使用してください。 - 上級演習(難易度 ⭐⭐):既存のプロジェクトのパスマッピングを設定します。
@utilsをsrc/utilsに、@modelsをsrc/modelsにマッピングしてください。import typeを使用して型をインポートし、importを使用して値をインポートしてください。 - 課題(難易度:⭐⭐⭐):CommonJS パッケージ
legacy-sdkを、ES Module 形式で TypeScript にインポートできるようにする宣言ファイルimport LegacySDK from "legacy-sdk"を作成してください。esModuleInteropが有効な場合と無効な場合の両方を考慮してください。



