# 機能設計書 34-HMRプラグイン

## 概要

本ドキュメントは、webpackにおけるHot Module Replacement（HMR）プラグインの機能について記述する。HotModuleReplacementPluginがモジュールの差分更新・ランタイムパッチ適用を実現し、開発中にブラウザをリロードせずにモジュール単位の更新を可能にする。

### 本機能の処理概要

HMRプラグインは、ソースコードの変更をブラウザやNode.jsランタイムに即座に反映させるための中核機能である。変更されたモジュールのみを差分更新し、アプリケーション状態を維持したまま開発を続行できる。

**業務上の目的・背景**：フロントエンド開発において、コード変更のたびにブラウザをフルリロードすると、UIの状態（フォーム入力値、スクロール位置、モーダル状態等）が失われ開発効率が低下する。HMRにより変更モジュールのみを差し替えることで、アプリケーション状態を保持したままの高速な開発サイクルを実現する。

**機能の利用シーン**：webpack-dev-server等の開発サーバーと組み合わせて使用される。開発者がソースコードを編集・保存した際に、変更されたモジュールのホットアップデートマニフェストとチャンクが生成され、クライアントに配信・適用される。

**主要な処理内容**：
1. module.hot.accept() / module.hot.decline() / import.meta.webpackHot.accept() 等のHMR APIの解析
2. モジュールハッシュの比較による変更モジュールの検出
3. ホットアップデートマニフェスト（.hot-update.json）の生成
4. ホットアップデートチャンク（.hot-update.js / .hot-update.css）の生成
5. HotModuleReplacementRuntimeModuleによるランタイムコードの生成
6. レコード（records）を使用したビルド間の状態追跡

**関連システム・外部連携**：webpack-dev-server、ブラウザのEventSourceまたはWebSocket経由でホットアップデート通知を受信する。HMRランタイム（hot/配下）と連携する。

**権限による制御**：特になし。プラグインの適用有無で機能を制御する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | HMRプラグインは開発時のビルドインフラであり、特定の画面に直接関連しない |

## 機能種別

ビルド時処理 / ランタイムコード生成 / 差分更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| module.hot.accept(deps, callback) | - | No | HMR受け入れ宣言（module.hot API） | 第1引数は文字列または文字列配列 |
| module.hot.decline(deps) | - | No | HMR拒否宣言 | 第1引数は文字列または文字列配列 |
| import.meta.webpackHot.accept(deps, callback) | - | No | HMR受け入れ宣言（ESM API） | 第1引数は文字列または文字列配列 |
| import.meta.webpackHot.decline(deps) | - | No | HMR拒否宣言（ESM API） | 第1引数は文字列または文字列配列 |

### 入力データソース

- 前回ビルドのレコード（records: hash, hotIndex, chunkModuleHashes, chunkHashes等）
- 現在のコンパイル結果（モジュールハッシュ、チャンク情報）
- JavaScriptパーサが解析したmodule.hot / import.meta.webpackHot API呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ホットアップデートマニフェスト | JSON | 更新されたチャンクID、削除されたチャンクID、削除されたモジュールIDの一覧 |
| ホットアップデートチャンク | JavaScript/CSS | 更新されたモジュールのコードを含むチャンクファイル |
| HMRランタイムコード | string | モジュール差し替え処理のランタイムJavaScriptコード |

### 出力先

ホットアップデートファイル群がoutput.pathに出力される。ランタイムコードはバンドル内に含まれる。

## 処理フロー

### 処理シーケンス

```
1. HMR API解析（compilation時）
   ├─ module.hot.accept/declineの解析・依存登録
   └─ import.meta.webpackHot.accept/declineの解析・依存登録
2. 依存ファクトリ・テンプレート登録
   ├─ ModuleHotAcceptDependency / ModuleHotDeclineDependency
   └─ ImportMetaHotAcceptDependency / ImportMetaHotDeclineDependency
3. レコードからの変更検出（fullHash時）
   ├─ chunkModuleHashesの比較
   └─ updatedModulesセットの構築
4. ホットアップデートマニフェスト生成（processAssets時）
   ├─ 更新チャンクID一覧の算出
   ├─ 削除チャンクID一覧の算出
   └─ 削除モジュール一覧の算出
5. ホットアップデートチャンク生成
   └─ HotUpdateChunkとして更新モジュールのコードを出力
6. HMRランタイムコード生成
   └─ HotModuleReplacementRuntimeModuleの追加
```

### フローチャート

```mermaid
flowchart TD
    A[コンパイル開始] --> B[HMR APIの解析]
    B --> C[依存ファクトリ・テンプレート登録]
    C --> D{前回レコード存在?}
    D -->|No| E[初回ビルド: レコード生成]
    D -->|Yes| F[モジュールハッシュ比較]
    F --> G[updatedModules検出]
    G --> H{更新モジュールあり?}
    H -->|No| I[ホットアップデートなし]
    H -->|Yes| J[ホットアップデートマニフェスト生成]
    J --> K[ホットアップデートチャンク生成]
    K --> L[アセット出力]
    E --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | モジュール連結抑制 | HMR API使用モジュールはmoduleConcatenationBailout="Hot Module Replacement"が設定され、スコープホイスティングが無効化される | module.hot / import.meta.webpackHot使用時 |
| BR-02 | strictModuleErrorHandling | HMR有効時、output.strictModuleErrorHandlingが未設定の場合trueに設定 | プラグイン適用時 |
| BR-03 | ランタイム要件 | module.hot使用時にRuntimeGlobals.moduleが必須要件として追加される | HMR API使用時 |
| BR-04 | 子コンパイル除外 | compilation.compiler !== compilerの場合、子コンパイルにはHMRを適用しない | compilation.tap時 |
| BR-05 | ホットインデックス管理 | records.hotIndexによりビルド間のホットアップデートシーケンスを管理 | レコード記録時 |

### 計算ロジック

モジュール変更検出: 前回ビルドのchunkModuleHashes[`${chunkId}|${moduleIdentifier}`]と現在のcodeGenerationResults.getHash(module, chunk.runtime)を比較する。ハッシュが異なる場合、そのモジュールはupdatedModulesセットに追加される。

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

### 操作別データベース影響一覧

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | TypeError | parserがJavascriptParserインスタンスでない | getParserHooksでTypeErrorをスロー |
| - | WebpackError | HMR処理中の内部エラー | コンパイルエラーとして報告 |

### リトライ仕様

HMRの適用失敗時はランタイム側（hot/dev-server.js等）でフルリロードへのフォールバックが行われる。

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

該当なし（ファイルベースのレコード管理）

## パフォーマンス要件

- 差分ビルドにより変更モジュールのみを再コンパイル
- レコード（records）によるビルド間の状態保持で増分更新を実現
- nonCodeGeneratedModulesの追跡により不要なコード生成を回避

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

- HMRは開発環境専用であり、本番環境での使用は推奨されない
- ホットアップデートファイルにはソースコードが含まれるため、外部公開サーバーでの使用に注意

## 備考

- HMRプラグインはmodule.hot（CommonJS形式）とimport.meta.webpackHot（ESM形式）の両方のAPIをサポート
- SyncBailHookのhotAcceptCallback / hotAcceptWithoutCallbackによりHMR受け入れ処理のカスタマイズが可能
- TupleSetによりモジュールとチャンクの組を効率的に管理

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

HMRプラグインが使用する型定義とデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 63-83行目: 型定義（HMRJavascriptParserHooks, HotUpdateMainContentByRuntime等） |
| 1-2 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 66-69行目: HMRJavascriptParserHooksの型（hotAcceptCallback, hotAcceptWithoutCallback） |

**読解のコツ**: HMRプラグインはWebpackの中でも大規模なプラグインである。records（前回ビルドの状態）を介した差分検出がHMR実現の鍵であるため、recordsのデータ構造を先に理解すること。

#### Step 2: エントリーポイントを理解する

apply()メソッドの全体構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 116-120行目: apply()の開始、strictModuleErrorHandling設定 |
| 2-2 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 128-187行目: createAcceptHandler - module.hot.accept()の解析ロジック |
| 2-3 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 194-228行目: createDeclineHandler - module.hot.decline()の解析ロジック |
| 2-4 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 252-281行目: applyModuleHot - CommonJS HMR API登録 |
| 2-5 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 287-313行目: applyImportMetaHot - ESM HMR API登録 |

**主要処理フロー**:
1. **118-119行目**: strictModuleErrorHandlingをtrueに設定
2. **128-187行目**: createAcceptHandlerでmodule.hot.accept()の引数を解析し、依存関係を登録
3. **142-143行目**: moduleConcatenationBailoutを"Hot Module Replacement"に設定
4. **315-320行目**: compilation.tapでHMRプラグインを子コンパイルを除外して登録

#### Step 3: 変更検出を理解する

fullHashフックでのモジュール変更検出ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 367-393行目: record.tapでレコードを保存 |
| 3-2 | HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | 400-449行目: fullHash.tapでモジュールハッシュを比較し変更を検出 |

**主要処理フロー**:
- **367-393行目**: compilation.hooks.record でhash, hotIndex, chunkModuleHashes, chunkHashesをレコードに記録
- **408-417行目**: getModuleHash関数でモジュールのハッシュ値を取得
- **430-448行目**: 前回のchunkModuleHashesと比較し、変更があればupdatedModulesに追加

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

```
HotModuleReplacementPlugin.apply(compiler)
    |
    +-- compiler.hooks.compilation.tap
    |      |
    |      +-- compilation.dependencyFactories.set (×4: Accept/Decline × module.hot/import.meta)
    |      +-- compilation.dependencyTemplates.set (×4)
    |      +-- compilation.hooks.record.tap → レコード保存
    |      +-- compilation.hooks.fullHash.tap → 変更モジュール検出
    |      +-- compilation.hooks.processAssets.tap → マニフェスト/チャンク生成
    |      +-- compilation.hooks.additionalTreeRuntimeRequirements.tap → HMRランタイム追加
    |
    +-- normalModuleFactory.hooks.parser.tap
           |
           +-- applyModuleHot(parser) → module.hot.accept/decline解析
           +-- applyImportMetaHot(parser) → import.meta.webpackHot.accept/decline解析
```

### データフロー図

```
[入力]                        [処理]                              [出力]

前回records ──────────┐
                      v
ソースコード ──────> JavascriptParser                       ──> 依存関係
  (module.hot.accept)   |                                      (HMR依存)
                        v
現在のhash ────────> HotModuleReplacementPlugin            ──> ホットアップデート
前回のhash            |                                        マニフェスト(.json)
                      +-> 変更モジュール検出                    ホットアップデート
                      +-> マニフェスト生成                      チャンク(.js/.css)
                      +-> チャンク生成
                      +-> HotModuleReplacementRuntimeModule ──> ランタイムコード
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| HotModuleReplacementPlugin.js | `lib/HotModuleReplacementPlugin.js` | ソース | HMRの中核プラグイン（本機能の主要ファイル） |
| HotModuleReplacementRuntimeModule.js | `lib/hmr/HotModuleReplacementRuntimeModule.js` | ソース | HMRランタイムコードの生成 |
| HotUpdateChunk.js | `lib/HotUpdateChunk.js` | ソース | ホットアップデートチャンクの定義 |
| ModuleHotAcceptDependency.js | `lib/dependencies/ModuleHotAcceptDependency.js` | ソース | module.hot.accept依存の定義 |
| ModuleHotDeclineDependency.js | `lib/dependencies/ModuleHotDeclineDependency.js` | ソース | module.hot.decline依存の定義 |
| ImportMetaHotAcceptDependency.js | `lib/dependencies/ImportMetaHotAcceptDependency.js` | ソース | import.meta.webpackHot.accept依存の定義 |
| ImportMetaHotDeclineDependency.js | `lib/dependencies/ImportMetaHotDeclineDependency.js` | ソース | import.meta.webpackHot.decline依存の定義 |
| ConstDependency.js | `lib/dependencies/ConstDependency.js` | ソース | 定数置換依存 |
| TupleSet.js | `lib/util/TupleSet.js` | ソース | タプルセットユーティリティ |
