# 機能設計書 68-ソースマップ生成

## 概要

本ドキュメントは、webpackのソースマップ生成機能の設計を記述する。`SourceMapDevToolPlugin` と `EvalSourceMapDevToolPlugin` により、各種ソースマップ戦略を提供する。

### 本機能の処理概要

ソースマップ生成機能は、バンドルされたコードと元のソースコードの対応関係を示すソースマップを生成し、デバッグ時のソースコード追跡を可能にする機能を提供する。2つの主要プラグイン（SourceMapDevToolPluginとEvalSourceMapDevToolPlugin）により、異なるソースマップ戦略（外部ファイル、インライン、eval）をサポートする。

**業務上の目的・背景**：バンドルされたコードは、モジュール結合・最適化・難読化によって元のソースコードとは大きく異なる形になる。ソースマップは、ブラウザの開発者ツールでバンドルされたコードを元のソースコードにマッピングし、効率的なデバッグを可能にする。開発時の生産性向上と本番環境でのエラー追跡に不可欠な機能である。

**機能の利用シーン**：開発時のブラウザデバッグ、本番環境でのエラースタック追跡、CSSソースマップによるスタイル調査、eval方式による高速リビルドに利用される。

**主要な処理内容**：

**SourceMapDevToolPlugin**:
1. `processAssets` フック（PROCESS_ASSETS_STAGE_DEV_TOOLING）でアセットを処理する
2. 各アセットのソースマップを取得し、モジュール名のマッピングを構築する
3. 外部ソースマップファイルの生成、またはインラインData URLとしてアセットに埋め込む
4. キャッシュを活用し、変更のないアセットのソースマップ再生成をスキップする
5. ignoreListオプションによるソースマップのignoreList（Chrome DevTools対応）をサポートする
6. debugIdsオプションによるデバッグID付与をサポートする

**EvalSourceMapDevToolPlugin**:
1. JavascriptModulesPluginの `renderModuleContent` フックで個々のモジュールを処理する
2. 各モジュールのソースマップをData URL形式でeval()内に埋め込む
3. ソースキャッシュ（WeakMap）により同一ソースの再処理を回避する

**関連システム・外部連携**：webpack-sources（ConcatSource, RawSource）、Compilation、ProgressPlugin、ModuleFilenameHelpers、JavascriptModulesPluginと連携する。

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

## 関連画面

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

## 機能種別

データ連携 / コード生成（ソースマップ生成・埋め込み）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| filename | `string \| null \| false` | No | ソースマップファイル名テンプレート | falseでインライン |
| append | `string \| false \| Function` | No | ソースマップURLコメントのテンプレート | デフォルト: `//# sourceMappingURL=[url]` |
| module | `boolean` | No | モジュール単位のソースマップ有効化 | デフォルト: true |
| columns | `boolean` | No | カラム情報の有効化 | - |
| noSources | `boolean` | No | ソースコンテンツを含めない | - |
| sourceRoot | `string` | No | ソースルートパス | - |
| moduleFilenameTemplate | `string` | No | モジュールファイル名テンプレート | デフォルト: `webpack://[namespace]/[resourcePath]` |
| fallbackModuleFilenameTemplate | `string` | No | 衝突時のフォールバックテンプレート | デフォルト: `webpack://[namespace]/[resourcePath]?[hash]` |
| namespace | `string` | No | 名前空間 | - |
| test / include / exclude | `Rules` | No | 対象ファイルのフィルタ条件 | デフォルト: `.js/.mjs/.cjs/.css` |
| publicPath | `string` | No | ソースマップの公開パス | - |
| fileContext | `string` | No | ファイルコンテキスト | - |
| ignoreList | `Rules` | No | Chrome DevToolsのignoreListに追加するソース | - |
| debugIds | `boolean` | No | デバッグID付与 | - |

### 入力データソース

- webpack設定ファイルの `devtool` オプションまたはpluginsセクション
- コンパイルアセット（ソースマップを含むSource オブジェクト）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ソースマップファイル | `.map` | 外部ソースマップファイル（filenameが指定されている場合） |
| ソースマップURL | `string` | インラインData URLまたは外部ファイルへの参照コメント |
| 更新済みアセット | `Source` | ソースマップ参照コメントが追加されたアセット |

### 出力先

コンパイルアセットとして出力。外部ファイルの場合は補助ファイルとしてチャンクに関連付けられる。

## 処理フロー

### 処理シーケンス

```
SourceMapDevToolPlugin:
1. compilationフックでモジュールオプションを設定
   └─ SourceMapDevToolModuleOptionsPluginでuseSourceMap=trueを設定
2. processAssetsフック（STAGE_DEV_TOOLING）でアセットを処理
   └─ matchObjectで対象アセットをフィルタ
3. 各アセットのキャッシュを確認
   └─ キャッシュヒット時はキャッシュ結果を使用
4. ソースマップタスクを生成
   └─ getTaskForFile()でソースとソースマップを取得
5. モジュール名の衝突を解決
   └─ 重複時はfallbackテンプレートまたは"*"追加
6. ソースマップを適用
   └─ 外部ファイル: emitAsset + sourceMappingURLコメント追加
   └─ インライン: Data URLをアセットに埋め込み

EvalSourceMapDevToolPlugin:
1. compilationフックでモジュールオプションを設定
2. renderModuleContentフックで各モジュールを処理
   └─ NormalModule/ConcatenatedModuleのみ対象
3. ソースマップをData URL化してeval()内に埋め込み
```

### フローチャート

```mermaid
flowchart TD
    A[compilationフック] --> B[SourceMapDevToolModuleOptionsPlugin適用]
    B --> C[processAssets STAGE_DEV_TOOLING]
    C --> D[対象アセットのフィルタ]
    D --> E{キャッシュヒット?}
    E -->|Yes| F[キャッシュ結果を使用]
    E -->|No| G[getTaskForFile でソースマップ取得]
    G --> H[モジュール名マッピング構築]
    H --> I[名前衝突の解決]
    I --> J{filename 指定あり?}
    J -->|Yes| K[外部ファイルとして出力]
    J -->|No| L[インラインData URLとして埋め込み]
    K --> M[キャッシュに保存]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルト対象 | `.js`, `.mjs`, `.cjs`, `.css` ファイルがデフォルトの対象 | test未指定時 |
| BR-02 | 名前衝突解決 | 同名モジュールはfallbackテンプレート、さらに衝突する場合は"*"を付加して一意にする | ソースマップのsourcesに同名が存在する場合 |
| BR-03 | CSS対応 | CSSファイルのソースマップコメントは `/* */` 形式に変換される | ファイル拡張子が.cssの場合 |
| BR-04 | contenthash回避 | ソースマップとアセットがcontenthashを使用する場合、循環依存を避けるためにfileフィールドでハッシュを"x"に置換する | contenthash使用時 |
| BR-05 | ignoreList | 指定パターンに一致するソースをignoreListに追加する | ignoreListオプション指定時 |
| BR-06 | debugIds | ソースとファイル名からデバッグIDを生成し付与する | debugIdsオプション指定時 |

### 計算ロジック

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

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Error | 設定エラー | filename未指定かつappend=falseの場合 | filenameを指定するかappendを有効にする |
| Error | 設定エラー | filename未指定かつappendが関数の場合 | filenameを指定する |

### リトライ仕様

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

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

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

## パフォーマンス要件

- キャッシュ（LazyHashedEtag）により、変更のないアセットのソースマップ再生成をスキップする
- EvalSourceMapDevToolPluginはWeakMapキャッシュにより同一ソースの再処理を回避する
- ProgressPluginとの連携で処理進捗を報告する

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

- ソースマップは元のソースコードを含む場合があるため、本番環境ではソースマップを公開しないか、noSourcesオプションでソースコンテンツを除外すること
- ソースマップファイルのアクセス制御を適切に設定すること
- evalベースのソースマップはCSP（Content Security Policy）制限に注意が必要

## 備考

- webpackの `devtool` オプションに応じて、内部的にSourceMapDevToolPluginまたはEvalSourceMapDevToolPluginが自動的に適用される
- EvalSourceMapDevToolPluginはeval()内にソースマップを埋め込むため、リビルド速度が速いが本番環境には適さない
- SourceMapDevToolModuleOptionsPluginは両プラグインから共通で使用され、モジュールのuseSourceMapフラグを設定する

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SourceMapDevToolPlugin.js | `lib/SourceMapDevToolPlugin.js` | `SourceMapTask` 型（46-54行目）: asset, assetInfo, modules, source, file, sourceMap, cacheItem |

**読解のコツ**: SourceMapTaskはアセットごとに1つ生成され、ソースマップの生成・適用に必要な全データを保持する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SourceMapDevToolPlugin.js | `lib/SourceMapDevToolPlugin.js` | `apply()` メソッド（172-643行目）がエントリーポイント |
| 2-2 | EvalSourceMapDevToolPlugin.js | `lib/EvalSourceMapDevToolPlugin.js` | `apply()` メソッド（77-249行目）がEval版のエントリーポイント |

**主要処理フロー（SourceMapDevToolPlugin）**:
1. **191行目**: `compilation` フックでSourceMapDevToolModuleOptionsPluginを適用
2. **194-198行目**: `processAssets` フック（STAGE_DEV_TOOLING）にタップ
3. **210-217行目**: チャンクからファイルへのマッピングを構築
4. **232-359行目**: 各アセットのキャッシュ確認とタスク生成
5. **384-422行目**: モジュール名衝突の解決
6. **426-637行目**: ソースマップの適用（外部/インライン）

#### Step 3: モジュールオプション設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SourceMapDevToolModuleOptionsPlugin.js | `lib/SourceMapDevToolModuleOptionsPlugin.js` | `apply()` メソッド（28-49行目）- useSourceMap/useSimpleSourceMapの設定 |

#### Step 4: タスク生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SourceMapDevToolPlugin.js | `lib/SourceMapDevToolPlugin.js` | `getTaskForFile()` 関数（91-134行目）- ソースとソースマップの取得 |

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

```
SourceMapDevToolPlugin.apply(compiler)
    |
    +-- compiler.hooks.compilation.tap()
            |
            +-- SourceMapDevToolModuleOptionsPlugin.apply(compilation)
            |       +-- buildModule.tap() → module.useSourceMap = true
            |
            +-- compilation.hooks.processAssets.tapAsync(STAGE_DEV_TOOLING)
                    |
                    +-- [各アセット] getTaskForFile()
                    |       +-- asset.sourceAndMap() / asset.map()
                    |       +-- モジュール→ソース名マッピング構築
                    |
                    +-- モジュール名衝突解決
                    |       +-- fallbackテンプレート → "*"追加
                    |
                    +-- [外部ファイル] compilation.emitAsset(sourceMapFile)
                    |                compilation.updateAsset(file, asset + URLコメント)
                    |
                    +-- [インライン] compilation.updateAsset(file, asset + DataURL)

EvalSourceMapDevToolPlugin.apply(compiler)
    |
    +-- compiler.hooks.compilation.tap()
            |
            +-- JavascriptModulesPlugin.getCompilationHooks(compilation)
            |       +-- renderModuleContent.tap() → eval(source + DataURL)
            |
            +-- inlineInRuntimeBailout.tap()
            +-- render.tap() → devtoolWarning追加
```

### データフロー図

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

compilation.assets ─────▶ SourceMapDevToolPlugin ─────▶ .mapファイル (外部)
(Source + SourceMap)       (processAssets)                 または
                                                          DataURL (インライン)
                                                          + sourceMappingURLコメント付きアセット

module.source ──────────▶ EvalSourceMapDevToolPlugin ──▶ eval(code + SourceMap DataURL)
                          (renderModuleContent)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SourceMapDevToolPlugin.js | `lib/SourceMapDevToolPlugin.js` | ソース | 外部/インラインソースマップの生成（646行） |
| EvalSourceMapDevToolPlugin.js | `lib/EvalSourceMapDevToolPlugin.js` | ソース | eval方式のソースマップ生成（253行） |
| SourceMapDevToolModuleOptionsPlugin.js | `lib/SourceMapDevToolModuleOptionsPlugin.js` | ソース | モジュールのuseSourceMapフラグ設定 |
| ModuleFilenameHelpers.js | `lib/ModuleFilenameHelpers.js` | ソース | モジュールファイル名テンプレート処理 |
| generateDebugId.js | `lib/util/generateDebugId.js` | ソース | デバッグID生成ユーティリティ |
| SourceMapDevToolPlugin.json | `schemas/plugins/SourceMapDevToolPlugin.json` | 設定 | バリデーションスキーマ |
