# 機能設計書 18-重複チャンク除去

## 概要

本ドキュメントは、webpackの重複チャンク除去機能について、MergeDuplicateChunksPlugin / RemoveEmptyChunksPlugin / RemoveParentModulesPluginによる冗長なチャンクの削除の設計と実装の詳細を記述する。

### 本機能の処理概要

重複チャンク除去は、コード分割後に生成されたチャンクの中から、同一モジュールセットを持つ重複チャンク、モジュールを持たない空チャンク、親チャンクに既に含まれるモジュールを検出して除去する最適化機能である。

**業務上の目的・背景**：SplitChunksPlugin等のコード分割処理後、複数のチャンクが全く同じモジュール群を含む「重複チャンク」が生成される場合がある。また、最適化の結果としてモジュールが移動されて空になったチャンクや、親チャンクグループに既に含まれるモジュールが子チャンクにも含まれるケースがある。これらの冗長性を除去することで、不要なHTTPリクエストやバンドルサイズの増大を防止する。

**機能の利用シーン**：マルチエントリポイント構成での重複チャンク統合、最適化後の空チャンク除去、親子チャンクグループ間の冗長モジュール除去。

**主要な処理内容**：
1. **MergeDuplicateChunksPlugin**: 同一モジュールセットを持つチャンクを検出してマージ
2. **RemoveEmptyChunksPlugin**: モジュール数0でランタイムもエントリモジュールも持たないチャンクを削除
3. **RemoveParentModulesPlugin**: 親チャンクグループに含まれるモジュールを子チャンクから除去

**関連システム・外部連携**：ChunkGraph、ModuleGraph、Compilation.chunks、チャンクグループの親子関係と連携する。

**権限による制御**：optimization.mergeDuplicateChunks、optimization.removeEmptyChunks設定で制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | Page A (many-pages) | 補助機能 | 冗長な共有チャンクの検出・除去 |
| 2 | Page B (many-pages) | 補助機能 | 冗長な共有チャンクの検出・除去 |
| 3 | Page C (many-pages) | 補助機能 | 冗長な共有チャンクの検出・除去 |
| 4 | Page D (many-pages) | 補助機能 | 冗長な共有チャンクの検出・除去 |

## 機能種別

最適化処理 / 冗長性除去

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| optimization.mergeDuplicateChunks | `boolean` | No | 重複チャンクマージの有効/無効 | デフォルト: true |
| optimization.removeEmptyChunks | `boolean` | No | 空チャンク削除の有効/無効 | デフォルト: true |
| MergeDuplicateChunksPlugin.stage | `number` | No | 実行ステージ | デフォルト: STAGE_BASIC |

### 入力データソース

- Compilationのchunksコレクション
- ChunkGraphのモジュール-チャンクマッピング
- チャンクグループの親子関係

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| マージ後のチャンク | `Chunk` | 重複がマージされたチャンク |
| 削除されたチャンク | `Set<Chunk>` | 重複/空チャンクの削除 |
| 除去されたモジュール参照 | - | 親と重複するモジュールの切断 |

### 出力先

Compilationのchunksコレクション

## 処理フロー

### 処理シーケンス

```
1. MergeDuplicateChunksPlugin (STAGE_BASIC)
   └─ 各チャンクに対して同一モジュールセットを持つチャンクを検出
   └─ ランタイム条件・エントリモジュール条件を確認
   └─ エクスポート使用状態の同等性を確認
   └─ integrateChunksでマージ
2. RemoveParentModulesPlugin (STAGE_BASIC)
   └─ 全チャンクグループの親子関係を走査
   └─ 各チャンクグループの利用可能モジュール（親から継承）を計算
   └─ 子チャンクから親に既に含まれるモジュールを除去
3. RemoveEmptyChunksPlugin (STAGE_BASIC + STAGE_ADVANCED)
   └─ モジュール数0かつランタイムなしかつエントリモジュールなしのチャンクを削除
```

### フローチャート

```mermaid
flowchart TD
    A[optimizeChunks STAGE_BASIC] --> B[MergeDuplicateChunksPlugin]
    B --> C[各チャンクをスキャン]
    C --> D{同一モジュールセット<br>のチャンクあり?}
    D -->|Yes| E{ランタイム・エントリ<br>条件OK?}
    E -->|Yes| F[integrateChunks]
    E -->|No| C
    D -->|No| G[RemoveParentModulesPlugin]
    F --> C
    G --> H[親から継承可能な<br>モジュールを計算]
    H --> I[子チャンクから<br>重複モジュールを除去]
    I --> J[RemoveEmptyChunksPlugin]
    J --> K{モジュール数0?}
    K -->|Yes| L{ランタイムなし<br>エントリなし?}
    L -->|Yes| M[チャンク削除]
    L -->|No| N[保持]
    K -->|No| N
    M --> O[完了]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 同一モジュール数チェック | モジュール数が異なるチャンクは重複候補から除外 | MergeDuplicateChunksPlugin |
| BR-02 | ランタイム同等性 | ランタイムが異なる場合はエクスポート使用状態の同等性を追加確認 | MergeDuplicateChunksPlugin |
| BR-03 | エントリモジュール保護 | エントリモジュールを持つチャンクはマージ対象外 | MergeDuplicateChunksPlugin |
| BR-04 | ランタイムチャンク保護 | hasRuntime()がtrueのチャンクは空でも削除しない | RemoveEmptyChunksPlugin |
| BR-05 | 親モジュール継承 | 子は全親の共通部分（交差）のみを継承と見なす | RemoveParentModulesPlugin |
| BR-06 | 2回実行 | RemoveEmptyChunksPluginはSTAGE_BASICとSTAGE_ADVANCEDの両方で実行 | RemoveEmptyChunksPlugin |

### 計算ロジック

- **RemoveParentModulesPlugin**: bigintビットマスクでモジュールを表現し、AND演算で交差を計算
  - `getOrCreateModuleMask(mod)`で各モジュールに一意のビット位置を割り当て
  - チャンクのモジュールマスク: 所属モジュールのビットOR
  - 利用可能モジュール: 親チャンクグループのマスクのAND（交差）
  - 除去対象: `chunkMask & availableModulesMask`

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

該当なし

## エラー処理

### エラーケース一覧

該当なし

### リトライ仕様

該当なし

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

該当なし

## パフォーマンス要件

- MergeDuplicateChunksPlugin: `notDuplicates`セットで処理済みチャンクを記録し、二重チェックを防止
- RemoveParentModulesPlugin: bigintビットマスクによるO(1)のモジュール比較で大規模プロジェクトでも効率的

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

該当なし

## 備考

- MergeDuplicateChunksPluginのstageオプションで実行タイミングをカスタマイズ可能
- RemoveParentModulesPluginは`getModulesFromMask`ジェネレータでビットマスクからモジュール群を復元

---

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

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

### 推奨読解順序

#### Step 1: MergeDuplicateChunksPluginを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MergeDuplicateChunksPlugin.js | `lib/optimize/MergeDuplicateChunksPlugin.js` | 全139行。重複チャンクの検出とマージ |

**主要処理フロー**:
1. **54行目**: notDuplicatesセットで処理済みを記録
2. **57-93行目**: 各チャンクについて、同じモジュール数のチャンクをpossibleDuplicatesとして収集
3. **61-93行目**: 最初のモジュールのチャンク群から候補を構築し、後続モジュールで絞り込む
4. **100-106行目**: ランタイムが異なる場合、isEquallyUsedで全モジュールの使用状態を比較
5. **122-124行目**: canChunksBeIntegrated→integrateChunks→chunks.deleteでマージ実行

**読解のコツ**: possibleDuplicatesの構築が段階的に行われる。最初のモジュールで候補を初期化し、2番目以降のモジュールで候補を絞り込む（交差）。全モジュールが一致するチャンクのみが最終候補に残る。

#### Step 2: RemoveEmptyChunksPluginを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RemoveEmptyChunksPlugin.js | `lib/optimize/RemoveEmptyChunksPlugin.js` | 全61行。シンプルな空チャンク削除 |

**主要処理フロー**:
- **29-38行目**: handler関数で3条件（モジュール数0、ランタイムなし、エントリモジュールなし）を検証
- **35-36行目**: disconnectChunk→chunks.deleteで削除
- **42-55行目**: STAGE_BASICとSTAGE_ADVANCEDの両方で同じハンドラを登録

#### Step 3: RemoveParentModulesPluginを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RemoveParentModulesPlugin.js | `lib/optimize/RemoveParentModulesPlugin.js` | 全217行。bigintビットマスクによる効率的な親モジュール除去 |

**主要処理フロー**:
1. **82-102行目**: getOrCreateModuleMaskで各モジュールにユニークなビット位置を割り当て
2. **107-114行目**: チャンクごとのモジュールマスク（ビットOR）を構築
3. **117-127行目**: チャンクグループごとのマスク（チャンクマスクのOR）を構築
4. **129-142行目**: エントリポイントのavailableModulesを初期化（ZERO_BIGINT）
5. **144-177行目**: キューベースの幅優先探索で利用可能モジュールを伝播（親のAND交差）
6. **180-203行目**: 各チャンクで利用可能モジュールとの交差を計算し、重複モジュールを切断
7. **38-60行目**: getModulesFromMaskジェネレータでビットマスクからモジュールを復元

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

```
Compilation.hooks.optimizeChunks
    |
    +-- STAGE_BASIC
    |       +-- MergeDuplicateChunksPlugin
    |       |       +-- chunkGraph.getChunkModulesIterable()
    |       |       +-- chunkGraph.getModuleChunksIterable()
    |       |       +-- chunkGraph.isModuleInChunk()
    |       |       +-- chunkGraph.canChunksBeIntegrated()
    |       |       +-- chunkGraph.integrateChunks()
    |       |       +-- moduleGraph.getExportsInfo().isEquallyUsed()
    |       |
    |       +-- RemoveParentModulesPlugin
    |       |       +-- chunkGraph.getChunkModulesIterable()
    |       |       +-- chunkGraph.disconnectChunkAndModule()
    |       |
    |       +-- RemoveEmptyChunksPlugin
    |               +-- chunkGraph.getNumberOfChunkModules()
    |               +-- chunkGraph.disconnectChunk()
    |               +-- compilation.chunks.delete()
    |
    +-- STAGE_ADVANCED
            +-- RemoveEmptyChunksPlugin (再実行)
```

### データフロー図

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

チャンク群 -----------------> MergeDuplicateChunksPlugin   +--> マージされたチャンク
                                   |                        |
チャンクグループ親子関係 --> RemoveParentModulesPlugin     +--> 冗長モジュール除去
                                   |                        |
最適化後のチャンク --------> RemoveEmptyChunksPlugin       +--> 空チャンク削除
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MergeDuplicateChunksPlugin.js | `lib/optimize/MergeDuplicateChunksPlugin.js` | ソース | 重複チャンクマージ |
| RemoveEmptyChunksPlugin.js | `lib/optimize/RemoveEmptyChunksPlugin.js` | ソース | 空チャンク削除 |
| RemoveParentModulesPlugin.js | `lib/optimize/RemoveParentModulesPlugin.js` | ソース | 親モジュール除去 |
| ChunkGraph.js | `lib/ChunkGraph.js` | ソース | チャンク-モジュール管理 |
| ModuleGraph.js | `lib/ModuleGraph.js` | ソース | エクスポート使用状態 |
| OptimizationStages.js | `lib/OptimizationStages.js` | 定数 | 最適化ステージ定数 |
| runtime.js | `lib/util/runtime.js` | ユーティリティ | ランタイム比較関数 |
