TypeScriptパフォーマンス最適化実践:コンパイル時からランタイムまでの5つのプロダクションパターン
あなたのTypeScriptプロジェクトは緩やかに死んでいる
10万行のTypeScriptプロジェクトでtscに45秒かかり、HMRリフレッシュに3秒ラグがあり、バンドルが2.4MBで、本番のファーストペイントが3.8秒——これは例外ではなく、2026年のほとんどのTypeScriptプロジェクトの現実だ。型システムは開発生産性の倍増器であるはずが、パフォーマンスキラーになっている。 Project Referencesを設定する人はおらず、skipLibCheckを有効にする勇気もなく、ジェネリクスが5層にネストし、ランタイムの型ガードがホットパスに置かれ、バンドルにはツリーシェイクされなかったデッドコードが詰め込まれている。
本記事では、コア概念から出発し、型チェック高速化→ビルド最適化→ランタイムパフォーマンス→メモリ効率→プロダクション監視の5つのプロダクションパターンを、コンパイル時からランタイムまで、開発体験から本番メトリクスまで一歩ずつ解説する。
コア概念
| 概念 | 説明 |
|---|---|
| Type Checking Speed | TypeScriptコンパイラが型をチェックする速度、プロジェクト構造・型の複雑さ・設定の影響を受ける |
| Project References | 大規模プロジェクトを複数のサブプロジェクトに分割し、インクリメンタルコンパイルと並列型チェックを実現 |
| Incremental Build | キャッシュを利用して変更部分のみを再チェックし、フル型チェックを回避 |
| Tree-Shaking | ビルドツールが未参照コードを識別・削除し、最終バンドルサイズを削減 |
| Structural Typing | TypeScriptの構造的型システム、名前ではなく形状で型の互換性を判断 |
| Branded Types | 一意のマーカーで名義型を作成し、構造的型システム内で型の分離を実現 |
| Const Assertions | as constアサーション、値を最も狭いリテラル型として推論し、型空間の膨張を削減 |
| Performance Budget | バンドルサイズやコンパイル時間などに予算閾値を設定し、CIでパフォーマンス退化を自動ブロック |
最適化フロー
コンパイル時最適化:
プロジェクト分割(Project References) → インクリメンタルビルド → skipLibCheck → 分離モジュールコンパイル
ビルド時最適化:
tsup/esbuildによるtsc代替 → Tree-Shaking → Code Splitting → バンドル圧縮
ランタイム最適化:
ホットパスの型ガード回避 → 効率的ジェネリクス → インライン型アサーション → ランタイム型チェックの削減
メモリ最適化:
構造的型の再利用 → Branded Typesによるクラス継承の代替 → const assertions → 型の絞り込み
プロダクション監視:
型カバレッジ → バンドル分析 → Performance Budget → CI統合
問題分析:TypeScriptパフォーマンスの5つのボトルネック
- 型チェック速度の遅さ:単一の大規模プロジェクトのフルtsc時間がコード量に比例して増加、10万行プロジェクトでtscに30-60秒、CIパイプラインが型チェックでブロック
- ビルド産物の肥大化:tscは型チェックのみでツリーシェイクなし、barrel exportsで大量のデッドコードがバンドルに混入、ランタイム型ガードコードの膨張
- ランタイムパフォーマンスの損失:ホットパスでの頻繁な
typeof/instanceofチェック、深いジェネリクスのインスタンス化によるランタイムオーバーヘッド、過度なクラスデコレータ使用 - メモリ効率の低下:複雑な型推論がコンパイラメモリを大量消費、ランタイムで類似構造型の重複オブジェクト作成、未使用のconst assertionによる型空間の膨張
- パフォーマンス監視の欠如:型カバレッジメトリクスなし、バンドルサイズの予算管理なし、コンパイル時間の退化に気づかない、本番パフォーマンス問題を型設計に追溯到不能
ステップバイステップ:5つのプロダクションパターン
パターン1:型チェック速度最適化
// tsconfig.base.json - 基本設定
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}
// packages/core/tsconfig.json - コアパッケージ設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{ "path": "../shared" }
]
}
// packages/shared/tsconfig.json - 共有パッケージ設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
// packages/app/tsconfig.json - アプリケーションパッケージ設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"noEmit": true
},
"include": ["src"],
"references": [
{ "path": "../core" },
{ "path": "../shared" }
]
}
// tsconfig.json - トップレベルソリューション設定
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/core" },
{ "path": "packages/app" }
]
}
// scripts/typecheck.ts - インクリメンタル型チェックスクリプト
import { execSync } from 'child_process';
import { readFileSync, existsSync, unlinkSync } from 'fs';
import { performance } from 'perf_hooks';
interface TypeCheckResult {
project: string;
duration: number;
errors: number;
cached: boolean;
}
function typecheckProject(projectPath: string, clean: boolean = false): TypeCheckResult {
const buildInfoFile = `${projectPath}/.tsbuildinfo`;
const wasCached = existsSync(buildInfoFile) && !clean;
if (clean && existsSync(buildInfoFile)) {
unlinkSync(buildInfoFile);
}
const start = performance.now();
let errors = 0;
try {
execSync(`npx tsc --build ${projectPath} --verbose`, {
encoding: 'utf-8',
stdio: 'pipe',
});
} catch (error: any) {
const output = error.stdout || error.stderr || '';
const errorMatches = output.match(/error TS\d+:/g);
errors = errorMatches ? errorMatches.length : 0;
}
const duration = performance.now() - start;
return {
project: projectPath,
duration: Math.round(duration),
errors,
cached: wasCached,
};
}
function runFullTypeCheck(clean: boolean = false): void {
const projects = [
'packages/shared',
'packages/core',
'packages/app',
];
console.log('=== TypeScript Type Check Report ===\n');
let totalDuration = 0;
let totalErrors = 0;
for (const project of projects) {
const result = typecheckProject(project, clean);
totalDuration += result.duration;
totalErrors += result.errors;
const status = result.errors === 0 ? '✓' : '✗';
const cacheStatus = result.cached ? '(cached)' : '(full)';
console.log(
`${status} ${result.project}: ${result.duration}ms ${cacheStatus}, ${result.errors} errors`
);
}
console.log(`\nTotal: ${totalDuration}ms, ${totalErrors} errors`);
if (totalDuration > 10000) {
console.warn('\n⚠️ 型チェックが10秒の予算を超過!以下を検討:');
console.warn(' - Project Referencesの追加');
console.warn(' - skipLibCheckの有効化');
console.warn(' - isolatedModulesの使用');
}
}
const isClean = process.argv.includes('--clean');
runFullTypeCheck(isClean);
// src/types/performance-optimized.ts - チェック負荷を減らす最適化型定義
// ❌ 複雑な条件型が型チェックを遅くする
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// ✅ 組み込みユーティリティ型を使用し、重複インスタンス化を回避
type CachedPartial<T> = Partial<T>;
type CachedRequired<T> = Required<T>;
// ❌ 過度な再帰型の深さ
type FlattenDeep<T> = T extends Array<infer U>
? FlattenDeep<U>
: T;
// ✅ 再帰の深さを制限
type FlattenN<T, N extends number, D extends any[] = []> =
D['length'] extends N
? T
: T extends Array<infer U>
? FlattenN<U, N, [...D, 1]>
: T;
type Flatten3<T> = FlattenN<T, 3>;
// ❌ テンプレートリテラル型の過剰使用
type Route = `/api/${string}/${string}`;
// ✅ ユニオン型でテンプレートリテラルを代替
type ApiRoute =
| '/api/users'
| '/api/users/:id'
| '/api/posts'
| '/api/posts/:id'
| '/api/comments'
| '/api/comments/:id';
// ❌ 大きなユニオン型
type Status = 'pending' | 'processing' | 'approved' | 'rejected' | 'cancelled' | 'refunded' | 'completed' | 'archived' | 'draft' | 'published';
// ✅ グループ化されたユニオン型
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
type ContentStatus = 'draft' | 'published' | 'archived';
type PaymentStatus = 'approved' | 'rejected' | 'refunded';
// skipLibCheck最適化の.d.ts
declare module 'heavy-lib' {
export interface HeavyConfig {
readonly apiKey: string;
readonly endpoint: string;
readonly timeout: number;
readonly retries: number;
}
export function createClient(config: HeavyConfig): HeavyClient;
export interface HeavyClient {
getData<T>(id: string): Promise<T>;
setData<T>(id: string, data: T): Promise<void>;
}
}
パターン2:tsup/esbuildによるビルド最適化
// tsup.config.ts - tsupビルド設定
import { defineConfig } from 'tsup';
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: {
resolve: true,
compilerOptions: {
skipLibCheck: true,
composite: false,
},
},
splitting: true,
treeshake: true,
minify: true,
sourcemap: true,
clean: true,
outDir: 'dist',
target: 'es2022',
platform: 'node',
external: ['react', 'react-dom'],
esbuildOptions(options) {
options.logLevel = 'info';
options.chunkNames = 'chunks/[name]-[hash]';
},
},
{
entry: ['src/browser.ts'],
format: ['esm'],
treeshake: true,
minify: true,
sourcemap: true,
outDir: 'dist/browser',
target: 'es2022',
platform: 'browser',
esbuildOptions(options) {
options.conditions = ['browser'];
},
},
]);
// src/index.ts - ツリーシェイク対応のエクスポート構造最適化
// ❌ barrel exportがツリーシェイクを破壊
// export * from './utils';
// export * from './validators';
// export * from './transformers';
// ✅ バンドラの依存分析に有利な明示的名前付きエクスポート
export { validateEmail, validateUrl } from './validators/email';
export { validateAge, validateRange } from './validators/number';
export { formatDate, parseDate } from './transformers/date';
export { formatCurrency, parseCurrency } from './transformers/currency';
// ✅ 条件付きエクスポートでブラウザバンドルを削減
export type { UserSchema, CreateUserInput } from './types/user';
export type { PaginationParams, SearchParams } from './types/pagination';
// scripts/bundle-analysis.ts - バンドル分析スクリプト
import { build } from 'esbuild';
import { readFileSync, writeFileSync } from 'fs';
import { gzipSync } from 'zlib';
interface BundleMetrics {
name: string;
size: number;
gzipSize: number;
modules: number;
}
const BUDGET = {
maxBundleSize: 100 * 1024,
maxGzipSize: 30 * 1024,
maxChunkSize: 50 * 1024,
};
async function analyzeBundle(entryPoint: string): Promise<BundleMetrics> {
const result = await build({
entryPoints: [entryPoint],
bundle: true,
minify: true,
write: false,
metafile: true,
format: 'esm',
target: 'es2022',
external: ['react', 'react-dom'],
});
const outputFiles = result.outputFiles || [];
const metafile = result.metafile;
let totalSize = 0;
for (const file of outputFiles) {
totalSize += file.contents.byteLength;
}
const gzipSize = gzipSync(Buffer.from(outputFiles[0]?.contents || [])).byteLength;
const moduleCount = metafile ? Object.keys(metafile.inputs).length : 0;
return {
name: entryPoint,
size: totalSize,
gzipSize,
modules: moduleCount,
};
}
async function runBundleAnalysis(): Promise<void> {
console.log('=== Bundle Analysis Report ===\n');
const entryPoints = [
'src/index.ts',
'src/browser.ts',
];
let hasViolation = false;
for (const entry of entryPoints) {
const metrics = await analyzeBundle(entry);
const sizeKB = (metrics.size / 1024).toFixed(1);
const gzipKB = (metrics.gzipSize / 1024).toFixed(1);
console.log(`${entry}:`);
console.log(` Size: ${sizeKB}KB (budget: ${BUDGET.maxBundleSize / 1024}KB)`);
console.log(` Gzip: ${gzipKB}KB (budget: ${BUDGET.maxGzipSize / 1024}KB)`);
console.log(` Modules: ${metrics.modules}`);
if (metrics.size > BUDGET.maxBundleSize) {
console.warn(` ⚠️ バンドルサイズが予算を超過!`);
hasViolation = true;
}
if (metrics.gzipSize > BUDGET.maxGzipSize) {
console.warn(` ⚠️ Gzipサイズが予算を超過!`);
hasViolation = true;
}
console.log();
}
if (hasViolation) {
console.error('❌ パフォーマンス予算違反!');
process.exit(1);
} else {
console.log('✅ すべてのバンドルが予算内。');
}
}
runBundleAnalysis();
// package.json - 最適化ビルドスクリプト
{
"scripts": {
"typecheck": "tsc --build",
"typecheck:clean": "tsc --build --clean && tsc --build",
"build": "tsup",
"build:analyze": "tsup --metafile && node scripts/bundle-analysis.ts",
"dev": "tsup --watch",
"check:all": "npm run typecheck && npm run build && npm run test",
"prepack": "npm run build"
}
}
パターン3:ランタイムパフォーマンス最適化
// src/runtime/hot-path.ts - ホットパスの型ガード最適化
// ❌ ホットパスでの複雑な型ガード
function processData(data: unknown): ProcessedData {
if (
typeof data === 'object' &&
data !== null &&
'id' in data &&
typeof (data as any).id === 'string' &&
'name' in data &&
typeof (data as any).name === 'string' &&
'age' in data &&
typeof (data as any).age === 'number' &&
'email' in data &&
typeof (data as any).email === 'string'
) {
return data as ProcessedData;
}
throw new Error('Invalid data');
}
// ✅ 最小化された型ガード、コンパイル時の型を信頼
interface ProcessedData {
id: string;
name: string;
age: number;
email: string;
}
function processDataOptimized(data: unknown): ProcessedData {
if (typeof data !== 'object' || data === null) {
throw new Error('Expected object');
}
return data as ProcessedData;
}
// ✅ 階層型バリデーション:ホットパスは高速チェック、コールドパスは厳格バリデーション
function processHotPath(data: unknown): ProcessedData {
if (typeof data !== 'object' || data === null) {
throw new Error('Expected object');
}
return data as ProcessedData;
}
function processColdPath(data: unknown): ProcessedData {
const result = ProcessedDataSchema.safeParse(data);
if (!result.success) {
throw new Error(`バリデーション失敗: ${result.error.message}`);
}
return result.data;
}
import { z } from 'zod';
const ProcessedDataSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
age: z.number().int().min(0),
email: z.string().email(),
});
// src/runtime/generics.ts - 効率的なジェネリクスパターン
// ❌ ジェネリッククラスのインスタンス化がランタイム膨張を引き起こす
class DataStore<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
filter(predicate: (item: T) => boolean): T[] {
return this.items.filter(predicate);
}
map<U>(transform: (item: T) => U): U[] {
return this.items.map(transform);
}
reduce<U>(reducer: (acc: U, item: T) => U, initial: U): U {
return this.items.reduce(reducer, initial);
}
}
// ✅ 関数型ユーティリティでジェネリッククラスを代替
interface DataStore {
readonly items: readonly unknown[];
}
function createStore<T>(initialItems: T[] = []): DataStore {
return { items: Object.freeze([...initialItems]) };
}
function addItem<T>(store: DataStore, item: T): DataStore {
return { items: Object.freeze([...store.items, item]) };
}
function getItem<T>(store: DataStore, index: number): T {
return store.items[index] as T;
}
function filterItems<T>(store: DataStore, predicate: (item: T) => boolean): T[] {
return store.items.filter(predicate as (item: unknown) => boolean) as T[];
}
// ✅ 条件型でランタイム分岐を代替
type Result<T, E = Error> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
function ok<T>(value: T): Result<T> {
return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
return result.ok ? ok(fn(result.value)) : result;
}
function flatMapResult<T, U, E>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>
): Result<U, E> {
return result.ok ? fn(result.value) : result;
}
// ✅ ランタイム型消去のオーバーヘッドを回避
interface TypeTag<T extends string> {
readonly __type: T;
}
type UserId = string & TypeTag<'UserId'>;
type OrderId = string & TypeTag<'OrderId'>;
function createUserId(id: string): UserId {
return id as UserId;
}
function createOrderId(id: string): OrderId {
return id as OrderId;
}
function getUser(id: UserId): Promise<User | null> {
return Promise.resolve(null);
}
function getOrder(id: OrderId): Promise<Order | null> {
return Promise.resolve(null);
}
interface User {
id: UserId;
name: string;
}
interface Order {
id: OrderId;
total: number;
}
// src/runtime/iteration.ts - 効率的なイテレーションパターン
// ❌ 各イテレーションで新しい配列を作成
function processUsers(users: User[]): ProcessedUser[] {
return users
.filter((u) => u.age >= 18)
.map((u) => ({ ...u, displayName: u.name.toUpperCase() }))
.filter((u) => u.displayName.length > 0)
.map((u) => ({ ...u, score: calculateScore(u) }));
}
// ✅ 単一パスで複数のチェーン呼び出しを代替
function processUsersOptimized(users: User[]): ProcessedUser[] {
const result: ProcessedUser[] = [];
for (const u of users) {
if (u.age < 18) continue;
const displayName = u.name.toUpperCase();
if (displayName.length === 0) continue;
result.push({
...u,
displayName,
score: calculateScore(u),
});
}
return result;
}
// ✅ ジェネレーターで遅延計算
function* filterUsers(users: Iterable<User>): Generator<User> {
for (const u of users) {
if (u.age >= 18) yield u;
}
}
function* mapUsers(users: Iterable<User>): Generator<ProcessedUser> {
for (const u of users) {
yield {
...u,
displayName: u.name.toUpperCase(),
score: calculateScore(u),
};
}
}
function processUsersLazy(users: User[]): ProcessedUser[] {
return [...mapUsers(filterUsers(users))];
}
interface ProcessedUser extends User {
displayName: string;
score: number;
}
function calculateScore(user: User): number {
return user.name.length * user.age;
}
パターン4:メモリ効率最適化
// src/memory/structural-typing.ts - 構造的型の再利用
// ❌ 各インターフェースが独立定義、コンパイラが各々に独立した型を作成
interface UserResponse {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
}
interface UserListItem {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
}
interface UserProfile {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
bio: string;
avatar: string;
}
// ✅ 基本型+合成で再利用
interface BaseEntity {
readonly id: string;
readonly createdAt: string;
readonly updatedAt: string;
}
type UserRole = 'admin' | 'editor' | 'viewer';
interface UserBase extends BaseEntity {
readonly name: string;
readonly email: string;
readonly role: UserRole;
}
type UserResponse = UserBase;
type UserListItem = UserBase;
interface UserProfile extends UserBase {
readonly bio: string;
readonly avatar: string;
}
// ✅ Pick/Omitで基本型から派生
type UserSummary = Pick<UserBase, 'id' | 'name' | 'role'>;
type UserEmailInfo = Pick<UserBase, 'id' | 'email'>;
type UserWithoutRole = Omit<UserBase, 'role'>;
// src/memory/branded-types.ts - Branded Typesによるクラス継承の代替
// ❌ クラス継承がランタイムプロトタイプチェーンのオーバーヘッドを引き起こす
class Entity {
constructor(
public id: string,
public createdAt: Date,
) {}
}
class UserEntity extends Entity {
constructor(
id: string,
createdAt: Date,
public name: string,
public email: string,
) {
super(id, createdAt);
}
}
class OrderEntity extends Entity {
constructor(
id: string,
createdAt: Date,
public total: number,
public status: string,
) {
super(id, createdAt);
}
}
// ✅ Branded Types + 純粋データオブジェクトを使用
interface Brand<T extends string> {
readonly __brand: T;
}
type Branded<T, B extends string> = T & Brand<B>;
type UserId = Branded<string, 'UserId'>;
type OrderId = Branded<string, 'OrderId'>;
type ProductId = Branded<string, 'ProductId'>;
function createUserId(id: string): UserId {
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) {
throw new Error('UserIdのUUID形式が無効です');
}
return id as UserId;
}
function createOrderId(id: string): OrderId {
if (!/^ORD-\d{8}$/.test(id)) {
throw new Error('注文IDの形式が無効です');
}
return id as OrderId;
}
interface User {
readonly id: UserId;
readonly name: string;
readonly email: string;
readonly role: UserRole;
}
interface Order {
readonly id: OrderId;
readonly userId: UserId;
readonly total: number;
readonly status: OrderStatus;
readonly items: ReadonlyArray<OrderItem>;
}
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
interface OrderItem {
readonly productId: ProductId;
readonly quantity: number;
readonly unitPrice: number;
}
// ✅ Branded Typesが型安全性を確保、ランタイムオーバーヘッドゼロ
function getUserOrders(userId: UserId, orders: ReadonlyArray<Order>): ReadonlyArray<Order> {
return orders.filter((o) => o.userId === userId);
}
// コンパイル時のID誤用を防止
// getUserOrders(createOrderId('ORD-20260616'), orders); // ❌ 型エラー
// src/memory/const-assertions.ts - const assertions最適化
// ❌ 緩い型がコンパイラに多数の型インスタンスを作成させる
const ROUTES = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments',
};
type RouteKey = keyof typeof ROUTES; // string
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // string
// ✅ const assertionでリテラル型を固定
const ROUTES = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments',
} as const;
type RouteKey = keyof typeof ROUTES; // 'users' | 'posts' | 'comments'
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // '/api/users' | '/api/posts' | '/api/comments'
// ✅ const assertion + satisfiesパターン
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
type HttpMethod = typeof HTTP_METHODS[number]; // 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
const STATUS_CODES = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_ERROR: 500,
} as const;
type StatusCode = typeof STATUS_CODES[keyof typeof STATUS_CODES];
// 200 | 201 | 400 | 401 | 403 | 404 | 500
// ✅ satisfiesで型の正確性を確保しつつリテラル型を保持
const API_CONFIG = {
baseUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
} as const satisfies Record<string, string | number | Record<string, string>>;
type ApiConfigKey = keyof typeof API_CONFIG; // 'baseUrl' | 'timeout' | 'retries' | 'headers'
// ✅ オブジェクトを凍結してランタイム変更を防止
const IMMUTABLE_CONFIG = Object.freeze({
MAX_RETRIES: 3,
TIMEOUT_MS: 5000,
PAGE_SIZE: 20,
MAX_PAGE_SIZE: 100,
} as const);
type Config = typeof IMMUTABLE_CONFIG;
// src/memory/type-narrowing.ts - 効率的な型の絞り込み
// ❌ 繰り返しの型ガード
function handleData(data: unknown): string {
if (typeof data === 'object' && data !== null && 'type' in data) {
if ((data as any).type === 'user') {
if (typeof (data as any).name === 'string') {
return (data as any).name;
}
}
if ((data as any).type === 'order') {
if (typeof (data as any).id === 'string') {
return (data as any).id;
}
}
}
return 'unknown';
}
// ✅ 判別可能なユニオン + 型ガード関数を使用
interface UserData {
readonly type: 'user';
readonly name: string;
readonly email: string;
}
interface OrderData {
readonly type: 'order';
readonly id: string;
readonly total: number;
}
type AppData = UserData | OrderData;
function isUserData(data: AppData): data is UserData {
return data.type === 'user';
}
function isOrderData(data: AppData): data is OrderData {
return data.type === 'order';
}
function handleDataOptimized(data: AppData): string {
switch (data.type) {
case 'user':
return data.name;
case 'order':
return data.id;
}
}
// ✅ 型述語のキャッシュを使用
type TypeGuard<T> = (value: unknown) => value is T;
function createTypeGuard<T>(check: (value: unknown) => boolean): TypeGuard<T> {
return (value: unknown): value is T => check(value);
}
const isString = createTypeGuard<string>((v) => typeof v === 'string');
const isNumber = createTypeGuard<number>((v) => typeof v === 'number');
const isBoolean = createTypeGuard<boolean>((v) => typeof v === 'boolean');
function parseEnvValue<T>(
value: string | undefined,
guard: TypeGuard<T>,
defaultValue: T,
): T {
if (value === undefined) return defaultValue;
const parsed: unknown = value;
return guard(parsed) ? parsed : defaultValue;
}
const port = parseEnvValue(process.env.PORT, isNumber, 3000);
const debug = parseEnvValue(process.env.DEBUG, isBoolean, false);
パターン5:プロダクション監視とパフォーマンス予算
// scripts/type-coverage.ts - 型カバレッジチェック
import { execSync } from 'child_process';
interface TypeCoverageResult {
totalFiles: number;
typedFiles: number;
coverage: number;
anyCount: number;
unsafeCount: number;
}
function checkTypeCoverage(): TypeCoverageResult {
try {
const output = execSync(
'npx type-coverage --detail --at-least 95',
{ encoding: 'utf-8', stdio: 'pipe' }
);
const match = output.match(/(\d+)\/(\d+) files/);
const anyMatch = output.match(/(\d+) any/);
const typedFiles = match ? parseInt(match[1], 10) : 0;
const totalFiles = match ? parseInt(match[2], 10) : 0;
const anyCount = anyMatch ? parseInt(anyMatch[1], 10) : 0;
return {
totalFiles,
typedFiles,
coverage: totalFiles > 0 ? (typedFiles / totalFiles) * 100 : 0,
anyCount,
unsafeCount: anyCount,
};
} catch {
return {
totalFiles: 0,
typedFiles: 0,
coverage: 0,
anyCount: -1,
unsafeCount: -1,
};
}
}
function reportTypeCoverage(): void {
const result = checkTypeCoverage();
console.log('=== 型カバレッジレポート ===\n');
console.log(`ファイル: ${result.typedFiles}/${result.totalFiles}`);
console.log(`カバレッジ: ${result.coverage.toFixed(1)}%`);
console.log(`any数: ${result.anyCount}`);
if (result.coverage < 95) {
console.warn('\n⚠️ 型カバレッジが95%を下回っています!');
console.warn(' `npx type-coverage --detail`を実行して型なしコードを見つけてください。');
}
if (result.coverage < 80) {
console.error('❌ 型カバレッジが危険なほど低いです!');
process.exit(1);
}
}
reportTypeCoverage();
// scripts/performance-budget.ts - パフォーマンス予算CIチェック
import { readFileSync } from 'fs';
import { gzipSync } from 'zlib';
interface PerformanceBudget {
maxBundleSize: number;
maxGzipSize: number;
maxTypeCheckTime: number;
minTypeCoverage: number;
maxChunkCount: number;
}
const DEFAULT_BUDGET: PerformanceBudget = {
maxBundleSize: 100 * 1024, // 100KB
maxGzipSize: 30 * 1024, // 30KB
maxTypeCheckTime: 15000, // 15s
minTypeCoverage: 95, // 95%
maxChunkCount: 10,
};
interface BudgetCheckResult {
metric: string;
value: number;
budget: number;
unit: string;
passed: boolean;
}
function checkBundleBudget(
bundlePath: string,
budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult[] {
const results: BudgetCheckResult[] = [];
try {
const content = readFileSync(bundlePath);
const size = content.byteLength;
const gzipSize = gzipSync(content).byteLength;
results.push({
metric: 'バンドルサイズ',
value: size,
budget: budget.maxBundleSize,
unit: 'bytes',
passed: size <= budget.maxBundleSize,
});
results.push({
metric: 'Gzipサイズ',
value: gzipSize,
budget: budget.maxGzipSize,
unit: 'bytes',
passed: gzipSize <= budget.maxGzipSize,
});
} catch {
results.push({
metric: 'バンドルサイズ',
value: -1,
budget: budget.maxBundleSize,
unit: 'bytes',
passed: false,
});
}
return results;
}
function checkTypeCheckBudget(
budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult {
const start = Date.now();
try {
execSync('npx tsc --noEmit', { encoding: 'utf-8', stdio: 'pipe' });
} catch {
// 型エラーは別途処理
}
const duration = Date.now() - start;
return {
metric: '型チェック時間',
value: duration,
budget: budget.maxTypeCheckTime,
unit: 'ms',
passed: duration <= budget.maxTypeCheckTime,
};
}
function runPerformanceBudget(): void {
console.log('=== パフォーマンス予算チェック ===\n');
const results: BudgetCheckResult[] = [
...checkBundleBudget('dist/index.js'),
checkTypeCheckBudget(),
];
let allPassed = true;
for (const result of results) {
const status = result.passed ? '✓' : '✗';
const valueStr = result.value >= 0
? result.unit === 'bytes'
? `${(result.value / 1024).toFixed(1)}KB`
: `${result.value}${result.unit}`
: 'N/A';
const budgetStr = result.unit === 'bytes'
? `${(result.budget / 1024).toFixed(1)}KB`
: `${result.budget}${result.unit}`;
console.log(`${status} ${result.metric}: ${valueStr} (予算: ${budgetStr})`);
if (!result.passed) {
allPassed = false;
}
}
if (!allPassed) {
console.error('\n❌ パフォーマンス予算違反!');
process.exit(1);
} else {
console.log('\n✅ すべてのパフォーマンス予算をクリア。');
}
}
import { execSync } from 'child_process';
runPerformanceBudget();
// .github/workflows/typescript-perf.yml - CIパフォーマンス監視
// GitHub Actionsワークフロー設定
/*
name: TypeScript Performance
on:
pull_request:
paths:
- '**.ts'
- '**.tsx'
- 'tsconfig*.json'
- 'package.json'
jobs:
performance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Type Check
run: |
START=$(date +%s%N)
npx tsc --noEmit
END=$(date +%s%N)
DURATION=$(( (END - START) / 1000000 ))
echo "Type check duration: ${DURATION}ms"
if [ $DURATION -gt 15000 ]; then
echo "::error::型チェックが15秒の予算を超過 (${DURATION}ms)"
exit 1
fi
- name: Type Coverage
run: |
npx type-coverage --at-least 95 || {
echo "::error::型カバレッジが95%を下回っています"
exit 1
}
- name: Bundle Analysis
run: |
npm run build
node scripts/performance-budget.ts
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('perf-report.txt', 'utf8');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## 📊 TypeScriptパフォーマンスレポート\n${report}`
});
*/
// src/monitoring/runtime-metrics.ts - ランタイムパフォーマンスメトリクス収集
interface PerformanceMetric {
name: string;
value: number;
timestamp: number;
tags: Record<string, string>;
}
class PerformanceCollector {
private metrics: PerformanceMetric[] = [];
private readonly maxMetrics = 1000;
recordMetric(name: string, value: number, tags: Record<string, string> = {}): void {
this.metrics.push({
name,
value,
timestamp: Date.now(),
tags,
});
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics);
}
}
timeAsync<T>(name: string, fn: () => Promise<T>, tags?: Record<string, string>): Promise<T> {
const start = performance.now();
return fn().finally(() => {
const duration = performance.now() - start;
this.recordMetric(name, duration, { ...tags, unit: 'ms' });
});
}
timeSync<T>(name: string, fn: () => T, tags?: Record<string, string>): T {
const start = performance.now();
try {
return fn();
} finally {
const duration = performance.now() - start;
this.recordMetric(name, duration, { ...tags, unit: 'ms' });
}
}
getMetrics(name?: string): PerformanceMetric[] {
if (name) {
return this.metrics.filter((m) => m.name === name);
}
return [...this.metrics];
}
getAverage(name: string): number {
const named = this.getMetrics(name);
if (named.length === 0) return 0;
return named.reduce((sum, m) => sum + m.value, 0) / named.length;
}
getP95(name: string): number {
const named = this.getMetrics(name);
if (named.length === 0) return 0;
const sorted = named.map((m) => m.value).sort((a, b) => a - b);
const index = Math.ceil(sorted.length * 0.95) - 1;
return sorted[index];
}
generateReport(): string {
const metricNames = [...new Set(this.metrics.map((m) => m.name))];
const lines: string[] = ['### ランタイムパフォーマンスレポート\n'];
for (const name of metricNames) {
const avg = this.getAverage(name).toFixed(2);
const p95 = this.getP95(name).toFixed(2);
const count = this.getMetrics(name).length;
lines.push(`- **${name}**: avg=${avg}ms, p95=${p95}ms, n=${count}`);
}
return lines.join('\n');
}
}
const perfCollector = new PerformanceCollector();
async function fetchWithMetrics<T>(
url: string,
schema: z.ZodSchema<T>,
): Promise<T> {
return perfCollector.timeAsync(
'api.fetch',
async () => {
const response = await fetch(url);
const raw = await response.json();
const result = schema.safeParse(raw);
if (!result.success) {
throw new Error(`APIバリデーション失敗: ${result.error.message}`);
}
return result.data;
},
{ url: new URL(url).pathname },
);
}
よくある落とし穴
落とし穴1:skipLibCheckによる型の不安全化
// ❌ グローバルskipLibCheckがすべての.d.tsチェックを無効化
// tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true // すべてのライブラリ型チェックを無効化
}
}
// ✅ skipLibCheckはnode_modulesの.d.tsのみスキップ、自前の.d.tsは引き続きチェック
// tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true // 安全:node_modulesのみに影響
}
}
// 自前のtypesディレクトリは別途strictチェックを設定
// types/tsconfig.json
{
"compilerOptions": {
"strict": true,
"skipLibCheck": false
}
}
落とし穴2:barrel exportsによるツリーシェイクの破壊
// ❌ index.tsですべてのモジュールをre-export
// src/index.ts
export * from './users';
export * from './orders';
export * from './products';
export * from './analytics';
// ✅ 明示的なオンデマンドエクスポート
// src/index.ts
export { UserService } from './users/service';
export { OrderService } from './orders/service';
export type { User, CreateUserInput } from './users/types';
export type { Order, CreateOrderInput } from './orders/types';
// ✅ package.jsonでsideEffectsを設定
// package.json
{
"sideEffects": false,
"module": "./dist/index.mjs",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./users": {
"import": "./dist/users.mjs"
}
}
}
落とし穴3:Project Referencesの不完全な設定
// ❌ compositeオプションの欠落で参照が無効
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
// "composite": true が欠落
}
}
// ✅ 参照されるプロジェクトはcompositeを有効にする必要がある
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
落とし穴4:過度なランタイム型ガード
// ❌ 各ループイテレーションでフル型チェック
function processItems(items: unknown[]): Result[] {
return items.map((item) => {
const validated = ItemSchema.parse(item); // 毎回フルバリデーション
return transform(validated);
});
}
// ✅ エントリで1回バリデーション、内部は型を信頼
function processItems(items: unknown[]): Result[] {
const validated = z.array(ItemSchema).parse(items); // 一括バリデーション
return validated.map(transform);
}
// ✅ または階層型バリデーションを使用
function processItems(items: unknown[]): Result[] {
return items.map((item) => {
if (!isPlainObject(item)) throw new Error('Invalid item');
return transform(item as Item);
});
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
落とし穴5:コンパイラメモリ制限の無視
// ❌ デフォルト1.5GBのメモリでは大規模プロジェクトに不十分
// tsc実行時にメモリオーバーフロー
// FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
// ✅ Node.jsのメモリ制限を増加
// package.json
{
"scripts": {
"typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --build",
"typecheck:ci": "NODE_OPTIONS='--max-old-space-size=8192' tsc --build"
}
}
// ✅ プロジェクトを分割してコンパイラごとのメモリ要件を削減
// Project Referencesで大規模プロジェクトを複数のサブプロジェクトに分割
// 各サブプロジェクトが独立してコンパイルされ、ピークメモリが大幅に削減
エラートラブルシューティング表
| エラー現象 | 可能な原因 | 解決策 |
|---|---|---|
tscが30秒を超過 |
Project Referencesなしの単一大規模プロジェクト | 複数のサブプロジェクトに分割、インクリメンタルコンパイルを有効化 |
error TS6307 |
Project Referencesにcompositeが欠落 | 参照先プロジェクトに"composite": trueを追加 |
| バンドルサイズが異常に大きい | barrel exportsがツリーシェイクを破壊 | 明示的名前付きエクスポートに変更、sideEffectsを設定 |
| HMRリフレッシュが遅い | tscがフル再チェック | incrementalとisolatedModulesを有効化 |
FATAL ERROR: Reached heap limit |
コンパイラメモリオーバーフロー | --max-old-space-sizeを増加またはプロジェクトを分割 |
| 型推論がタイムアウト | 再帰型の深さが過大 | 再帰の深さを制限、FlattenN<T, 3>を使用 |
Type instantiation is excessively deep |
条件型のネストが深すぎる | 型ロジックを簡略化、中間型エイリアスを使用 |
| ランタイムパフォーマンスが悪い | ホットパスでの頻繁な型ガード | 階層型バリデーション、ホットパスのチェックを最小化 |
skipLibCheck後の型エラー |
カスタム.d.tsに問題あり | カスタムtypesディレクトリに別途strictチェックを設定 |
| バンドルに未使用コードが含まれる | tsconfig importsNotUsedAsValues |
tsup/esbuildでtscの代わりにバンドル |
高度な最適化
コンパイラパフォーマンスチューニング
// tsconfig.perf.json - 最大パフォーマンス設定
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo",
"skipLibCheck": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"assumeChangesOnlyAffectDirectDependencies": true,
"disableSourceOfProjectReferenceRedirect": true,
"disableReferencedProjectLoad": true
}
}
// scripts/tsc-watch.ts - スマートインクリメンタルウォッチャー
import { watch } from 'chokidar';
import { execSync } from 'child_process';
import { relative, dirname, extname } from 'path';
import { performance } from 'perf_hooks';
interface WatchConfig {
include: string[];
exclude: string[];
debounceMs: number;
projects: Record<string, string[]>;
}
const watchConfig: WatchConfig = {
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
debounceMs: 300,
projects: {
'packages/shared': ['packages/shared/src/**/*.ts'],
'packages/core': ['packages/core/src/**/*.ts'],
'packages/app': ['packages/app/src/**/*.ts'],
},
};
let debounceTimer: NodeJS.Timeout | null = null;
function getAffectedProject(filePath: string): string | null {
for (const [project, patterns] of Object.entries(watchConfig.projects)) {
for (const pattern of patterns) {
const dir = pattern.replace('/**/*.ts', '');
if (filePath.startsWith(dir)) {
return project;
}
}
}
return null;
}
function typecheckAffected(filePath: string): void {
const project = getAffectedProject(filePath);
if (!project) return;
const start = performance.now();
try {
execSync(`npx tsc --build ${project}`, { encoding: 'utf-8', stdio: 'pipe' });
const duration = (performance.now() - start).toFixed(0);
console.log(`✓ ${project}: ${duration}ms`);
} catch (error: any) {
const duration = (performance.now() - start).toFixed(0);
console.error(`✗ ${project}: ${duration}ms`);
console.error(error.stdout || error.message);
}
}
const watcher = watch(watchConfig.include, {
ignored: watchConfig.exclude,
persistent: true,
ignoreInitial: true,
});
watcher.on('change', (filePath) => {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
typecheckAffected(filePath);
}, watchConfig.debounceMs);
});
watcher.on('add', (filePath) => {
typecheckAffected(filePath);
});
console.log('🔍 TypeScriptの変更を監視中...');
バンドル最適化戦略
// scripts/advanced-bundle-opt.ts - 高度なバンドル最適化
import { build, Plugin } from 'esbuild';
const sizeLimitPlugin: Plugin = {
name: 'size-limit',
setup(build) {
build.onEnd((result) => {
for (const output of result.outputFiles || []) {
const sizeKB = output.contents.byteLength / 1024;
const path = output.path;
if (sizeKB > 50) {
console.warn(`⚠️ ${path}: ${sizeKB.toFixed(1)}KBが50KBチャンク制限を超過`);
}
}
});
},
};
const deadCodeEliminationPlugin: Plugin = {
name: 'dead-code-elimination',
setup(build) {
build.onEnd((result) => {
const content = result.outputFiles?.[0]?.text || '';
const deadCodePatterns = [
/if\s*\(\s*false\s*\)/g,
/if\s*\(\s*true\s*\)\s*\{/g,
/\/\/\s*@ts-ignore/g,
];
for (const pattern of deadCodePatterns) {
const matches = content.match(pattern);
if (matches && matches.length > 0) {
console.warn(`⚠️ デッドコードの可能性: ${matches.length}件の${pattern}`);
}
}
});
},
};
async function buildOptimized(): Promise<void> {
await build({
entryPoints: ['src/index.ts'],
bundle: true,
minify: true,
treeShaking: true,
format: 'esm',
target: 'es2022',
outdir: 'dist',
metafile: true,
splitting: true,
plugins: [sizeLimitPlugin, deadCodeEliminationPlugin],
external: ['react', 'react-dom', 'zod'],
define: {
'process.env.NODE_ENV': '"production"',
},
logLevel: 'info',
});
}
buildOptimized();
ランタイムパフォーマンスプロファイリング
// src/monitoring/profiler.ts - ランタイムパフォーマンスプロファイラ
interface ProfileEntry {
operation: string;
startTime: number;
endTime: number;
duration: number;
memoryBefore: number;
memoryAfter: number;
memoryDelta: number;
}
class RuntimeProfiler {
private entries: ProfileEntry[] = [];
private readonly maxEntries = 500;
profile<T>(operation: string, fn: () => T): T {
const memBefore = process.memoryUsage().heapUsed;
const startTime = performance.now();
try {
return fn();
} finally {
const endTime = performance.now();
const memAfter = process.memoryUsage().heapUsed;
this.entries.push({
operation,
startTime,
endTime,
duration: endTime - startTime,
memoryBefore: memBefore,
memoryAfter: memAfter,
memoryDelta: memAfter - memBefore,
});
if (this.entries.length > this.maxEntries) {
this.entries = this.entries.slice(-this.maxEntries);
}
}
}
async profileAsync<T>(operation: string, fn: () => Promise<T>): Promise<T> {
const memBefore = process.memoryUsage().heapUsed;
const startTime = performance.now();
try {
return await fn();
} finally {
const endTime = performance.now();
const memAfter = process.memoryUsage().heapUsed;
this.entries.push({
operation,
startTime,
endTime,
duration: endTime - startTime,
memoryBefore: memBefore,
memoryAfter: memAfter,
memoryDelta: memAfter - memBefore,
});
if (this.entries.length > this.maxEntries) {
this.entries = this.entries.slice(-this.maxEntries);
}
}
}
getHotspots(thresholdMs: number = 10): ProfileEntry[] {
return this.entries
.filter((e) => e.duration > thresholdMs)
.sort((a, b) => b.duration - a.duration);
}
getMemoryHotspots(thresholdBytes: number = 1024 * 100): ProfileEntry[] {
return this.entries
.filter((e) => e.memoryDelta > thresholdBytes)
.sort((a, b) => b.memoryDelta - a.memoryDelta);
}
generateReport(): string {
const lines: string[] = [
'### ランタイムパフォーマンスプロファイル\n',
'#### タイムホットスポット (>10ms)',
];
for (const entry of this.getHotspots()) {
lines.push(
`- **${entry.operation}**: ${entry.duration.toFixed(2)}ms, ` +
`メモリ: ${(entry.memoryDelta / 1024).toFixed(1)}KB`
);
}
lines.push('\n#### メモリホットスポット (>100KB)');
for (const entry of this.getMemoryHotspots()) {
lines.push(
`- **${entry.operation}**: ${(entry.memoryDelta / 1024).toFixed(1)}KB, ` +
`時間: ${entry.duration.toFixed(2)}ms`
);
}
return lines.join('\n');
}
}
const profiler = new RuntimeProfiler();
// 使用例
const users = profiler.profile('fetchUsers', () => {
return Array.from({ length: 1000 }, (_, i) => ({
id: `user-${i}`,
name: `ユーザー ${i}`,
email: `user${i}@example.com`,
}));
});
const result = profiler.profile('processUsers', () => {
return users.filter((u) => u.email.includes('example')).map((u) => u.name);
});
console.log(profiler.generateReport());
ソリューション比較
| 最適化次元 | tscインクリメンタル | Project References | tsup/esbuild | Vite | Turbopack |
|---|---|---|---|---|---|
| コンパイル速度 | ★★★ | ★★★★ | ★★★★★ | ★★★★★ | ★★★★★ |
| 設定の複雑さ | 低 | 中 | 低 | 低 | 低 |
| インクリメンタル対応 | はい | はい | いいえ(フルだが非常に高速) | はい | はい |
| 型チェック | 内蔵 | 内蔵 | 外部(tsc) | 外部(tsc) | 外部(tsc) |
| ツリーシェイキング | いいえ | いいえ | はい | はい | はい |
| バンドルサイズ | N/A | N/A | 優秀 | 優秀 | 優秀 |
| ユースケース | ライブラリ開発 | 大規模プロジェクト | ライブラリ/ツール | Webアプリ | Next.js |
| プロダクション対応 | ★★★★★ | ★★★★★ | ★★★★★ | ★★★★★ | ★★★★ |
| ランタイム最適化 | 型ガード最適化 | Branded Types | const assertions | ジェネレーター | 階層型バリデーション |
|---|---|---|---|---|---|
| パフォーマンス向上 | ★★★★ | ★★★ | ★★ | ★★★ | ★★★★ |
| コード侵入 | 中 | 低 | 低 | 中 | 中 |
| 型安全性 | ★★★ | ★★★★★ | ★★★★★ | ★★★ | ★★★★ |
| 学習コスト | 低 | 低 | 低 | 中 | 低 |
| ユースケース | ホットパス | ID型 | 定数設定 | 大規模データセット | API境界 |
TypeScriptパフォーマンス最適化は「錦上花を添える」ものではなく、プロダクションプロジェクトの「生命線」だ。 コンパイル時はProject Referencesとインクリメンタルコンパイルで10倍高速化、ビルド時はtsup/esbuildでtscを代替してツリーシェイキングを獲得、ランタイムはホットパスの型ガードを回避、メモリはBranded Typesとconst assertionsで膨張を削減、プロダクションはパフォーマンス予算と型カバレッジでベースラインを守る。パフォーマンスは最適化するものではなく、設計するものだ。 初日からProject Referencesを設定し、最初のモジュールから明示的エクスポートを使い、最初の型からconst assertionを使う。
推奨ツール
- JSONフォーマッター — tsconfigやビルド出力JSONをフォーマット、設定問題を迅速にトラブルシュート
- コードフォーマッター — TypeScriptコードをフォーマット、チームのコードスタイルを統一
- cURL→コード変換 — APIリクエストを型安全なTypeScript fetchコードに変換
ブラウザローカルツールを無料で試す →