TypeScript 5の新機能 - より高速で使いやすく進化

2025.12.18

公式ドキュメント

TypeScript 5の概要

TypeScript 5は、大幅なパフォーマンス改善と多数の新機能を備えたメジャーリリースです。ビルド速度の向上、新しいECMAScriptデコレーター構文、型システムの強化、そして内部アーキテクチャの刷新による軽量化が実現されています。5.0以降、5.1、5.2、5.3、5.4、5.5と継続的にマイナーリリースが重ねられ、それぞれで推論能力、エディタ体験、モジュール解決が着実に進化しています。

背景

TypeScriptは2012年にMicrosoftからリリースされて以来、JavaScriptエコシステムのほぼ標準となりました。Node.js、フロントエンドフレームワーク、エッジランタイムのいずれでもTypeScriptが第一級でサポートされており、型による安全性と補完支援はもはや不可欠です。TypeScript 5ではこれまでの累積的な改善を一括して再構成し、Namespace importsを使った内部構造への刷新、モジュール解決戦略の再設計、そして言語仕様のモダン化が実施されました。

flowchart LR
 TS4["TypeScript 4.x"] --> TS5["TypeScript 5.x"]
 TS5 --> A["新デコレーター (Stage 3)"]
 TS5 --> B["const 型パラメータ"]
 TS5 --> C["moduleResolution: bundler"]
 TS5 --> D["パフォーマンス大幅改善"]
 TS5 --> E["全enum がユニオン化"]

デコレーターの正式サポート

ECMAScriptのデコレーター提案(Stage 3)に準拠した新しいデコレーター構文が利用可能になりました。従来のexperimentalDecoratorsフラグに依存した旧式デコレーターとは別の、標準仕様に沿った安定した実装です。

function logged<This, Args extends any[], Return>(
 originalMethod: (this: This, ...args: Args) => Return,
 context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
 const methodName = String(context.name);

 function replacement(this: This, ...args: Args): Return {
 console.log(`Calling ${methodName} with`, args);
 const result = originalMethod.call(this, ...args);
 console.log(`${methodName} returned`, result);
 return result;
 }
 return replacement;
}

class Calculator {
 @logged
 add(a: number, b: number): number {
 return a + b;
 }
}

new Calculator().add(2, 3);

クラスデコレーターとアクセサデコレーター

function singleton<T extends new (...args: any[]) => any>(
 target: T,
 context: ClassDecoratorContext
) {
 let instance: InstanceType<T> | undefined;
 return class extends target {
 constructor(...args: any[]) {
 if (instance) return instance;
 super(...args);
 instance = this as InstanceType<T>;
 }
 };
}

@singleton
class Database {
 constructor(public url: string) {}
}

従来との違い: experimentalDecoratorsフラグは不要になり、デコレーターのシグネチャが標準化されました。旧式デコレーターと新デコレーターは同一ファイルで混在させられません。

ポイント: TypeScript 5のデコレーターはTC39提案Stage 3に準拠しており、experimentalDecoratorsフラグなしで使用できます。

const型パラメータ

リテラル型をより簡単に推論できるようになりました。関数呼び出しのたびにas constを書く必要がなくなります。

// 従来
function getConfig<T extends readonly string[]>(items: T): T {
 return items;
}
const config1 = getConfig(['a', 'b', 'c'] as const);

// TypeScript 5
function getConfig<const T extends readonly string[]>(items: T): T {
 return items;
}
const config2 = getConfig(['a', 'b', 'c']);
// 型: readonly ['a', 'b', 'c']

実践的な利用

function defineRoutes<const T extends Record<string, { path: string; method: 'GET' | 'POST' }>>(
 routes: T
): T {
 return routes;
}

const routes = defineRoutes({
 listUsers: { path: '/users', method: 'GET' },
 createUser: { path: '/users', method: 'POST' },
});

type RouteName = keyof typeof routes; // "listUsers" | "createUser"
type UserPath = typeof routes.listUsers.path; // "/users"

実践メモ: const型パラメータを使うとリテラル型が推論され、as constを書く必要がなくなります。

パフォーマンスの改善

TypeScript 5では内部構造の最適化により大幅な高速化を実現しています。Namespace importsの導入により、tsc自身がツリーシェイキングされ、コンパイラパッケージサイズが劇的に縮小されました。

項目改善率
ビルド速度10〜25%向上
パッケージサイズ約46%削減
メモリ使用量10〜20%削減
型チェック時間大規模プロジェクトで10〜20%短縮

その他の新機能

すべてのenumがユニオン型に

enum Color {
 Red,
 Green,
 Blue
}

function paint(c: Color) {
 if (c === Color.Red) { /* ... */ }
}

// Color型は Color.Red | Color.Green | Color.Blue として扱われる
// 計算されたメンバーも適切にユニオン化される

moduleResolution: bundler

Viteやesbuild、webpackなどのモダンなバンドラー向けのモジュール解決戦略が追加されました。Node.js互換のESMルールから開発体験に不要な制約(拡張子必須など)を取り除いた設定です。

{
 "compilerOptions": {
 "module": "ESNext",
 "moduleResolution": "bundler",
 "allowImportingTsExtensions": false,
 "resolveJsonModule": true,
 "target": "ES2022",
 "strict": true,
 "noUncheckedIndexedAccess": true
 }
}

—verbatimModuleSyntax

import typeと通常importを明確に分離し、出力されるJSに対する意図を一貫させるフラグ。

import type { User } from './types';
import { fetchUser } from './api';

using宣言(TS 5.2)

Stage 3のExplicit Resource Managementに対応。スコープ離脱時にリソースを自動解放できます。

{
 using file = openFile('./data.txt');
 // スコープを抜けるとfile[Symbol.dispose]()が自動呼び出し
}

実践的な型定義例

// 型安全なイベントエミッター
type EventMap = {
 login: { userId: string };
 logout: { userId: string; reason: string };
 error: Error;
};

class Emitter<T extends Record<string, any>> {
 private handlers: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};

 on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
 (this.handlers[event] ??= []).push(handler);
 }

 emit<K extends keyof T>(event: K, payload: T[K]): void {
 this.handlers[event]?.forEach((h) => h(payload));
 }
}

const emitter = new Emitter<EventMap>();
emitter.on('login', ({ userId }) => console.log(`login: ${userId}`));
emitter.emit('login', { userId: 'u1' });

satisfies演算子の活用

type Route = { path: string; auth: boolean };

const routes = {
 home: { path: '/', auth: false },
 dashboard: { path: '/dashboard', auth: true },
} satisfies Record<string, Route>;

// keyは絞り込まれたリテラルのまま保持される
type RouteKey = keyof typeof routes; // "home" | "dashboard"

旧バージョンとの比較

機能TS 4.9TS 5.0TS 5.2TS 5.4
新デコレーター-GAGAGA
const型パラメータ-GAGAGA
moduleResolution: bundler-GAGAGA
using宣言--GAGA
NoInfer型ユーティリティ---GA
satisfies演算子GAGAGAGA

他言語・旧バージョンとの比較

項目TypeScript 5TypeScript 4Flow
エコシステム最大大きい縮小傾向
推論性能高速普通
標準デコレーター対応未対応未対応
バンドラー統合簡単やや煩雑難しい
IDE補完品質優秀良好

注意: TypeScript 5では旧experimentalDecoratorsとの互換性がありません。既存のデコレーターライブラリが新構文に対応しているか確認してください

移行のポイント

  • target: ES2022以降を推奨
  • 新しいデコレーターへの段階的移行を検討(旧式との混在不可)
  • moduleResolution: bundlerの活用(Vite/Next.js/Remix等と相性良好)
  • verbatimModuleSyntaxで型インポートの意図を明示

ベストプラクティス

  1. strict系フラグを全部有効化: strict: trueに加えてnoUncheckedIndexedAccessexactOptionalPropertyTypesを推奨
  2. 不要なany排除: unknownを使い、型ガードで絞り込む
  3. satisfiesで設定オブジェクトを型安全に: 型の絞り込みを失わない
  4. 型定義ファイルの分離: src/types/に集約してモノレポ間で共有
  5. incremental / tsBuildInfoFile: 大規模プロジェクトで差分ビルドを有効化
{
 "compilerOptions": {
 "incremental": true,
 "tsBuildInfoFile": "./.tsbuildinfo",
 "composite": true
 }
}

よくある落とし穴とトラブルシューティング

1. 新旧デコレーターの混在

旧式experimentalDecoratorsと新デコレーターは同じファイルで使えません。プロジェクト全体での方針を決めて移行しましょう。

2. moduleResolution: bundlerで型定義が見つからない

パッケージのexportsフィールドを参照するため、古いライブラリでは解決できないことがあります。"moduleResolution": "node16"へのフォールバックか、ライブラリのアップデートを検討します。

3. enumが想定より緩いユニオンに推論される

数値enumでは依然として数値ワイドニングが発生します。リテラル型ユニオンまたはas constオブジェクトを検討してください。

const Status = {
 Idle: 'idle',
 Loading: 'loading',
 Done: 'done',
} as const;
type Status = typeof Status[keyof typeof Status];

4. tsc実行が遅い

  • skipLibCheck: trueを設定
  • includeを必要ファイルに限定
  • プロジェクトリファレンスで分割
  • typescript-go(Goポート、将来的に10倍高速化予定)を試すのも選択肢

導入手順

# 1. インストール
npm install -D typescript@5

# 2. tsconfig.jsonの初期化
npx tsc --init --target ES2022 --module ESNext \
 --moduleResolution bundler --strict

# 3. 型チェック
npx tsc --noEmit

# 4. Watch mode
npx tsc --noEmit --watch

CI統合例

# .github/workflows/typecheck.yml
name: typecheck
on: [push, pull_request]
jobs:
 typecheck:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - uses: actions/setup-node@v4
 with:
 node-version: '20'
 cache: 'npm'
 - run: npm ci
 - run: npx tsc --noEmit

パフォーマンスベンチマーク

大規模モノレポ(TypeScript内部のベンチマーク例)での概算値:

プロジェクトTS 4.9TS 5.0改善
VS Code100秒78秒-22%
Playwright42秒34秒-19%
TypeScript自体76秒66秒-13%

数値は各プロジェクトの公開ベンチマーク・リリースノート記載を参考にした目安値です。

FAQ

Q1. 旧デコレーターはすぐ使えなくなりますか? A. experimentalDecoratorsフラグ経由で引き続き利用可能です。ただし長期的には新デコレーターへの移行を推奨します。

Q2. moduleResolution: bundlerはNode.js実行時にも使えますか? A. このオプションはあくまで「バンドラーで解決される前提」での型チェック用です。Node.js直接実行の場合はnode16またはnodenextを使います。

Q3. satisfiesと型アノテーションの違いは? A. satisfiesは型チェックを行いつつ、リテラル型などの具体的な型情報を保持します。アノテーションは型を広げてしまうことがあります。

Q4. using宣言はどのバージョンから使えますか? A. TS 5.2で導入されました。ランタイム側でもSymbol.disposeのポリフィルが必要な場合があります。

Q5. 型チェックが遅いのですが、どうすればよいですか? A. skipLibCheck、Project References、incrementalを活用し、巨大ユニオンや条件型のネストを避けます。

ここまでの整理

TypeScript 5は、開発者体験の向上とパフォーマンス改善を両立したリリースです。新しいデコレーター構文、const型パラメータ、moduleResolution: bundlerusing宣言、satisfiesなどにより、より型安全で表現力豊かなコードが書けるようになりました。既存プロジェクトの段階的移行を進めつつ、新規プロジェクトでは積極的にこれらの新機能を活用していきましょう。

より深い型プログラミング例

TypeScript 5の型システムは非常に強力で、実行時の値を使わずに複雑な制約を表現できます。以下はよく使うパターンの実例です。

深いオブジェクトのPath型

type Path<T> = T extends object
 ? {
 [K in keyof T]: K extends string
 ? T[K] extends object
 ? `${K}` | `${K}.${Path<T[K]>}`
 : `${K}`
 : never;
 }[keyof T]
 : never;

type PathValue<T, P extends string> =
 P extends `${infer K}.${infer Rest}`
 ? K extends keyof T
 ? PathValue<T[K], Rest>
 : never
 : P extends keyof T
 ? T[P]
 : never;

type User = {
 id: string;
 profile: {
 name: string;
 address: { city: string; zip: string };
 };
};

type UserPaths = Path<User>;
// "id" | "profile" | "profile.name" | "profile.address"
// | "profile.address.city" | "profile.address.zip"

type CityType = PathValue<User, 'profile.address.city'>; // string

Builderパターン

type Builder<T, Required extends keyof T = keyof T> = {
 [K in keyof T]-?: (value: T[K]) => Builder<T, Exclude<Required, K>>;
} & (Required extends never ? { build: () => T } : {});

type Query = {
 from: string;
 select: string[];
 where?: string;
 limit?: number;
};

declare function createQueryBuilder(): Builder<Query, 'from' | 'select'>;

const q = createQueryBuilder()
 .from('users')
 .select(['id', 'name'])
 .where('active = true')
 .limit(10)
 .build();

NoInferを使った推論制御(TS 5.4)

function createFSM<State extends string>(config: {
 initial: NoInfer<State>;
 states: Record<State, { on: Partial<Record<string, NoInfer<State>>> }>;
}) {
 return config;
}

const fsm = createFSM({
 initial: 'idle',
 states: {
 idle: { on: { start: 'loading' } },
 loading: { on: { done: 'success', fail: 'error' } },
 success: { on: {} },
 error: { on: { retry: 'loading' } },
 },
});

ブランド型(Nominal Typing)

type Brand<T, B> = T & { readonly __brand: B };

type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

function asUserId(s: string): UserId { return s as UserId; }
function asOrderId(s: string): OrderId { return s as OrderId; }

function getUser(id: UserId) { /* ... */ }

const uid = asUserId('u_123');
const oid = asOrderId('o_456');
getUser(uid); // OK
// getUser(oid); // Error: Type 'OrderId' is not assignable to 'UserId'

プロジェクトリファレンス

大規模モノレポでは、Project Referencesを使うことで増分ビルドと依存関係の明示化ができます。

// tsconfig.json (ルート)
{
 "files": [],
 "references": [
 { "path": "./packages/shared" },
 { "path": "./packages/api" },
 { "path": "./packages/web" }
 ]
}
// packages/shared/tsconfig.json
{
 "compilerOptions": {
 "composite": true,
 "declaration": true,
 "outDir": "dist",
 "rootDir": "src",
 "target": "ES2022",
 "module": "ESNext",
 "moduleResolution": "bundler",
 "strict": true
 },
 "include": ["src/**/*"]
}
# 増分ビルド
tsc --build

# クリーンビルド
tsc --build --clean && tsc --build

型テスト

型自体もユニットテストで保護できます。tsdexpect-typeを使うと、意図しない型変化を検出できます。

import { expectTypeOf } from 'expect-type';

expectTypeOf<PathValue<User, 'profile.name'>>().toEqualTypeOf<string>();
expectTypeOf<Path<User>>().toMatchTypeOf<'id' | 'profile' | 'profile.name'>();

ESLintとの統合

// .eslintrc.json
{
 "parser": "@typescript-eslint/parser",
 "parserOptions": {
 "project": "./tsconfig.json",
 "tsconfigRootDir": "."
 },
 "plugins": ["@typescript-eslint"],
 "extends": [
 "eslint:recommended",
 "plugin:@typescript-eslint/recommended",
 "plugin:@typescript-eslint/recommended-requiring-type-checking"
 ],
 "rules": {
 "@typescript-eslint/no-floating-promises": "error",
 "@typescript-eslint/consistent-type-imports": "error",
 "@typescript-eslint/no-misused-promises": "error"
 }
}

まとめ

このニュースは、今すぐ全員が対応すべき話か、学習や新規開発で前提を更新しておく話かを分けて読むことが重要です。まずは公式情報で対象バージョンと影響範囲を確認し、自分の環境に関係する部分から見直してください。

参考リンク

参考リソース

← 一覧に戻る
PR
PR
PR
PR