# 機能設計書 65-NormalModuleReplacementPlugin

## 概要

本ドキュメントは、webpackの組込みプラグインである `NormalModuleReplacementPlugin` の機能設計を記述する。`NormalModuleReplacementPlugin` は、指定モジュールを別のモジュールに置換するプラグインである。

### 本機能の処理概要

NormalModuleReplacementPluginは、正規表現でマッチしたモジュールリクエストを別のモジュールに置換する機能を提供する。NormalModuleFactoryの `beforeResolve` と `afterResolve` の2つのフックにタップし、モジュール解決の前後で置換を行う。

**業務上の目的・背景**：環境やビルドターゲットに応じて異なるモジュール実装を使い分ける必要がある場合がある。例えば、ブラウザ向けビルドではNode.js固有のモジュールを代替実装に差し替えたり、テスト時にモックモジュールを注入したりする用途で利用される。

**機能の利用シーン**：プラットフォーム固有モジュールの差し替え、テスト用モックの注入、レガシーモジュールの新実装への移行、ビルドターゲットに応じた実装切り替えに利用される。

**主要な処理内容**：
1. NormalModuleFactoryの `beforeResolve` フックでモジュールリクエスト文字列を置換する（解決前の置換）
2. NormalModuleFactoryの `afterResolve` フックで解決済みのリソースパスを置換する（解決後の置換）
3. 置換先は文字列（モジュールパス）または関数（resolveDataを受け取る）で指定可能
4. afterResolve時の文字列置換では、絶対パスでない場合は元リソースのディレクトリからの相対パスとして解決する

**関連システム・外部連携**：NormalModuleFactory、InputFileSystem（パス解決用）と連携する。

**権限による制御**：権限による制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能に直接関連する画面はない |

## 機能種別

データ連携 / コード変換（モジュール置換）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| resourceRegExp | `RegExp` | Yes | 置換対象モジュールを特定する正規表現 | 正規表現型 |
| newResource | `string \| ((resolveData: ResolveData) => void)` | Yes | 置換先のモジュールパスまたは置換関数 | 文字列または関数型 |

### 入力データソース

- webpack設定ファイルのpluginsセクション
- NormalModuleFactoryの解決データ（request, createData.resource）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result.request | `string` | beforeResolve時に置換されたリクエスト文字列 |
| createData.resource | `string` | afterResolve時に置換されたリソースパス |

### 出力先

NormalModuleFactoryの解決データが直接変更される（in-place mutation）。

## 処理フロー

### 処理シーケンス

```
1. apply()でNormalModuleFactoryフックにタップ
   └─ normalModuleFactory.hooks.beforeResolve と afterResolve に登録
2. beforeResolveフックでリクエスト文字列を検査
   └─ resourceRegExpがresult.requestにマッチした場合、置換を実行
3. afterResolveフックで解決済みリソースパスを検査
   └─ resourceRegExpがcreateData.resourceにマッチした場合、置換を実行
4. 置換先がstring型の場合
   └─ beforeResolve: result.requestを直接置換
   └─ afterResolve: 絶対パスならそのまま、相対パスなら元リソースからの相対で解決
5. 置換先がfunction型の場合
   └─ resolveDataオブジェクトを引数として関数を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[NormalModuleFactory] --> B[beforeResolve フック]
    B --> C{resourceRegExp.test request?}
    C -->|No| D[スキップ]
    C -->|Yes| E{newResource が関数?}
    E -->|Yes| F[newResource resolveData を呼び出し]
    E -->|No| G[result.request = newResource]

    A --> H[afterResolve フック]
    H --> I{resourceRegExp.test resource?}
    I -->|No| J[スキップ]
    I -->|Yes| K{newResource が関数?}
    K -->|Yes| L[newResource resolveData を呼び出し]
    K -->|No| M{絶対パス?}
    M -->|Yes| N[createData.resource = newResource]
    M -->|No| O[元リソースのdirnameからjoinで解決]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 二段階置換 | beforeResolve（リクエスト文字列）とafterResolve（解決済みパス）の両方で置換が行われる | 正規表現がマッチした場合 |
| BR-02 | 絶対パス判定 | 置換先が"/"で始まるか、2文字目が":"の場合は絶対パスとして扱う | afterResolveでの文字列置換時 |
| BR-03 | 相対パス解決 | 絶対パスでない場合、元リソースのディレクトリからの相対パスとして解決する | afterResolveでの文字列置換時 |
| BR-04 | 関数置換 | 関数型の場合、resolveDataオブジェクトをin-placeで変更可能 | newResourceが関数型の場合 |

### 計算ロジック

特筆すべき計算ロジックは存在しない。

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

データベース操作は存在しない。

## エラー処理

### エラーケース一覧

エラー処理は存在しない。不正なパスが指定された場合、後続のモジュール解決で失敗する。

### リトライ仕様

リトライ仕様は存在しない。

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

トランザクション仕様は存在しない。

## パフォーマンス要件

各モジュール解決に対して正規表現テストが実行されるが、パフォーマンスへの影響は極めて小さい。

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

- 意図しないモジュールの置換が発生しないよう、正規表現パターンを厳密に指定する必要がある
- 置換先モジュールが信頼できるソースであることを確認する必要がある

## 備考

- beforeResolveとafterResolveの2段階で置換が行われるため、解決前のリクエスト文字列と解決後のファイルパスの両方にマッチする可能性がある
- 関数型の置換では、resolveDataの任意のプロパティを変更できるため、柔軟な置換が可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | NormalModuleReplacementPlugin.js | `lib/NormalModuleReplacementPlugin.js` | `resourceRegExp`と`newResource`の2つのインスタンスプロパティ |

**読解のコツ**: コンストラクタ（24-27行目）はシンプルで、2つの引数をそのまま保持するのみ。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NormalModuleReplacementPlugin.js | `lib/NormalModuleReplacementPlugin.js` | `apply()` メソッド（34-71行目）がエントリーポイント |

**主要処理フロー**:
1. **37行目**: `compiler.hooks.normalModuleFactory` にタップ
2. **38-46行目**: `beforeResolve` でリクエスト文字列を置換
3. **47-70行目**: `afterResolve` で解決済みリソースパスを置換
4. **56-67行目**: afterResolveでの絶対パス/相対パス分岐

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

```
NormalModuleReplacementPlugin.apply(compiler)
    |
    +-- compiler.hooks.normalModuleFactory.tap()
            |
            +-- nmf.hooks.beforeResolve.tap()
            |       |
            |       +-- resourceRegExp.test(result.request)
            |       +-- [string] result.request = newResource
            |       +-- [function] newResource(result)
            |
            +-- nmf.hooks.afterResolve.tap()
                    |
                    +-- resourceRegExp.test(createData.resource)
                    +-- [string] createData.resource = join/newResource
                    +-- [function] newResource(result)
```

### データフロー図

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

result.request ─────▶ beforeResolve ──▶ 置換されたrequest
                     (正規表現マッチ)

createData.resource ─▶ afterResolve ──▶ 置換されたresource
                     (正規表現マッチ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NormalModuleReplacementPlugin.js | `lib/NormalModuleReplacementPlugin.js` | ソース | 本機能のメインプラグイン（75行） |
| fs.js | `lib/util/fs.js` | ソース | dirname, join関数の提供 |
