# 機能設計書 110-HMR

## 概要

本ドキュメントは、BunにおけるHMR（Hot Module Replacement）機能の機能設計を記述したものである。HMRは、開発中のアプリケーションを再起動せずにモジュールを更新する機能を提供する。

### 本機能の処理概要

HMRは、ブラウザではWebSocketを介してバンドラーと通信し、サーバーでは`server_exports`を介して通信する。`bun build`の動作を模倣するモジュールローダーを実装しており、開発ビルドが本番ビルドと同様に機能することを目指している。

**業務上の目的・背景**：開発時のイテレーション速度は生産性に直結する。ページ全体をリロードせずにコード変更を反映することで、アプリケーション状態を維持しながら迅速な開発が可能になる。

**機能の利用シーン**：
- Reactコンポーネントの編集とリアルタイム更新
- CSSスタイルの即座反映
- サーバーサイドコードの変更検出
- エラー発生時のオーバーレイ表示

**主要な処理内容**：
1. `HMRModule`: ホットリロード対応モジュールローダー
2. `import.meta.hot`: HMR API（accept/dispose/invalidate）
3. WebSocket通信: クライアント-サーバー間のHMRメッセージ
4. CSSリロード: スタイルシートの動的更新

**関連システム・外部連携**：
- DevServer: サーバーサイドのHMR処理
- React Fast Refresh: Reactコンポーネントの状態保持更新
- WebSocket: リアルタイム通信

**権限による制御**：特になし

## 関連画面

本機能は開発者向け機能であり、直接的な関連画面は存在しない。ただし、エラーオーバーレイをブラウザに表示する。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | エラーオーバーレイ | 出力 | ランタイムエラー表示 |

## 機能種別

開発ツール / モジュールローダー

## 入力仕様

### 入力パラメータ

#### `import.meta.hot` API

| メソッド | 型 | 説明 |
|---------|-----|------|
| accept() | void | 自己受け入れ（モジュール自身の更新を受け入れ） |
| accept(callback) | void | 自己受け入れ + コールバック |
| acceptSpecifiers(specifiers, callback) | void | 依存モジュールの更新を受け入れ |
| dispose(callback) | void | クリーンアップ処理の登録 |
| decline() | void | 更新の拒否（現在はnoop） |
| invalidate() | void | 完全リロードを要求 |
| on(event, callback) | void | イベントリスナー登録 |
| off(event, callback) | void | イベントリスナー解除 |
| data | object | 更新間で保持されるデータ |

#### WebSocketメッセージ（サーバー -> クライアント）

| MessageId | 説明 | ペイロード |
|-----------|------|----------|
| version | サーバーバージョン/設定ハッシュ | 16 chars |
| hot_update | ホットアップデート | ルート情報, CSS, JSモジュール |
| set_url_response | URL設定応答 | route index (u32) |
| errors | エラー情報 | SerializedFailure |

#### WebSocketメッセージ（クライアント -> サーバー）

| IncomingMessageId | 説明 | ペイロード |
|-------------------|------|----------|
| init | 初期化 | generation (8 hex chars) |
| subscribe | トピック購読 | topic chars |
| set_url | アクティブルート設定 | pathname |

### 入力データソース

- WebSocketメッセージ
- ファイルシステム変更（DevServer経由）

## 出力仕様

### 出力データ

#### HMRModule.State

| 状態 | 値 | 説明 |
|------|-----|------|
| Pending | 0 | ロード中 |
| Stale | 1 | 古い（再ロード必要） |
| Loaded | 2 | ロード完了 |
| Error | 3 | エラー |

#### HMRイベント

| イベント名 | 説明 |
|-----------|------|
| bun:afterUpdate | モジュール更新完了後 |
| bun:invalidate | 完全リロード要求時 |
| bun:ws:connect | WebSocket接続確立 |
| bun:ws:disconnect | WebSocket切断 |

### 出力先

- ブラウザコンソール
- DOMへのスタイル適用
- エラーオーバーレイ

## 処理フロー

### 処理シーケンス（クライアントHMR初期化）

```
1. WebSocket初期化
   └─ initWebSocket(handlers)
2. バージョン確認
   └─ handlers[MessageId.version]
3. トピック購読
   └─ ws.send("she") // hot_update + errors
4. URL設定
   └─ ws.send("n" + location.pathname)
5. ソースマップ登録
   └─ ws.send("i" + config.generation)
```

### 処理シーケンス（モジュール更新）

```
1. hot_update受信
   └─ handlers[MessageId.hot_update]
2. ルート更新確認
   └─ serverSideRoutesUpdated
3. CSS更新
   └─ editCssArray() / editCssContent()
4. JSモジュール更新
   ├─ スクリプトタグ作成
   ├─ bun:hmr シンボルでモジュール登録
   └─ replaceModules()
5. disposeコールバック実行
   └─ module.onDispose.forEach(cb => cb(data))
6. acceptコールバック実行
   └─ module.selfAccept(module.exports)
7. イベント発火
   └─ emitEvent("bun:afterUpdate")
```

### 処理シーケンス（サーバーHMR）

```
1. server_exports.registerUpdate受信
   └─ replaceModules(modules)
2. コンポーネントマニフェスト更新
   ├─ serverManifest追加/削除
   └─ ssrManifest追加/削除
3. リクエスト処理
   └─ server_exports.handleRequest
```

### フローチャート

```mermaid
flowchart TD
    A[ファイル変更] --> B[DevServer検出]
    B --> C[インクリメンタルビルド]
    C --> D[HMRペイロード生成]
    D --> E{クライアント or サーバー?}

    E -->|クライアント| F[WebSocket送信]
    F --> G[hot_updateハンドラー]
    G --> H{CSS or JS?}
    H -->|CSS| I[editCssContent]
    H -->|JS| J[スクリプトタグ追加]
    J --> K[bun:hmrシンボル呼び出し]
    K --> L[replaceModules]
    L --> M[disposeコールバック]
    M --> N[acceptコールバック]
    N --> O{React Refresh?}
    O -->|Yes| P[performReactRefresh]
    O -->|No| Q[bun:afterUpdateイベント]
    P --> Q

    E -->|サーバー| R[registerUpdate]
    R --> S[replaceModules]
    S --> T[マニフェスト更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-1001 | バージョンチェック | config.version不一致で完全リロード | version受信時 |
| BR-1002 | 自己受け入れ | accept()でモジュール自身の更新を受け入れ | 明示的呼び出し時 |
| BR-1003 | disposeによるクリーンアップ | 更新前にdisposeコールバックを実行 | モジュール置換時 |
| BR-1004 | React Refresh境界 | isReactRefreshBoundaryでReactコンポーネントを検出 | React使用時 |
| BR-1005 | CSS ID形式 | 16文字の16進数 | CSSリロード時 |
| BR-1006 | エラー時フォールバック | HMRエラー時は完全リロード | エラー発生時 |

### 計算ロジック

**モジュール状態遷移**：
```
Pending -> Loaded (正常ロード)
Pending -> Error (ロードエラー)
Loaded -> Stale (更新検出)
Stale -> Pending (再ロード開始)
```

**React Refresh境界判定**：
- デフォルトエクスポートが関数
- 関数名が大文字で始まる
- または$refreshRegシンボルを持つ

## データベース操作仕様

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーケース | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Version mismatch | リロード | 設定ハッシュ不一致 | 完全リロード |
| Module not found | Error | 存在しないモジュールをインポート | エラー表示 |
| Async require | Error | TLAモジュールをrequire | エラーメッセージ表示 |
| HMR script load failed | リロード | スクリプトタグ読み込みエラー | 完全リロード |
| Accept callback error | リロード | acceptコールバックでエラー | 完全リロード |

### リトライ仕様

- WebSocket切断時は自動再接続（2.5秒待機）
- HMRエラー時は完全リロードにフォールバック

## トランザクション仕様

本機能はトランザクションを使用しない。

## パフォーマンス要件

- 差分バンドル: 変更モジュールのみ送信
- ソースマップ: 参照カウントによる遅延解放
- CSS: CSSStyleSheet APIによる即座適用

## セキュリティ考慮事項

- WebSocketはhttps使用時にwssにアップグレード
- ローカルホスト判定でデバッグ機能を制限

## 備考

- Vite互換: `vite:`プレフィックスのイベントを`bun:`に変換
- `import.meta.hot`は間接的に使用不可（直接呼び出しが必要）
- Firefox 2025時点では`import(a, b)`構文が未サポートのため遅延評価

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: クライアントHMRランタイムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hmr-runtime-client.ts | `src/bake/hmr-runtime-client.ts` | クライアントエントリーポイント |

**読解のコツ**:
- **1-2行目**: ファイルの目的（WebSocketでバンドラーと通信）
- **3-25行目**: 依存モジュールのインポート
- **37-63行目**: `performRouteReload`でサーバーサイドリロード
- **68-87行目**: `bun:hmr`シンボルでスクリプトタグからモジュール登録
- **90-202行目**: `handlers`オブジェクトでメッセージハンドラー定義
- **203-207行目**: `initWebSocket`でWebSocket初期化
- **239-254行目**: グローバルエラーハンドラー

#### Step 2: HMRモジュールローダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | hmr-module.ts | `src/bake/hmr-module.ts` | モジュールローダー実装 |

**主要処理フロー**:
1. **21-30行目**: `registry`/`serverManifest`/`ssrManifest`の定義
2. **37-49行目**: `State`列挙型でモジュール状態
3. **54-57行目**: `loadExports`でモジュールエクスポート取得
4. **72-270行目**: `HMRModule`クラスの実装
5. **172-192行目**: `accept`メソッドの実装
6. **213-215行目**: `dispose`メソッドの実装
7. **222-226行目**: `invalidate`メソッドの実装

#### Step 3: サーバーHMRランタイムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hmr-runtime-server.ts | `src/bake/hmr-runtime-server.ts` | サーバーエントリーポイント |

**主要処理フロー**:
- **13-18行目**: `RequestContext`型定義
- **24-51行目**: `Exports`インターフェース定義
- **54-160行目**: `server_exports.handleRequest`実装
- **161-197行目**: `server_exports.registerUpdate`実装

#### Step 4: CSS HMRを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | css-reloader.ts | `src/bake/client/css-reloader.ts` | CSSホットリロード |

**主要処理フロー**:
- **1-16行目**: CSSリロードの設計方針説明
- **18-25行目**: `cssStore`/`registeredLinkTags`データ構造
- **27-56行目**: `activateCss`/`deactivateCss`でスタイル制御
- **59-100行目**: `MutationObserver`でリンクタグ監視

#### Step 5: WebSocket通信を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | websocket.ts | `src/bake/client/websocket.ts` | WebSocket管理 |

**主要処理フロー**:
- **41-54行目**: `WebSocketWrapper`インターフェース
- **62-72行目**: `normalizeWebSocketURL`でプロトコル変換
- **74-100行目**: `initWebSocket`でWebSocket初期化

### プログラム呼び出し階層図

```
[クライアント]
    │
    ├─ hmr-runtime-client.ts
    │      │
    │      ├─ initWebSocket(handlers)
    │      │      └─ websocket.ts
    │      │             └─ new WebSocket(url)
    │      │
    │      ├─ handlers[MessageId.version]
    │      │      └─ バージョンチェック
    │      │
    │      ├─ handlers[MessageId.hot_update]
    │      │      ├─ editCssArray() [css-reloader.ts]
    │      │      ├─ editCssContent() [css-reloader.ts]
    │      │      └─ スクリプトタグ追加
    │      │             └─ bun:hmrシンボル
    │      │                    └─ replaceModules() [hmr-module.ts]
    │      │
    │      └─ performRouteReload()
    │             └─ onServerSideReload() or fullReload()
    │
    └─ hmr-module.ts
           │
           ├─ HMRModule.accept()
           ├─ HMRModule.dispose()
           ├─ HMRModule.invalidate()
           ├─ loadModuleSync()
           └─ loadModuleAsync()

[サーバー]
    │
    └─ hmr-runtime-server.ts
           │
           ├─ server_exports.handleRequest()
           │      ├─ loadExports(routerTypeMain)
           │      └─ serverRenderer(request, metadata)
           │
           └─ server_exports.registerUpdate()
                  ├─ replaceModules(modules)
                  └─ マニフェスト更新
```

### データフロー図

```
[DevServer]                    [WebSocket]                    [ブラウザ]
     │                              │                              │
     │  hot_update                  │                              │
     ├──────────────────────────────▶                              │
     │                              │  MessageId.hot_update        │
     │                              ├──────────────────────────────▶
     │                              │                              │
     │                              │                        [CSSリロード]
     │                              │                              │
     │                              │                        editCssContent()
     │                              │                              │
     │                              │                        [JSリロード]
     │                              │                              │
     │                              │                        スクリプトタグ
     │                              │                              │
     │                              │                        bun:hmrシンボル
     │                              │                              │
     │                              │                        replaceModules()
     │                              │                              │
     │                              │                        disposeコールバック
     │                              │                              │
     │                              │                        acceptコールバック
     │                              │                              │
     │                              │                        bun:afterUpdate
     │                              │                              │
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hmr-runtime-client.ts | `src/bake/hmr-runtime-client.ts` | ソース | クライアントHMRエントリーポイント |
| hmr-runtime-server.ts | `src/bake/hmr-runtime-server.ts` | ソース | サーバーHMRエントリーポイント |
| hmr-module.ts | `src/bake/hmr-module.ts` | ソース | HMRモジュールローダー |
| websocket.ts | `src/bake/client/websocket.ts` | ソース | WebSocket管理 |
| css-reloader.ts | `src/bake/client/css-reloader.ts` | ソース | CSSホットリロード |
| overlay.ts | `src/bake/client/overlay.ts` | ソース | エラーオーバーレイ |
| stack-trace.ts | `src/bake/client/stack-trace.ts` | ソース | スタックトレース処理 |
| data-view.ts | `src/bake/client/data-view.ts` | ソース | バイナリデータ読み取り |
| generated.ts | `src/bake/generated.ts` | ソース | MessageId定義 |
| HmrSocket.zig | `src/bake/DevServer/HmrSocket.zig` | ソース | サーバーサイドWebSocket |
