# 機能設計書 60-DotenvPlugin

## 概要

本ドキュメントは、webpackにおけるDotenvPlugin機能の設計仕様を記述する。.envファイルから環境変数を読み込み、指定プレフィックスでフィルタリングした上で、DefinePlugin経由で`process.env.*`および`import.meta.env.*`として注入する機能である。

### 本機能の処理概要

**業務上の目的・背景**：フロントエンド開発において、環境変数をバンドルに注入することは一般的なパターンである。従来は外部パッケージ（dotenv + DefinePlugin手動連携）が必要であったが、DotenvPluginはこの機能を組み込みプラグインとして提供する。Viteの`.env`ファイル読み込み仕様に類似した設計で、モード別.envファイル（.env, .env.local, .env.[mode], .env.[mode].local）の読み込み、変数展開、プレフィックスフィルタリングを一括で処理する。

**機能の利用シーン**：フロントエンドアプリケーションでのAPI URLやフィーチャーフラグの環境別設定、`.env`ファイルベースの環境変数管理、Viteからの移行プロジェクトに利用される。

**主要な処理内容**：
1. .envファイルテンプレートに基づいて読み込み対象ファイルを決定する
2. 各.envファイルを読み込み・パースし、変数を統合する（後のファイルが優先）
3. dotenv-expand互換の変数展開を行う（`${VAR}`形式の展開）
4. process.envの値を優先し、パースされた値をフォールバックとする
5. プレフィックスフィルタ（デフォルト: "WEBPACK_"）で注入対象を絞り込む
6. DefinePluginに委譲して`process.env.KEY`と`import.meta.env.KEY`の両形式で注入する
7. ファイルシステムスナップショットによるキャッシュとウォッチモード対応を行う

**関連システム・外部連携**：DefinePlugin（No.57）に内部的に委譲する。FileSystemInfoを使用してスナップショットベースのキャッシュ管理を行う。dotenv/dotenv-expandのパースロジックを内蔵（ポーティング）している。

**権限による制御**：特になし。webpack設定のpluginsで設定する。

## 関連画面

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

## 機能種別

構成管理 / 環境変数注入

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| prefix | string \| string[] | No | 注入対象の環境変数プレフィックス（デフォルト: "WEBPACK_"） | DotenvPluginOptionsスキーマ |
| template | string[] | No | .envファイルテンプレート（デフォルト: [".env", ".env.local", ".env.[mode]", ".env.[mode].local"]） | DotenvPluginOptionsスキーマ |
| dir | string \| false | No | .envファイルのディレクトリ（デフォルト: compiler.context） | DotenvPluginOptionsスキーマ |

### 入力データソース

- .envファイル群: テンプレートパターンに基づいて読み込まれるファイル
- process.env: 既存の環境変数（.envファイルより優先）
- compiler.options.mode: モード文字列（"development", "production"等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| definitions | Record<string, string> | DefinePlugin用の定義マップ（process.env.KEYおよびimport.meta.env.KEY） |

### 出力先

DefinePluginの定義として注入され、バンドル内の該当識別子が定数値に置換される

## 処理フロー

### 処理シーケンス

```
1. DotenvPlugin.apply() → DefinePlugin インスタンスを生成（空定義で初期化）
   └─ DefinePlugin.apply(compiler) も呼び出す
2. compiler.hooks.beforeCompile にタップ（Promise版）
   a. _loadEnv() で .env ファイルを読み込み
      └─ キャッシュチェック → スナップショット検証 → 新規読み込み/パース
   b. _getEnv() でプレフィックスフィルタリング + 変数展開
   c. envToDefinitions() で DefinePlugin 定義形式に変換
   d. definePlugin.definitions に設定
3. compiler.hooks.compilation にタップ
   └─ スナップショットのファイル依存を compilation.fileDependencies に追加
4. コンパイル時に DefinePlugin が定義を注入
```

### フローチャート

```mermaid
flowchart TD
    A[DotenvPlugin.apply] --> B[DefinePlugin生成・apply]
    B --> C[compiler.hooks.beforeCompile]
    C --> D{キャッシュ有効?}
    D -->|Yes| E{スナップショット有効?}
    E -->|Yes| F[キャッシュから取得]
    E -->|No| G[新規読み込み]
    D -->|No| G
    G --> H[_getEnvFilesForMode: 対象ファイル決定]
    H --> I[_getParsed: 各ファイルをパース・統合]
    I --> J[expand: 変数展開]
    J --> K[_getEnv: プレフィックスフィルタ]
    K --> L[envToDefinitions: 定義形式変換]
    L --> M[definePlugin.definitions更新]
    F --> M
    M --> N[compiler.hooks.compilation]
    N --> O[fileDependencies追加]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-60-01 | テンプレートパターン | デフォルトテンプレート: [".env", ".env.local", ".env.[mode]", ".env.[mode].local"]。[mode]はcompiler.options.modeで置換される | 常時 |
| BR-60-02 | ファイル優先順位 | 後のファイルが前のファイルの値を上書きする。process.envの値は.envファイルの値より優先される | 常時 |
| BR-60-03 | プレフィックスフィルタ | デフォルトプレフィックス"WEBPACK_"で始まる環境変数のみ注入される | prefix設定 |
| BR-60-04 | 変数展開 | `${VAR}`形式の変数参照を展開する。`:+`, `+`, `:-`, `-`演算子をサポート | expand関数 |
| BR-60-05 | 自己参照検出 | 変数展開時に循環参照を検出し、デフォルト値にフォールバックする | expandValue関数内のseenセット |
| BR-60-06 | 両形式注入 | `process.env.KEY`と`import.meta.env.KEY`の両方の形式で定義を注入する | envToDefinitions関数 |
| BR-60-07 | スナップショットキャッシュ | FileSystemInfoのスナップショットでファイル変更を検出し、変更がなければキャッシュを使用する | _loadEnv関数 |
| BR-60-08 | .envパース仕様 | dotenvライブラリ互換のパースロジック。export接頭辞対応、引用符（シングル/ダブル/バッククォート）対応、改行エスケープ | parse関数 |
| BR-60-09 | dir=falseで無効化 | dir=falseの場合、.envファイルの読み込みをスキップし、空の定義を使用する | dir設定 |

### 計算ロジック

.envパース正規表現: `/^\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?$/gm`。変数展開正規表現: `/(?<!\\)\$\{([^{}]+)\}|(?<!\\)\$([a-z_]\w*)/gi`。

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

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

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

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

該当なし（ファイルシステム読み込みとインメモリ処理）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ENOENT | .envファイルが存在しない場合 | missingDependenciesに追加して正常続行（存在しないのは正常） |
| - | ValidationError | オプションがスキーマに違反した場合 | createSchemaValidationエラー |
| - | Error | スナップショット作成失敗 | reject()でPromiseを拒否 |

### リトライ仕様

該当なし。ファイル不在時はmissingDependenciesに追加して続行する。

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

該当なし（非同期ファイル読み込みとインメモリ処理）

## パフォーマンス要件

- FileSystemInfoスナップショットによるキャッシュで、リビルド時の.envファイル再読み込みを最小化
- ItemCacheFacadeでパース結果をキャッシュし、スナップショットが有効な間は再パース不要

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

- プレフィックスフィルタにより、意図しない環境変数がバンドルに含まれることを防止する
- デフォルトプレフィックス"WEBPACK_"は、NODE_ENVやDB_PASSWORD等のセンシティブ変数を自動的に除外する
- プレフィックスを空文字列にすると全環境変数が注入されるため注意が必要

## 備考

- dotenvライブラリ（https://github.com/motdotla/dotenv）のパースロジックをwebpackに内蔵（ポーティング）
- dotenv-expand（https://github.com/motdotla/dotenv-expand）の変数展開ロジックも内蔵
- Viteの.envファイル仕様に類似した設計（getEnvFilesForMode等）
- 全468行の中規模プラグインである

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | DotenvPlugin.js | `lib/DotenvPlugin.js` | デフォルトオプション、型定義、正規表現を確認する |

**読解のコツ**: DotenvPluginはdotenvとdotenv-expandのポーティング部分と、webpack固有のプラグインロジック部分に分かれる。まずパースと変数展開の仕組みを理解し、次にwebpack連携部分を読む。

- **22-25行目**: DEFAULT_OPTIONS - prefix: "WEBPACK_"、template: [".env", ".env.local", ".env.[mode]", ".env.[mode].local"]
- **29-30行目**: LINE正規表現 - .envファイルのパース用（dotenvから移植）

#### Step 2: .envファイルのパースと変数展開

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DotenvPlugin.js | `lib/DotenvPlugin.js` | parse関数、expandValue関数、expand関数 |

**主要処理フロー**:
- **56-93行目**: `parse`関数 - .envファイルの内容をキー・値ペアにパース
  - **68-90行目**: 正規表現マッチングと引用符処理
  - **84-87行目**: ダブルクォートの場合は`\n`を改行に展開
- **102-104行目**: `_resolveEscapeSequences`関数 - `\$`を`$`に変換
- **114-173行目**: `expandValue`関数 - `${VAR}`形式の変数展開
  - **117行目**: 展開用正規表現
  - **123行目**: seenセットで自己参照検出
  - **131-134行目**: `:+`, `+`, `:-`, `-`演算子のパース
- **181-212行目**: `expand`関数 - process.envを優先しつつ変数展開を実行

#### Step 3: 環境変数のフィルタリングと定義変換

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | DotenvPlugin.js | `lib/DotenvPlugin.js` | envToDefinitions関数、_getEnv関数 |

**主要処理フロー**:
- **219-229行目**: `envToDefinitions`関数 - `process.env.KEY`と`import.meta.env.KEY`の両形式で定義
- **427-449行目**: `_getEnv`関数 - プレフィックスフィルタリング + process.env優先

#### Step 4: プラグインのエントリーポイント

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | DotenvPlugin.js | `lib/DotenvPlugin.js` | DotenvPluginクラスのconstructor()とapply()メソッド |

**主要処理フロー**:
1. **231-238行目**: コンストラクタ - バリデーション + デフォルト値マージ
2. **244-245行目**: apply()内でDefinePluginインスタンスを空定義で生成
3. **246-255行目**: prefixesとdirの解決
4. **260-262行目**: キャッシュの取得
5. **264-272行目**: `compiler.hooks.beforeCompile`（Promise版）で.env読み込み → DefinePlugin定義設定
6. **274-279行目**: `compiler.hooks.compilation`でスナップショット依存をcompilationに追加
7. **281行目**: `definePlugin.apply(compiler)` でDefinePluginを適用

#### Step 5: ファイル読み込みとキャッシュ管理

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | DotenvPlugin.js | `lib/DotenvPlugin.js` | _loadEnv, _getParsed, _getEnvFilesForMode関数 |

**主要処理フロー**:
- **293-304行目**: `_getEnvFilesForMode` - テンプレートの[mode]を置換してファイルパス配列を生成
- **314-353行目**: `_getParsed` - 各.envファイルを非同期読み込み・パース・統合
- **362-418行目**: `_loadEnv` - FileSystemInfoスナップショットによるキャッシュ管理

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

```
DotenvPlugin.apply(compiler)
    │
    ├─ new DefinePlugin({}) ──▶ definePlugin.apply(compiler)
    │
    ├─ compiler.hooks.beforeCompile (Promise)
    │      ├─ _loadEnv(compiler, itemCache, dir)
    │      │      ├─ itemCache.getPromise() ──▶ キャッシュチェック
    │      │      ├─ fileSystemInfo.checkSnapshotValid() ──▶ スナップショット検証
    │      │      └─ _getParsed(fs, dir, mode)
    │      │             ├─ _getEnvFilesForMode(fs, dir, mode)
    │      │             │      └─ template.map(pattern => ...)
    │      │             └─ _loadFile(fs, filePath)
    │      │                    └─ fs.readFile()
    │      ├─ _getEnv(prefixes, parsed)
    │      │      └─ expand({ parsed, processEnv })
    │      │             └─ expandValue()
    │      └─ envToDefinitions(env)
    │             └─ definePlugin.definitions = {...}
    │
    └─ compiler.hooks.compilation
           └─ compilation.fileDependencies.addAll(snapshot.getFileIterable())
```

### データフロー図

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

.env ─────────────────────▶ parse() ───────────────────────▶ { KEY: "value" }
.env.local ───────────────▶ parse() ───────────────────────▶ { KEY: "override" }
.env.production ──────────▶ parse() ───────────────────────▶ { KEY: "prod_value" }
                                                                    │
                              expand() ──────────────────────▶ 変数展開済み
                                                                    │
process.env ──────────────▶ _getEnv(prefixes) ─────────────▶ フィルタ済み環境変数
                                                                    │
                              envToDefinitions() ──────────▶ { "process.env.KEY": "\"value\"",
                                                                "import.meta.env.KEY": "\"value\"" }
                                                                    │
                              DefinePlugin.definitions ────▶ バンドル内の定数置換
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DotenvPlugin.js | `lib/DotenvPlugin.js` | ソース | .env環境変数注入プラグイン本体（468行） |
| DefinePlugin.js | `lib/DefinePlugin.js` | ソース | 定数定義・置換プラグイン（DotenvPluginが委譲先として使用） |
| FileSystemInfo.js | `lib/FileSystemInfo.js` | ソース | ファイルシステムスナップショット管理 |
| fs.js | `lib/util/fs.js` | ソース | ファイルシステム抽象化（InputFileSystem） |
| WebpackOptions.json | `schemas/WebpackOptions.json` | スキーマ | DotenvPluginOptionsの定義元 |
