# 機能設計書 51-決定的ID生成

## 概要

本ドキュメントは、webpackにおける決定的ID生成機能（DeterministicModuleIdsPlugin / DeterministicChunkIdsPlugin）の設計仕様を記述する。モジュールおよびチャンクに対して、再現可能かつ安定的な数値IDを割り当てることで、ビルド間のキャッシュ効率を最大化する機能である。

### 本機能の処理概要

**業務上の目的・背景**：webpackのビルドにおいて、モジュールやチャンクに割り当てられるIDが毎回変わると、ブラウザキャッシュが無効化され、ユーザーのページ読み込み速度が劣化する。決定的ID生成は、モジュールの識別子（ファイルパス等）に基づいてハッシュ計算を行い、ソースコードに変更がない限り同一のIDを再生成することで、長期キャッシュの安定化とデプロイ効率の向上を実現する。webpackのproductionモードでデフォルトで有効となる。

**機能の利用シーン**：本番環境向けビルドにおいて、コード変更がないモジュール・チャンクのIDを安定させ、CDNやブラウザのキャッシュヒット率を維持する場合に利用される。`optimization.moduleIds: 'deterministic'`および`optimization.chunkIds: 'deterministic'`として設定される。

**主要な処理内容**：
1. コンパイル済みモジュール/チャンクの一覧を取得し、既に使用済みのIDを収集する
2. 各モジュール/チャンクの識別子（相対パス）からnumberHash関数を用いて数値IDを算出する
3. 衝突が発生した場合はソルト値をインクリメントして再計算する
4. ID空間を最適化するため、アイテム数の20倍のレンジを確保し、5%以下の充填率を目指す
5. 算出されたIDをChunkGraphまたはChunkオブジェクトに設定する

**関連システム・外部連携**：ChunkGraph APIを通じてモジュールIDを設定する。numberHash（lib/util/numberHash.js）およびcomparators（lib/util/comparators.js）に依存する。

**権限による制御**：特になし。webpack設定のoptimization.moduleIds / optimization.chunkIds により有効化・無効化を制御する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はUIを持たない内部最適化処理であり、直接関連する画面は存在しない |

## 機能種別

計算処理 / 最適化処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| context | string | No | モジュール識別子を相対化するベースディレクトリ | 未指定時はcompiler.contextを使用 |
| maxLength | number | No | ID桁数の初期最大長（デフォルト: 3） | 正の整数 |
| salt | number | No | ハッシュソルト値（デフォルト: 0） | 非負整数 |
| fixedLength | boolean | No | 最大長を固定するか（デフォルト: false） | - |
| failOnConflict | boolean | No | 衝突時にエラーをスローするか（デフォルト: false） | - |
| test | function | No | モジュールフィルタ関数（DeterministicModuleIdsPluginのみ） | - |

### 入力データソース

- compilation.modules: コンパイル対象の全モジュール一覧
- compilation.chunks: コンパイル対象の全チャンク一覧
- compilation.usedModuleIds / compilation.usedChunkIds: 既に予約済みのID

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| moduleId | number | 各モジュールに割り当てられた決定的数値ID |
| chunkId | number | 各チャンクに割り当てられた決定的数値ID |

### 出力先

ChunkGraphにsetModuleId()で設定、またはChunkオブジェクトのid/idsプロパティに直接設定

## 処理フロー

### 処理シーケンス

```
1. compilation.hooks.moduleIds / chunkIds フック発火
   └─ コンパイル最適化フェーズで呼び出される
2. 使用済みID一覧を収集（getUsedModuleIdsAndModules / getUsedChunkIds）
   └─ 既にIDが設定されているモジュール/チャンクのIDをSetに格納
3. 対象アイテムの識別子を生成（getFullModuleName / getFullChunkName）
   └─ contextを基準にした相対パスを算出
4. 最適なID空間レンジを決定
   └─ items.length * 20 + extraSpace を目標とし、10^maxLength から開始
5. ソート済みアイテムに対してnumberHashでIDを算出
   └─ 衝突時はソルトをインクリメントして再計算
6. IDをChunkGraph/Chunkに設定
   └─ usedIdsにも追加して重複を防止
```

### フローチャート

```mermaid
flowchart TD
    A[moduleIds/chunkIdsフック発火] --> B[使用済みID収集]
    B --> C[対象モジュール/チャンク取得]
    C --> D[識別子からコンテキスト相対パス算出]
    D --> E[最適レンジ決定: items*20]
    E --> F{レンジは十分か?}
    F -->|No| G[レンジを10倍に拡大]
    G --> F
    F -->|Yes| H[各アイテムにnumberHashでID算出]
    H --> I{ID衝突?}
    I -->|Yes| J[ソルトをインクリメントして再計算]
    J --> I
    I -->|No| K[IDを設定]
    K --> L{failOnConflict かつ衝突あり?}
    L -->|Yes| M[エラーをスロー]
    L -->|No| N[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | ID空間レンジ | アイテム数の20倍のレンジを確保し、充填率を5%以下に維持する | 常時 |
| BR-51-02 | 衝突解決 | ID衝突時はソルト値をインクリメントして再ハッシュする | fixedLengthでない場合 |
| BR-51-03 | 既存ID尊重 | 既にIDが設定されているモジュール/チャンクは処理対象外とする | 常時 |
| BR-51-04 | 決定的ソート | 処理前にアイテムをPreOrderIndexまたは識別子で安定ソートする | 常時 |
| BR-51-05 | CSS例外 | CSSモジュールはチャンクに属さなくてもID割り当て対象とする | module.buildMeta.isCSSModuleがtrue |

### 計算ロジック

ID算出: `numberHash(identifier + saltCounter, range)` を繰り返し、usedIdsに存在しないIDが見つかるまでsaltCounterをインクリメントする。レンジは `10^maxLength` から開始し、`items.length * 20 + extraSpace` に達するまで10倍ずつ拡大する。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | 本機能はデータベースを使用しない |

### テーブル別操作詳細

該当なし（インメモリ処理のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | failOnConflict=trueで衝突が発生した場合 | maxLengthを増やすかsaltを変更する |

### リトライ仕様

リトライは行わない。衝突解決はソルトインクリメントで内部的に自動処理される。failOnConflictが有効な場合はエラーメッセージに推奨対策が含まれる。

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

該当なし（同期的なインメモリ処理）

## パフォーマンス要件

- numberHashは軽量な数値ハッシュ関数であり、大量モジュールに対しても高速に動作する
- 充填率5%以下の設計により衝突は稀であり、再ハッシュのオーバーヘッドは最小限

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

特になし。内部ID生成処理であり、外部入力を直接処理しない。

## 備考

- productionモードのデフォルト設定: `optimization.moduleIds: 'deterministic'`, `optimization.chunkIds: 'deterministic'`
- developmentモードでは通常 `named` IDが使用される
- SyncModuleIdsPluginと組み合わせることで、ビルド間のID同期も可能

---

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

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

### 推奨読解順序

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

まず、ID生成に使用されるヘルパー関数群の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | IdHelpers.js | `lib/ids/IdHelpers.js` | ID生成に使用される全ヘルパー関数の定義を確認する |

**読解のコツ**: IdHelpers.jsは多数のヘルパー関数をエクスポートしている。`assignDeterministicIds`、`getUsedModuleIdsAndModules`、`getUsedChunkIds`、`getFullModuleName`、`getFullChunkName`が本機能の中核である。

- **26-31行目**: `getHash`関数 - 文字列からハッシュダイジェストを生成する基本関数
- **58-59行目**: `requestToId`関数 - リクエストパスをID文字列に変換
- **119-120行目**: `getFullModuleName`関数 - モジュールの完全識別子を相対パスで返す
- **194-206行目**: `getFullChunkName`関数 - チャンクの完全名を返す（名前付きチャンクはそのまま、無名チャンクはルートモジュールから生成）
- **232-261行目**: `getUsedModuleIdsAndModules`関数 - 使用済みIDのSetと未割り当てモジュールの配列を返す
- **269-286行目**: `getUsedChunkIds`関数 - 使用済みチャンクIDのSetを返す
- **372-412行目**: `assignDeterministicIds`関数 - 決定的ID割り当ての中核ロジック

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DeterministicModuleIdsPlugin.js | `lib/ids/DeterministicModuleIdsPlugin.js` | モジュールID割り当てプラグインのapply()メソッド |
| 2-2 | DeterministicChunkIdsPlugin.js | `lib/ids/DeterministicChunkIdsPlugin.js` | チャンクID割り当てプラグインのapply()メソッド |

**主要処理フロー（DeterministicModuleIdsPlugin）**:
1. **47行目**: `compilation.hooks.moduleIds`フックにタップ
2. **49-56行目**: context, maxLength, failOnConflict, fixedLength, saltの各オプションを解決
3. **59-62行目**: `getUsedModuleIdsAndModules`で既存ID・未割り当てモジュールを取得
4. **63-85行目**: `assignDeterministicIds`を呼び出してID割り当てを実行
5. **86-92行目**: failOnConflictモードで衝突があればエラーをスロー

**主要処理フロー（DeterministicChunkIdsPlugin）**:
1. **41行目**: `compilation.hooks.chunkIds`フックにタップ
2. **50行目**: `getUsedChunkIds`で既存IDを取得
3. **51-67行目**: `assignDeterministicIds`でチャンクIDを割り当て

#### Step 3: ID割り当てアルゴリズムの詳細

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | IdHelpers.js | `lib/ids/IdHelpers.js` | `assignDeterministicIds`関数（372-412行目）の詳細 |

**主要処理フロー**:
- **382行目**: アイテムをcomparatorでソート（決定的順序の保証）
- **385-388行目**: 最適レンジ計算 = min(items.length * 20 + extraSpace, MAX_SAFE_INTEGER)
- **390-401行目**: rangesパラメータから開始し、optimalRangeに達するまでexpandFactor倍ずつ拡大
- **403-411行目**: 各アイテムに対してnumberHashでIDを算出、衝突時はソルトインクリメント

#### Step 4: SyncModuleIdsPlugin（補完）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SyncModuleIdsPlugin.js | `lib/ids/SyncModuleIdsPlugin.js` | ビルド間のID同期（read/create/merge/updateモード） |

**主要処理フロー**:
- **57-78行目**: readRecordsフックでJSONファイルからID対応表を読み込み
- **80-93行目**: emitRecordsフックでID対応表をJSONファイルに書き出し
- **100-125行目**: reviveModulesフックで前回ビルドのIDを復元

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

```
Compiler
    │
    ├─ compilation.hooks.moduleIds
    │      └─ DeterministicModuleIdsPlugin.apply()
    │             ├─ IdHelpers.getUsedModuleIdsAndModules()
    │             │      └─ chunkGraph.getModuleId()
    │             ├─ IdHelpers.getFullModuleName()
    │             │      └─ identifier.makePathsRelative()
    │             └─ IdHelpers.assignDeterministicIds()
    │                    └─ numberHash()
    │
    └─ compilation.hooks.chunkIds
           └─ DeterministicChunkIdsPlugin.apply()
                  ├─ IdHelpers.getUsedChunkIds()
                  ├─ IdHelpers.getFullChunkName()
                  │      └─ identifier.makePathsRelative()
                  └─ IdHelpers.assignDeterministicIds()
                         └─ numberHash()
```

### データフロー図

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

compilation.modules ───────▶ getUsedModuleIdsAndModules() ──▶ [usedIds, modules]
                                                                    │
compiler.context ──────────▶ getFullModuleName() ──────────▶ identifier(相対パス)
                                                                    │
identifier + salt ─────────▶ numberHash(ident, range) ─────▶ 数値ID
                                                                    │
                              assignDeterministicIds() ─────▶ chunkGraph.setModuleId()
                                                                    │
compilation.chunks ────────▶ getUsedChunkIds() ────────────▶ [usedIds]
                                                                    │
chunk.name / rootModules ──▶ getFullChunkName() ───────────▶ identifier
                                                                    │
identifier + salt ─────────▶ numberHash(ident, range) ─────▶ chunk.id / chunk.ids
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DeterministicModuleIdsPlugin.js | `lib/ids/DeterministicModuleIdsPlugin.js` | ソース | モジュール向け決定的ID割り当てプラグイン |
| DeterministicChunkIdsPlugin.js | `lib/ids/DeterministicChunkIdsPlugin.js` | ソース | チャンク向け決定的ID割り当てプラグイン |
| IdHelpers.js | `lib/ids/IdHelpers.js` | ソース | ID生成ヘルパー関数群 |
| SyncModuleIdsPlugin.js | `lib/ids/SyncModuleIdsPlugin.js` | ソース | ビルド間ID同期プラグイン |
| numberHash.js | `lib/util/numberHash.js` | ソース | 数値ハッシュ関数 |
| comparators.js | `lib/util/comparators.js` | ソース | ソート用比較関数群 |
| identifier.js | `lib/util/identifier.js` | ソース | パス相対化ユーティリティ（makePathsRelative） |
| ChunkGraph.js | `lib/ChunkGraph.js` | ソース | モジュールID設定API（setModuleId） |
