T-CREATOR

Turbopack で TypeScript プロジェクトを構築する

Turbopack で TypeScript プロジェクトを構築する

TypeScript は現代の Web アプリケーション開発において、もはや欠かせない技術となりました。型安全性による開発効率の向上、コードの保守性向上、そして大規模プロジェクトでの品質担保など、その恩恵は計り知れません。

しかし、TypeScript プロジェクトの構築には従来から課題がありました。コンパイル時間の長さ、複雑な設定、そして開発体験の最適化など、多くの開発者が悩んできた問題です。

そこで注目されているのが Turbopack です。Vercel が開発したこの次世代バンドラーは、TypeScript プロジェクトの構築において革新的な解決策を提供します。従来のツールチェーンと比較して圧倒的な高速化を実現し、開発者の生産性を大幅に向上させることができるのです。

本記事では、Turbopack を使用した TypeScript プロジェクトの構築方法を、基盤構築から実際のプロジェクト例まで体系的に解説いたします。適切な設定により、あなたの TypeScript 開発体験は劇的に改善されるでしょう。

TypeScript プロジェクトの基盤構築

必要な依存関係のセットアップ

TypeScript プロジェクトを Turbopack で構築するには、適切な依存関係の管理が重要です。まず、基本的なパッケージから順番にセットアップしていきましょう。

プロジェクトの初期化

javascript// package.json の初期化
yarn init -y

// TypeScript の基本パッケージをインストール
yarn add typescript @types/node -D

// Turbopack の追加(Next.js 13.4+ で利用可能)
yarn add next@latest react react-dom
yarn add @types/react @types/react-dom -D

この初期セットアップにより、TypeScript の基本的な開発環境が整います。@types​/​node は Node.js の型定義を提供し、サーバーサイドの開発でも型安全性を確保できます。

開発効率を向上させる追加パッケージ

javascript// コード品質向上のためのツール
yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
yarn add prettier eslint-config-prettier eslint-plugin-prettier -D

// 型チェックの強化
yarn add ts-node nodemon -D

// パス解決の最適化
yarn add tsconfig-paths -D

これらのパッケージにより、コードの品質管理と開発効率が大幅に向上します。特に ts-node は TypeScript ファイルを直接実行できるため、開発時の作業効率が格段に上がります。

package.json のスクリプト設定

json{
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start",
    "type-check": "tsc --noEmit",
    "lint": "eslint . --ext .ts,.tsx",
    "lint:fix": "eslint . --ext .ts,.tsx --fix",
    "format": "prettier --write .",
    "clean": "rm -rf .next dist"
  }
}

--turbo フラグを指定することで、Turbopack が有効化されます。これにより、開発サーバーの起動時間とホットリロードの速度が劇的に改善されるでしょう。

tsconfig.json の最適設定

TypeScript の設定ファイルは、プロジェクトの型安全性とパフォーマンスに直接影響します。Turbopack に最適化された設定を詳しく見ていきましょう。

基本的な tsconfig.json 設定

json{
  "compilerOptions": {
    // 基本設定
    "target": "ES2022",
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,

    // モジュール設定
    "esModuleInterop": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,

    // 出力設定
    "noEmit": true,
    "incremental": true,
    "tsBuildInfoFile": ".next/cache/tsconfig.tsbuildinfo",

    // JSX設定
    "jsx": "preserve",
    "jsxImportSource": "react",

    // パス設定
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/utils/*": ["./src/utils/*"],
      "@/types/*": ["./src/types/*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules", ".next", "dist"]
}

この設定により、最新の JavaScript 機能を活用しつつ、厳密な型チェックが行われます。moduleResolution: "bundler" は Turbopack のような現代的なバンドラーに最適化された設定です。

パフォーマンス最適化設定

json{
  "compilerOptions": {
    // コンパイル最適化
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "incremental": true,
    "composite": false,

    // 型チェック最適化
    "assumeChangesOnlyAffectDirectDependencies": true,

    // 出力最適化
    "removeComments": true,
    "preserveConstEnums": false,
    "importsNotUsedAsValues": "remove"
  },

  // TypeScript 5.0+ の新機能
  "ts-node": {
    "esm": true,
    "experimentalSpecifierResolution": "node"
  }
}

これらの最適化により、大規模なプロジェクトでもコンパイル時間を短縮できます。特に incremental オプションは、変更されたファイルのみを再コンパイルするため、開発効率が大幅に向上するでしょう。

Turbopack 用 TypeScript 設定の調整

Turbopack を最大限活用するための、専用設定について詳しく解説します。

next.config.js での Turbopack 設定

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  // Turbopack の有効化
  experimental: {
    turbo: {
      // TypeScript の最適化設定
      rules: {
        '*.ts': {
          loaders: ['builtin:swc-loader'],
          as: '*.js',
        },
        '*.tsx': {
          loaders: ['builtin:swc-loader'],
          as: '*.js',
        },
      },

      // 解決設定
      resolveAlias: {
        '@': './src',
        '@/components': './src/components',
        '@/utils': './src/utils',
        '@/types': './src/types',
      },

      // 最適化オプション
      resolveExtensions: [
        '.ts',
        '.tsx',
        '.js',
        '.jsx',
        '.json',
      ],
    },
  },

  // TypeScript 設定
  typescript: {
    // 本番ビルド時の型チェック制御
    ignoreBuildErrors: false,
    // 型チェックの並列実行
    tsconfigPath: './tsconfig.json',
  },

  // SWC の最適化(Turbopack内部で使用)
  swcMinify: true,

  // 実験的機能
  experimental: {
    // Server Components の型安全性
    typedRoutes: true,
    // 並列ビルドの最適化
    parallelServerBuildTraces: true,
  },
};

module.exports = nextConfig;

この設定により、TypeScript ファイルが SWC(Speedy Web Compiler)によって高速に処理されます。従来の tsc と比較して、コンパイル速度が大幅に向上するでしょう。

turbo.json での TypeScript タスク設定

json{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    "**/.env.*local",
    "tsconfig.json",
    "next.config.js"
  ],
  "pipeline": {
    "type-check": {
      "dependsOn": [],
      "outputs": [],
      "inputs": [
        "src/**/*.{ts,tsx}",
        "types/**/*.ts",
        "tsconfig.json",
        "next-env.d.ts"
      ]
    },
    "build": {
      "dependsOn": ["type-check"],
      "outputs": [".next/**", "!.next/cache/**"],
      "env": ["NODE_ENV", "NEXT_PUBLIC_*"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

この設定により、型チェックとビルドプロセスが効率的に管理されます。type-check タスクを build の依存関係として設定することで、型エラーがある状態でのビルドを防げるのです。

開発環境の構築

型チェックとビルドプロセスの統合

Turbopack では、型チェックとビルドプロセスを効率的に統合できます。これにより、開発時の生産性と品質の両方を向上させることが可能です。

リアルタイム型チェックの設定

javascript// next.config.js での型チェック統合
const nextConfig = {
  typescript: {
    // 開発時の型チェック設定
    typeCheck: true,
    // 型エラー時の動作制御
    ignoreBuildErrors:
      process.env.NODE_ENV === 'development',
  },

  experimental: {
    turbo: {
      // 型チェックの並列実行
      loaders: {
        '.ts': ['builtin:swc-loader'],
        '.tsx': ['builtin:swc-loader'],
      },
    },
  },
};

この設定により、コード変更時に自動的に型チェックが実行されます。エラーがあった場合も開発サーバーは継続して動作し、開発効率を損なうことがありません。

段階的型チェック戦略

typescript// types/build.ts - ビルド時の型チェック制御
export interface BuildConfig {
  typeCheck: 'strict' | 'loose' | 'off';
  incrementalCheck: boolean;
  parallelCheck: boolean;
}

// 環境別の型チェック設定
export const getBuildConfig = (): BuildConfig => {
  const env = process.env.NODE_ENV;

  switch (env) {
    case 'development':
      return {
        typeCheck: 'loose',
        incrementalCheck: true,
        parallelCheck: true,
      };

    case 'production':
      return {
        typeCheck: 'strict',
        incrementalCheck: false,
        parallelCheck: true,
      };

    default:
      return {
        typeCheck: 'strict',
        incrementalCheck: true,
        parallelCheck: false,
      };
  }
};

環境に応じて型チェックの厳密さを調整することで、開発効率と品質のバランスを最適化できます。開発時は緩やかに、本番時は厳密に型チェックを行うことが重要です。

ホットリロードと TypeScript の組み合わせ

Turbopack の高速なホットリロード機能と TypeScript を組み合わせることで、驚異的な開発体験を実現できます。

高速ホットリロードの実現

typescript// src/utils/hmr.ts - ホットリロード最適化
interface HMRConfig {
  acceptUpdates: boolean;
  preserveState: boolean;
  logLevel: 'verbose' | 'info' | 'warn' | 'error';
}

// 開発環境でのHMR設定
export const setupHMR = (config: HMRConfig) => {
  if (process.env.NODE_ENV !== 'development') {
    return;
  }

  // TypeScript ファイルの変更を監視
  if (module.hot) {
    module.hot.accept((err) => {
      if (err) {
        console.error('HMR Error:', err);
        return;
      }

      // 型安全なホットリロード
      console.log('TypeScript module updated safely');
    });
  }
};

この設定により、TypeScript ファイルの変更が即座に反映されます。型エラーがあった場合でも、エラー情報が明確に表示され、修正が容易になるでしょう。

型安全なステート管理と HMR

typescript// src/hooks/useHMRState.ts - 型安全なステート保持
import { useState, useEffect } from 'react';

interface HMRState<T> {
  value: T;
  setValue: (value: T) => void;
  isHMRUpdate: boolean;
}

export function useHMRState<T>(
  initialValue: T,
  key: string
): HMRState<T> {
  const [value, setValue] = useState<T>(() => {
    // HMR時のステート復元
    if (
      typeof window !== 'undefined' &&
      window.__HMR_STATE__
    ) {
      return window.__HMR_STATE__[key] ?? initialValue;
    }
    return initialValue;
  });

  const [isHMRUpdate, setIsHMRUpdate] = useState(false);

  useEffect(() => {
    // ステートの保存
    if (typeof window !== 'undefined') {
      window.__HMR_STATE__ = {
        ...window.__HMR_STATE__,
        [key]: value,
      };
    }
  }, [value, key]);

  // HMR検知時の処理
  useEffect(() => {
    if (module.hot) {
      module.hot.addStatusHandler((status) => {
        setIsHMRUpdate(status === 'apply');
      });
    }
  }, []);

  return {
    value,
    setValue,
    isHMRUpdate,
  };
}

// グローバル型定義
declare global {
  interface Window {
    __HMR_STATE__?: Record<string, any>;
  }
}

この実装により、ホットリロード時にもアプリケーションの状態が保持されます。型安全性を保ちながら、開発効率を最大化できるのです。

エラーハンドリングと型安全性の確保

TypeScript プロジェクトでは、適切なエラーハンドリングが品質向上の鍵となります。

型安全なエラーハンドリング

typescript// src/utils/errors.ts - 型安全なエラー管理
export class TypedError<
  T extends string = string
> extends Error {
  constructor(
    public readonly type: T,
    message: string,
    public readonly context?: Record<string, unknown>
  ) {
    super(message);
    this.name = 'TypedError';
  }
}

// エラータイプの定義
export type AppErrorType =
  | 'VALIDATION_ERROR'
  | 'NETWORK_ERROR'
  | 'BUILD_ERROR'
  | 'TYPE_ERROR';

// エラーハンドラーの型定義
export interface ErrorHandler<T extends AppErrorType> {
  handle: (error: TypedError<T>) => void;
  canHandle: (error: unknown) => error is TypedError<T>;
}

// ビルドエラーハンドラー
export const buildErrorHandler: ErrorHandler<'BUILD_ERROR'> =
  {
    handle: (error) => {
      console.error(
        `Build Error [${error.type}]:`,
        error.message
      );
      if (error.context) {
        console.error('Context:', error.context);
      }
    },

    canHandle: (
      error
    ): error is TypedError<'BUILD_ERROR'> => {
      return (
        error instanceof TypedError &&
        error.type === 'BUILD_ERROR'
      );
    },
  };

型安全なエラーハンドリングにより、エラーの種類に応じた適切な処理が可能になります。これにより、デバッグ効率が大幅に向上するでしょう。

開発時のエラー表示最適化

typescript// src/components/ErrorBoundary.tsx - 型安全なエラー境界
import React, {
  Component,
  ErrorInfo,
  ReactNode,
} from 'react';

interface Props {
  children: ReactNode;
  fallback?: (
    error: Error,
    errorInfo: ErrorInfo
  ) => ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
  errorInfo?: ErrorInfo;
}

export class TypeSafeErrorBoundary extends Component<
  Props,
  State
> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return {
      hasError: true,
      error,
    };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.setState({
      error,
      errorInfo,
    });

    // 開発環境での詳細なエラー情報表示
    if (process.env.NODE_ENV === 'development') {
      console.group('🚨 TypeScript Error Details');
      console.error('Error:', error);
      console.error(
        'Component Stack:',
        errorInfo.componentStack
      );
      console.groupEnd();
    }
  }

  render() {
    if (this.state.hasError) {
      if (
        this.props.fallback &&
        this.state.error &&
        this.state.errorInfo
      ) {
        return this.props.fallback(
          this.state.error,
          this.state.errorInfo
        );
      }

      return (
        <div className='error-boundary'>
          <h2>型エラーが発生しました</h2>
          {process.env.NODE_ENV === 'development' && (
            <details>
              <summary>エラー詳細</summary>
              <pre>{this.state.error?.stack}</pre>
            </details>
          )}
        </div>
      );
    }

    return this.props.children;
  }
}

この実装により、型エラーが発生した場合でも適切にハンドリングされ、開発者にとって有用な情報が提供されます。

プロジェクト構造の設計

TypeScript プロジェクトのベストプラクティス

効率的な TypeScript プロジェクトには、適切なディレクトリ構造と命名規則が不可欠です。

推奨ディレクトリ構造

csharpsrc/
├── components/           # 再利用可能なコンポーネント
│   ├── ui/              # 基本UIコンポーネント
│   ├── forms/           # フォーム関連コンポーネント
│   └── layout/          # レイアウトコンポーネント
├── pages/               # ページコンポーネント(Next.js)
├── hooks/               # カスタムフック
├── utils/               # ユーティリティ関数
├── services/            # API呼び出し・外部サービス
├── store/               # 状態管理
├── types/               # 型定義
│   ├── api.ts          # API関連の型
│   ├── components.ts   # コンポーネントの型
│   └── global.ts       # グローバル型定義
└── constants/           # 定数定義

この構造により、コードの可読性と保守性が大幅に向上します。各ディレクトリの役割が明確で、チーム開発でも混乱が生じにくくなるでしょう。

型定義の管理戦略

typescript// src/types/api.ts - API関連の型定義
export interface ApiResponse<T> {
  data: T;
  status: 'success' | 'error';
  message?: string;
  timestamp: string;
}

export interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
  createdAt: string;
  updatedAt: string;
}

// API エンドポイントの型定義
export interface ApiEndpoints {
  users: {
    get: (id: string) => Promise<ApiResponse<User>>;
    list: (
      params?: UserListParams
    ) => Promise<ApiResponse<User[]>>;
    create: (
      data: CreateUserData
    ) => Promise<ApiResponse<User>>;
    update: (
      id: string,
      data: UpdateUserData
    ) => Promise<ApiResponse<User>>;
    delete: (id: string) => Promise<ApiResponse<void>>;
  };
}

// パラメータの型定義
export interface UserListParams {
  page?: number;
  limit?: number;
  search?: string;
  role?: User['role'];
}

export interface CreateUserData {
  name: string;
  email: string;
  role: User['role'];
}

export interface UpdateUserData
  extends Partial<CreateUserData> {}

型定義を体系的に管理することで、API の仕様変更やデータ構造の変更に迅速に対応できます。

モジュール解決とパス設定

効率的な開発のためには、適切なモジュール解決とパス設定が重要です。

パスマッピングの最適化

json// tsconfig.json でのパス設定
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/hooks/*": ["./src/hooks/*"],
      "@/utils/*": ["./src/utils/*"],
      "@/services/*": ["./src/services/*"],
      "@/types/*": ["./src/types/*"],
      "@/constants/*": ["./src/constants/*"],
      "@/assets/*": ["./public/assets/*"]
    }
  }
}

動的インポートの型安全性

typescript// src/utils/dynamicImport.ts - 型安全な動的インポート
export async function safeDynamicImport<T>(
  importFn: () => Promise<{ default: T }>,
  fallback?: T
): Promise<T> {
  try {
    const module = await importFn();
    return module.default;
  } catch (error) {
    console.error('Dynamic import failed:', error);
    if (fallback !== undefined) {
      return fallback;
    }
    throw error;
  }
}

// 使用例:コンポーネントの遅延読み込み
export const LazyComponent = React.lazy(() =>
  safeDynamicImport(
    () => import('@/components/heavy/HeavyComponent'),
    () => ({ default: () => <div>Loading failed</div> })
  )
);

この実装により、動的インポートでも型安全性が保たれ、エラーハンドリングも適切に行われます。

型定義ファイルの管理

大規模プロジェクトでは、型定義ファイルの適切な管理が成功の鍵となります。

型定義の階層化

typescript// src/types/base.ts - 基本型定義
export type ID = string;
export type Timestamp = string;
export type Status = 'active' | 'inactive' | 'pending';

// 共通インターフェース
export interface BaseEntity {
  id: ID;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  status: Status;
}

// src/types/domain.ts - ドメイン固有の型
import { BaseEntity } from './base';

export interface Product extends BaseEntity {
  name: string;
  description: string;
  price: number;
  category: Category;
  tags: string[];
}

export interface Category extends BaseEntity {
  name: string;
  slug: string;
  parentId?: ID;
}

// src/types/operations.ts - 操作関連の型
export type CreateProduct = Omit<Product, keyof BaseEntity>;
export type UpdateProduct = Partial<CreateProduct>;
export type ProductFilter = Partial<
  Pick<Product, 'category' | 'tags' | 'status'>
>;

階層化された型定義により、コードの再利用性と保守性が大幅に向上します。基本型から具体的な型へと段階的に定義することで、変更の影響範囲を最小限に抑えられるのです。

開発ワークフローの最適化

自動型生成とコード補完

Turbopack と TypeScript を組み合わせることで、開発効率を劇的に向上させる自動化機能を活用できます。

API スキーマからの型生成

typescript// scripts/generateTypes.ts - API スキーマから型を自動生成
import { writeFileSync } from 'fs';
import { join } from 'path';

interface ApiSchema {
  endpoints: Record<string, EndpointSchema>;
  models: Record<string, ModelSchema>;
}

interface EndpointSchema {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  path: string;
  request?: ModelSchema;
  response: ModelSchema;
}

interface ModelSchema {
  type:
    | 'object'
    | 'array'
    | 'string'
    | 'number'
    | 'boolean';
  properties?: Record<string, ModelSchema>;
  items?: ModelSchema;
  required?: string[];
}

// 型定義生成関数
export function generateTypesFromSchema(
  schema: ApiSchema
): string {
  let output = '// 自動生成された型定義ファイル\n';
  output += '// 手動での編集は避けてください\n\n';

  // モデル型の生成
  for (const [modelName, modelSchema] of Object.entries(
    schema.models
  )) {
    output += generateModelType(modelName, modelSchema);
    output += '\n\n';
  }

  // API エンドポイント型の生成
  output += 'export interface ApiClient {\n';
  for (const [
    endpointName,
    endpointSchema,
  ] of Object.entries(schema.endpoints)) {
    output += generateEndpointType(
      endpointName,
      endpointSchema
    );
  }
  output += '}\n';

  return output;
}

function generateModelType(
  name: string,
  schema: ModelSchema
): string {
  if (schema.type === 'object' && schema.properties) {
    let output = `export interface ${capitalize(name)} {\n`;

    for (const [propName, propSchema] of Object.entries(
      schema.properties
    )) {
      const isRequired =
        schema.required?.includes(propName) ?? false;
      const optional = isRequired ? '' : '?';
      const propType = getTypeString(propSchema);

      output += `  ${propName}${optional}: ${propType};\n`;
    }

    output += '}';
    return output;
  }

  return `export type ${capitalize(name)} = ${getTypeString(
    schema
  )};`;
}

function getTypeString(schema: ModelSchema): string {
  switch (schema.type) {
    case 'string':
      return 'string';
    case 'number':
      return 'number';
    case 'boolean':
      return 'boolean';
    case 'array':
      return schema.items
        ? `${getTypeString(schema.items)}[]`
        : 'unknown[]';
    case 'object':
      return 'Record<string, unknown>';
    default:
      return 'unknown';
  }
}

function generateEndpointType(
  name: string,
  schema: EndpointSchema
): string {
  const requestType = schema.request
    ? getTypeString(schema.request)
    : 'void';
  const responseType = getTypeString(schema.response);

  return `  ${name}: (data: ${requestType}) => Promise<${responseType}>;\n`;
}

function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

この自動生成スクリプトにより、API の変更に応じて型定義が自動的に更新されます。手動での型定義メンテナンスが不要になり、開発効率が大幅に向上するでしょう。

VSCode との統合設定

json// .vscode/settings.json - TypeScript 開発環境の最適化
{
  "typescript.preferences.importModuleSpecifier": "relative",
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  "typescript.inlayHints.parameterNames.enabled": "all",
  "typescript.inlayHints.variableTypes.enabled": true,
  "typescript.inlayHints.functionLikeReturnTypes.enabled": true,

  // Turbopack 固有の設定
  "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.next/**": true,
    "**/dist/**": true
  },

  // 自動フォーマット設定
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  }
}

これらの設定により、コード補完、自動インポート、リファクタリング支援が最適化されます。

リアルタイム型チェック

開発中のリアルタイム型チェックにより、エラーを早期に発見できます。

型チェック監視システム

typescript// src/utils/typeWatcher.ts - リアルタイム型チェック
import { spawn } from 'child_process';
import { watch } from 'chokidar';

interface TypeCheckResult {
  hasErrors: boolean;
  errors: TypeScriptError[];
  warnings: TypeScriptError[];
}

interface TypeScriptError {
  file: string;
  line: number;
  column: number;
  message: string;
  code: number;
}

export class TypeChecker {
  private isRunning = false;
  private watchedFiles = new Set<string>();

  constructor(private projectRoot: string) {}

  // 型チェックの開始
  async startWatching(): Promise<void> {
    if (this.isRunning) return;

    this.isRunning = true;
    console.log('🔍 TypeScript 監視を開始しました');

    // ファイル変更の監視
    const watcher = watch(['src/**/*.{ts,tsx}'], {
      ignored: ['node_modules', '.next', 'dist'],
      persistent: true,
    });

    watcher.on('change', (filePath) => {
      this.checkFile(filePath);
    });

    watcher.on('add', (filePath) => {
      this.watchedFiles.add(filePath);
      this.checkFile(filePath);
    });

    // 初回の全体チェック
    await this.checkAll();
  }

  // 特定ファイルの型チェック
  private async checkFile(
    filePath: string
  ): Promise<TypeCheckResult> {
    return new Promise((resolve) => {
      const tsc = spawn(
        'npx',
        ['tsc', '--noEmit', '--pretty', filePath],
        {
          cwd: this.projectRoot,
        }
      );

      let output = '';
      let errorOutput = '';

      tsc.stdout.on('data', (data) => {
        output += data.toString();
      });

      tsc.stderr.on('data', (data) => {
        errorOutput += data.toString();
      });

      tsc.on('close', (code) => {
        const result = this.parseTypeCheckOutput(
          output + errorOutput
        );

        if (result.hasErrors) {
          console.error(
            `❌ ${filePath} に型エラーがあります:`
          );
          result.errors.forEach((error) => {
            console.error(
              `  ${error.file}:${error.line}:${error.column} - ${error.message}`
            );
          });
        } else {
          console.log(
            `✅ ${filePath} の型チェックが完了しました`
          );
        }

        resolve(result);
      });
    });
  }

  // 全体の型チェック
  private async checkAll(): Promise<TypeCheckResult> {
    console.log('🔄 全体の型チェックを実行中...');

    return new Promise((resolve) => {
      const tsc = spawn(
        'npx',
        ['tsc', '--noEmit', '--pretty'],
        {
          cwd: this.projectRoot,
        }
      );

      let output = '';

      tsc.stdout.on('data', (data) => {
        output += data.toString();
      });

      tsc.stderr.on('data', (data) => {
        output += data.toString();
      });

      tsc.on('close', (code) => {
        const result = this.parseTypeCheckOutput(output);

        if (result.hasErrors) {
          console.error(
            `❌ ${result.errors.length} 件の型エラーが見つかりました`
          );
        } else {
          console.log(
            '✅ 型チェックが完了しました - エラーはありません'
          );
        }

        resolve(result);
      });
    });
  }

  // TypeScript 出力の解析
  private parseTypeCheckOutput(
    output: string
  ): TypeCheckResult {
    const lines = output.split('\n');
    const errors: TypeScriptError[] = [];
    const warnings: TypeScriptError[] = [];

    for (const line of lines) {
      const match = line.match(
        /^(.+?)\((\d+),(\d+)\): (error|warning) TS(\d+): (.+)$/
      );
      if (match) {
        const [
          ,
          file,
          lineNum,
          column,
          type,
          code,
          message,
        ] = match;
        const error: TypeScriptError = {
          file,
          line: parseInt(lineNum, 10),
          column: parseInt(column, 10),
          message,
          code: parseInt(code, 10),
        };

        if (type === 'error') {
          errors.push(error);
        } else {
          warnings.push(error);
        }
      }
    }

    return {
      hasErrors: errors.length > 0,
      errors,
      warnings,
    };
  }
}

// 使用例
const typeChecker = new TypeChecker(process.cwd());
typeChecker.startWatching();

このシステムにより、ファイル変更時に自動的に型チェックが実行され、問題を即座に発見できます。

デバッグ環境の構築

TypeScript プロジェクトの効率的なデバッグ環境を構築しましょう。

VSCode デバッグ設定

json// .vscode/launch.json - デバッグ設定
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/next",
      "args": ["dev", "--turbo"],
      "env": {
        "NODE_OPTIONS": "--inspect"
      },
      "skipFiles": ["<node_internals>/**"],
      "console": "integratedTerminal",
      "restart": true
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}",
      "sourceMapPathOverrides": {
        "webpack://_N_E/*": "${webRoot}/*",
        "webpack:///./*": "${webRoot}/*"
      }
    },
    {
      "name": "TypeScript: 単体テスト",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "env": {
        "NODE_ENV": "test"
      }
    }
  ]
}

型安全なログ出力システム

typescript// src/utils/logger.ts - 型安全なログシステム
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

interface LogContext {
  component?: string;
  userId?: string;
  requestId?: string;
  [key: string]: unknown;
}

interface LogEntry {
  level: LogLevel;
  message: string;
  timestamp: string;
  context?: LogContext;
  stack?: string;
}

export class TypeSafeLogger {
  private isDevelopment =
    process.env.NODE_ENV === 'development';

  // レベル別ログ出力
  debug(message: string, context?: LogContext): void {
    this.log('debug', message, context);
  }

  info(message: string, context?: LogContext): void {
    this.log('info', message, context);
  }

  warn(message: string, context?: LogContext): void {
    this.log('warn', message, context);
  }

  error(
    message: string,
    error?: Error,
    context?: LogContext
  ): void {
    const enhancedContext = error
      ? { ...context, error: error.message }
      : context;
    this.log(
      'error',
      message,
      enhancedContext,
      error?.stack
    );
  }

  // 型安全なログ出力
  private log(
    level: LogLevel,
    message: string,
    context?: LogContext,
    stack?: string
  ): void {
    const entry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      context,
      stack,
    };

    // 開発環境では詳細な情報を表示
    if (this.isDevelopment) {
      this.consoleOutput(entry);
    } else {
      // 本番環境では構造化ログを出力
      console.log(JSON.stringify(entry));
    }
  }

  // 開発環境用の見やすい出力
  private consoleOutput(entry: LogEntry): void {
    const emoji = this.getLevelEmoji(entry.level);
    const timestamp = entry.timestamp
      .split('T')[1]
      .split('.')[0];

    console.group(
      `${emoji} [${timestamp}] ${entry.message}`
    );

    if (entry.context) {
      console.log('Context:', entry.context);
    }

    if (entry.stack) {
      console.log('Stack trace:', entry.stack);
    }

    console.groupEnd();
  }

  private getLevelEmoji(level: LogLevel): string {
    const emojis = {
      debug: '🐛',
      info: 'ℹ️',
      warn: '⚠️',
      error: '❌',
    };
    return emojis[level];
  }
}

// シングルトンインスタンス
export const logger = new TypeSafeLogger();

// 使用例
logger.info('アプリケーションが開始されました', {
  component: 'App',
});
logger.error(
  'API呼び出しでエラーが発生しました',
  new Error('Network timeout'),
  {
    component: 'ApiClient',
    endpoint: '/api/users',
  }
);

この実装により、型安全なログ出力と効率的なデバッグが可能になります。

実際のプロジェクト例

シンプルな TypeScript アプリケーション

基本的な TypeScript アプリケーションを Turbopack で構築する例を見てみましょう。

プロジェクト構成

typescript// src/app/page.tsx - メインページコンポーネント
import { UserList } from '@/components/UserList';
import { CreateUserForm } from '@/components/CreateUserForm';
import { useUsers } from '@/hooks/useUsers';

export default function HomePage() {
  const { users, loading, error, createUser, deleteUser } =
    useUsers();

  if (loading) {
    return <div className='loading'>読み込み中...</div>;
  }

  if (error) {
    return (
      <div className='error'>エラー: {error.message}</div>
    );
  }

  return (
    <main className='container'>
      <h1>ユーザー管理アプリ</h1>

      <section className='create-section'>
        <h2>新規ユーザー作成</h2>
        <CreateUserForm onSubmit={createUser} />
      </section>

      <section className='list-section'>
        <h2>ユーザー一覧</h2>
        <UserList users={users} onDelete={deleteUser} />
      </section>
    </main>
  );
}

カスタムフックの実装

typescript// src/hooks/useUsers.ts - ユーザー管理フック
import { useState, useEffect, useCallback } from 'react';
import { User, CreateUserData } from '@/types/api';
import { userService } from '@/services/userService';
import { logger } from '@/utils/logger';

interface UseUsersReturn {
  users: User[];
  loading: boolean;
  error: Error | null;
  createUser: (data: CreateUserData) => Promise<void>;
  deleteUser: (id: string) => Promise<void>;
  refreshUsers: () => Promise<void>;
}

export function useUsers(): UseUsersReturn {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  // ユーザー一覧の取得
  const fetchUsers = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);

      const response = await userService.list();
      setUsers(response.data);

      logger.info('ユーザー一覧を取得しました', {
        component: 'useUsers',
        count: response.data.length,
      });
    } catch (err) {
      const error =
        err instanceof Error
          ? err
          : new Error('Unknown error');
      setError(error);

      logger.error(
        'ユーザー一覧の取得に失敗しました',
        error,
        {
          component: 'useUsers',
        }
      );
    } finally {
      setLoading(false);
    }
  }, []);

  // ユーザーの作成
  const createUser = useCallback(
    async (data: CreateUserData) => {
      try {
        const response = await userService.create(data);
        setUsers((prev) => [...prev, response.data]);

        logger.info('ユーザーを作成しました', {
          component: 'useUsers',
          userId: response.data.id,
          userName: response.data.name,
        });
      } catch (err) {
        const error =
          err instanceof Error
            ? err
            : new Error('Unknown error');
        setError(error);

        logger.error(
          'ユーザーの作成に失敗しました',
          error,
          {
            component: 'useUsers',
            userData: data,
          }
        );
        throw error;
      }
    },
    []
  );

  // ユーザーの削除
  const deleteUser = useCallback(async (id: string) => {
    try {
      await userService.delete(id);
      setUsers((prev) =>
        prev.filter((user) => user.id !== id)
      );

      logger.info('ユーザーを削除しました', {
        component: 'useUsers',
        userId: id,
      });
    } catch (err) {
      const error =
        err instanceof Error
          ? err
          : new Error('Unknown error');
      setError(error);

      logger.error('ユーザーの削除に失敗しました', error, {
        component: 'useUsers',
        userId: id,
      });
      throw error;
    }
  }, []);

  // 初回データ取得
  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

  return {
    users,
    loading,
    error,
    createUser,
    deleteUser,
    refreshUsers: fetchUsers,
  };
}

この実装により、型安全なデータ管理と効率的な状態更新が実現されます。

Next.js との統合例

Next.js と Turbopack を組み合わせた高度な例を見てみましょう。

App Router での型安全なルーティング

typescript// src/app/users/[id]/page.tsx - 動的ルートの型安全実装
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { UserDetail } from '@/components/UserDetail';
import { userService } from '@/services/userService';

interface PageProps {
  params: {
    id: string;
  };
  searchParams: {
    tab?: 'profile' | 'settings' | 'activity';
  };
}

// メタデータの生成
export async function generateMetadata({
  params,
}: PageProps): Promise<Metadata> {
  try {
    const user = await userService.get(params.id);

    return {
      title: `${user.data.name} - ユーザー詳細`,
      description: `${user.data.name}さんのプロフィールページです。`,
    };
  } catch {
    return {
      title: 'ユーザーが見つかりません',
    };
  }
}

// 静的パラメータの生成
export async function generateStaticParams() {
  try {
    const users = await userService.list({ limit: 100 });

    return users.data.map((user) => ({
      id: user.id,
    }));
  } catch {
    return [];
  }
}

// ページコンポーネント
export default async function UserPage({
  params,
  searchParams,
}: PageProps) {
  try {
    const user = await userService.get(params.id);
    const activeTab = searchParams.tab ?? 'profile';

    return (
      <div className='user-page'>
        <UserDetail
          user={user.data}
          activeTab={activeTab}
        />
      </div>
    );
  } catch {
    notFound();
  }
}

Server Actions の型安全実装

typescript// src/app/actions/userActions.ts - Server Actions
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
import { userService } from '@/services/userService';
import {
  CreateUserData,
  UpdateUserData,
} from '@/types/api';

// バリデーションスキーマ
const createUserSchema = z.object({
  name: z
    .string()
    .min(1, 'お名前は必須です')
    .max(100, 'お名前は100文字以内で入力してください'),
  email: z
    .string()
    .email('正しいメールアドレスを入力してください'),
  role: z.enum(['admin', 'user', 'guest'], {
    errorMap: () => ({
      message: '有効な役割を選択してください',
    }),
  }),
});

const updateUserSchema = createUserSchema.partial();

// Server Action の型定義
type ActionResult<T = void> = {
  success: boolean;
  data?: T;
  error?: string;
  fieldErrors?: Record<string, string[]>;
};

// ユーザー作成アクション
export async function createUserAction(
  formData: FormData
): Promise<ActionResult<{ id: string }>> {
  try {
    // フォームデータの解析
    const rawData = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      role: formData.get('role') as CreateUserData['role'],
    };

    // バリデーション
    const validationResult =
      createUserSchema.safeParse(rawData);
    if (!validationResult.success) {
      return {
        success: false,
        fieldErrors:
          validationResult.error.flatten().fieldErrors,
      };
    }

    // ユーザー作成
    const response = await userService.create(
      validationResult.data
    );

    // キャッシュの再検証
    revalidatePath('/users');

    return {
      success: true,
      data: { id: response.data.id },
    };
  } catch (error) {
    return {
      success: false,
      error:
        error instanceof Error
          ? error.message
          : '予期しないエラーが発生しました',
    };
  }
}

// ユーザー更新アクション
export async function updateUserAction(
  id: string,
  formData: FormData
): Promise<ActionResult> {
  try {
    const rawData = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      role: formData.get('role') as UpdateUserData['role'],
    };

    // 空の値を除外
    const filteredData = Object.fromEntries(
      Object.entries(rawData).filter(
        ([_, value]) => value !== '' && value !== null
      )
    );

    const validationResult =
      updateUserSchema.safeParse(filteredData);
    if (!validationResult.success) {
      return {
        success: false,
        fieldErrors:
          validationResult.error.flatten().fieldErrors,
      };
    }

    await userService.update(id, validationResult.data);

    revalidatePath('/users');
    revalidatePath(`/users/${id}`);

    return { success: true };
  } catch (error) {
    return {
      success: false,
      error:
        error instanceof Error
          ? error.message
          : '予期しないエラーが発生しました',
    };
  }
}

// ユーザー削除アクション
export async function deleteUserAction(
  id: string
): Promise<ActionResult> {
  try {
    await userService.delete(id);

    revalidatePath('/users');
    redirect('/users');
  } catch (error) {
    return {
      success: false,
      error:
        error instanceof Error
          ? error.message
          : '予期しないエラーが発生しました',
    };
  }
}

この実装により、Next.js の最新機能を型安全に活用できます。

Express + TypeScript API

バックエンド API の実装例も見てみましょう。

型安全な Express サーバー

typescript// src/server/app.ts - Express アプリケーション
import express, {
  Request,
  Response,
  NextFunction,
} from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { z } from 'zod';
import { userRouter } from './routes/userRoutes';
import { errorHandler } from './middleware/errorHandler';
import { validateRequest } from './middleware/validation';
import { logger } from '@/utils/logger';

// Express アプリケーションの型拡張
declare global {
  namespace Express {
    interface Request {
      user?: {
        id: string;
        role: 'admin' | 'user' | 'guest';
      };
    }
  }
}

const app = express();

// ミドルウェアの設定
app.use(helmet());
app.use(
  cors({
    origin: process.env.ALLOWED_ORIGINS?.split(',') || [
      'http://localhost:3000',
    ],
    credentials: true,
  })
);
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// リクエストログ
app.use(
  (req: Request, res: Response, next: NextFunction) => {
    logger.info('リクエストを受信しました', {
      component: 'ExpressServer',
      method: req.method,
      url: req.url,
      userAgent: req.get('User-Agent'),
    });
    next();
  }
);

// ルートの設定
app.use('/api/users', userRouter);

// ヘルスチェック
app.get('/health', (req: Request, res: Response) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version || '1.0.0',
  });
});

// エラーハンドリング
app.use(errorHandler);

// 404 ハンドラー
app.use('*', (req: Request, res: Response) => {
  res.status(404).json({
    status: 'error',
    message: 'エンドポイントが見つかりません',
    path: req.originalUrl,
  });
});

export { app };

型安全なルートハンドラー

typescript// src/server/routes/userRoutes.ts - ユーザールート
import { Router } from 'express';
import { z } from 'zod';
import { validateRequest } from '../middleware/validation';
import { asyncHandler } from '../utils/asyncHandler';
import { userController } from '../controllers/userController';

const router = Router();

// バリデーションスキーマ
const createUserSchema = z.object({
  body: z.object({
    name: z.string().min(1).max(100),
    email: z.string().email(),
    role: z.enum(['admin', 'user', 'guest']),
  }),
});

const updateUserSchema = z.object({
  params: z.object({
    id: z.string().uuid(),
  }),
  body: z.object({
    name: z.string().min(1).max(100).optional(),
    email: z.string().email().optional(),
    role: z.enum(['admin', 'user', 'guest']).optional(),
  }),
});

const getUserSchema = z.object({
  params: z.object({
    id: z.string().uuid(),
  }),
});

const listUsersSchema = z.object({
  query: z.object({
    page: z.string().transform(Number).optional(),
    limit: z.string().transform(Number).optional(),
    search: z.string().optional(),
    role: z.enum(['admin', 'user', 'guest']).optional(),
  }),
});

// ルートの定義
router.get(
  '/',
  validateRequest(listUsersSchema),
  asyncHandler(userController.list)
);
router.get(
  '/:id',
  validateRequest(getUserSchema),
  asyncHandler(userController.get)
);
router.post(
  '/',
  validateRequest(createUserSchema),
  asyncHandler(userController.create)
);
router.put(
  '/:id',
  validateRequest(updateUserSchema),
  asyncHandler(userController.update)
);
router.delete(
  '/:id',
  validateRequest(getUserSchema),
  asyncHandler(userController.delete)
);

export { router as userRouter };

この実装により、バックエンド API も型安全に構築できます。

まとめ

Turbopack を使用した TypeScript プロジェクトの構築について、基盤構築から実際のプロジェクト例まで包括的に解説いたしました。適切な設定と実装により、開発効率と品質の両方を大幅に向上させることができるでしょう。

TypeScript × Turbopack の真価

開発効率の劇的向上

従来の TypeScript 開発環境と比較して、Turbopack は圧倒的な高速化を実現します。コンパイル時間の短縮、ホットリロードの高速化、そして型チェックの並列実行により、開発者の生産性が格段に向上するのです。

型安全性の徹底

本記事で紹介した設定と実装パターンにより、プロジェクト全体で一貫した型安全性を確保できます。エラーの早期発見、リファクタリングの安全性、そしてチーム開発での品質担保が実現されるでしょう。

スケーラブルな開発基盤

適切なプロジェクト構造と型定義管理により、小規模なプロジェクトから大規模なエンタープライズアプリケーションまで対応できる基盤が構築されます。

継続的な改善のために

TypeScript と Turbopack の組み合わせは、まだ発展途上の技術です。新しい機能や最適化手法が継続的にリリースされているため、定期的な情報収集と設定の見直しが重要になります。

コミュニティの動向を注視し、ベストプラクティスを取り入れながら、あなたのプロジェクトを進化させ続けてください。適切に構築された TypeScript × Turbopack 環境は、開発チームの生産性を飛躍的に向上させる強力な武器となるでしょう。

関連リンク