# 機能設計書 83-ファイルシステム抽象化

## 概要

本ドキュメントは、webpackにおけるファイルシステム抽象化機能の設計を記述する。InputFileSystem、OutputFileSystem、IntermediateFileSystem、WatchFileSystemの4つのインターフェースを型定義（JSDoc typedef）として提供し、ファイル操作のクロスプラットフォーム互換性を確保するユーティリティ関数群を提供する。

### 本機能の処理概要

ファイルシステム抽象化機能は、webpackがファイル読み書き・ディレクトリ操作・パス操作を行う際の統一的なインターフェースを定義し、OS間・環境間の差異を吸収するユーティリティを提供する。

**業務上の目的・背景**：webpackはNode.jsのネイティブfsモジュールだけでなく、メモリファイルシステム（webpack-dev-server等）やカスタムファイルシステムも利用する。これらを統一的に扱うため、ファイルシステムのインターフェースを抽象化し、パス操作のPOSIX/Windows互換関数を提供する必要がある。enhanced-resolve等の外部ライブラリも同じインターフェースを通じてファイル操作を行う。

**機能の利用シーン**：
- Compilerがソースファイルを読み込む際のInputFileSystem利用
- ビルド成果物をディスクに書き込む際のOutputFileSystem利用
- キャッシュファイルの読み書きにIntermediateFileSystem利用
- ファイル監視にWatchFileSystem利用
- パス結合・相対パス計算・ディレクトリ取得等のクロスプラットフォーム操作

**主要な処理内容**：
1. InputFileSystem型定義: readFile、readdir、stat、readlink、realpath等の読み取り専用操作
2. OutputFileSystem型定義: mkdir、writeFile、stat、unlink等の書き込み操作
3. IntermediateFileSystem型定義: InputFileSystem + OutputFileSystem + 追加操作（mkdirSync、createWriteStream、open、read、close、rename）
4. WatchFileSystem型定義: watchメソッドによるファイル監視
5. relative関数: ファイルシステムのrelativeメソッドまたはpath.posix/path.win32を使用した相対パス計算
6. join関数: ファイルシステムのjoinメソッドまたはpath.posix/path.win32を使用したパス結合
7. dirname関数: ファイルシステムのdirnameメソッドまたはpath.posix/path.win32を使用した親ディレクトリ取得
8. mkdirp関数: 再帰的ディレクトリ作成（ENOENT/EEXISTの適切なハンドリング）
9. mkdirpSync関数: mkdirpの同期版
10. readJson関数: JSONファイルの読み込みとパース
11. lstatReadlinkAbsolute関数: シンボリックリンクの解決（リトライ機能付き）
12. isAbsolute関数: POSIX/Windows両方の絶対パス判定

**関連システム・外部連携**：Node.js fsモジュール、pathモジュール、watchpackライブラリ

**権限による制御**：特になし。

## 関連画面

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

## 機能種別

ユーティリティ（ファイルシステムインターフェース定義・パス操作ヘルパー）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| fs | InputFileSystem \| OutputFileSystem | Yes | ファイルシステムインスタンス | join/relative/dirnameメソッドの有無で分岐 |
| rootPath | string | Yes（relative/join） | ルートパスまたは起点パス | POSIX絶対パスまたはWindows絶対パス |
| targetPath | string | Yes（relative） | 対象パス | - |
| filename | string | Yes（join） | 結合するファイル名 | - |
| absPath | string | Yes（dirname） | 絶対パス | POSIX絶対パスまたはWindows絶対パス |
| p | string | Yes（mkdirp/readJson） | ファイルまたはディレクトリの絶対パス | - |

### 入力データソース

- webpack Compilerインスタンスが保持するinputFileSystem、outputFileSystem、intermediateFileSystem
- webpack設定で指定されたパス（context、output.path等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 相対パス | string | relative関数の結果 |
| 結合パス | string | join関数の結果 |
| 親ディレクトリ | string | dirname関数の結果 |
| JSONオブジェクト | JsonObject | readJson関数の結果 |
| パス判定 | boolean | isAbsolute関数の結果 |

### 出力先

呼び出し元に返却。webpack内部の各所でパス計算やファイル操作に使用される。

## 処理フロー

### 処理シーケンス

```
1. relative(fs, rootPath, targetPath)
   ├─ fs.relativeがあれば使用
   ├─ path.posix.isAbsolute(rootPath) → path.posix.relative
   ├─ path.win32.isAbsolute(rootPath) → path.win32.relative
   └─ どれにも該当しない → Error throw

2. join(fs, rootPath, filename)
   ├─ fs.joinがあれば使用
   ├─ path.posix.isAbsolute(rootPath) → path.posix.join
   ├─ path.win32.isAbsolute(rootPath) → path.win32.join
   └─ どれにも該当しない → Error throw

3. dirname(fs, absPath)
   ├─ fs.dirnameがあれば使用
   ├─ path.posix.isAbsolute(absPath) → path.posix.dirname
   ├─ path.win32.isAbsolute(absPath) → path.win32.dirname
   └─ どれにも該当しない → Error throw

4. mkdirp(fs, p, callback)
   ├─ fs.mkdir(p) 試行
   ├─ ENOENT → dirname(p)を再帰的にmkdirp → 再度mkdir(p)
   ├─ EEXIST → 成功として扱う
   └─ その他エラー → callback(err)

5. readJson(fs, p, callback)
   ├─ fs.readJsonがあれば直接使用
   └─ fs.readFile → JSON.parse

6. lstatReadlinkAbsolute(fs, p, callback)
   ├─ fs.lstatでシンボリックリンク判定
   ├─ シンボリックリンク → fs.readlink → join(dirname(p), target)
   └─ エラー時は最大3回リトライ
```

### フローチャート

```mermaid
flowchart TD
    A[パス操作関数呼び出し] --> B{fs にメソッドあり?}
    B -->|Yes| C[fs のメソッドを使用]
    B -->|No| D{POSIX 絶対パス?}
    D -->|Yes| E[path.posix を使用]
    D -->|No| F{Windows 絶対パス?}
    F -->|Yes| G[path.win32 を使用]
    F -->|No| H[Error throw]

    I[mkdirp 呼び出し] --> J[fs.mkdir 試行]
    J --> K{エラー?}
    K -->|ENOENT| L[親ディレクトリを再帰的に作成]
    L --> M[fs.mkdir 再試行]
    K -->|EEXIST| N[成功]
    K -->|なし| N
    K -->|その他| O[エラーコールバック]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-83-1 | FSメソッド優先 | ファイルシステムインスタンスがjoin/relative/dirnameメソッドを持つ場合はそれを優先使用する | パス操作関数呼び出し時 |
| BR-83-2 | OS自動検出 | パスのPOSIX/Windows判定はpath.posix.isAbsolute/path.win32.isAbsoluteで自動判定される | FSメソッドがない場合 |
| BR-83-3 | EEXIST許容 | mkdirpではEEXISTエラーを成功として扱い、並行処理での競合を吸収する | mkdirp実行時 |
| BR-83-4 | readJsonフォールバック | fs.readJsonメソッドがない場合はreadFile + JSON.parseにフォールバックする | readJson実行時 |
| BR-83-5 | シンボリックリンクリトライ | lstatReadlinkAbsoluteではreadlinkエラー時に最大3回リトライし、シンボリックリンクからファイルへの変更を検出する | lstatReadlinkAbsolute実行時 |
| BR-83-6 | isAbsolute両対応 | isAbsolute関数はPOSIX・Windows両方の絶対パス形式を判定する | パス判定時 |

### 計算ロジック

特になし。

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

本機能にデータベース操作は含まれない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | パスがPOSIXでもWindowsでもなく、FSにメソッドもない場合 | 有効な絶対パスまたはカスタムFSメソッドを提供する |
| ENOENT | NodeJS.ErrnoException | mkdirpで親ディレクトリが存在しない場合 | 再帰的に親ディレクトリを作成する（自動処理） |
| EEXIST | NodeJS.ErrnoException | mkdirpで既にディレクトリが存在する場合 | 成功として扱う（自動処理） |
| - | Error | readJsonでJSON.parseが失敗した場合 | コールバックにエラーを通知 |

### リトライ仕様

lstatReadlinkAbsoluteでは、readlinkエラー時にdoStatからdoReadLinkへのリトライを最大3回（i=3から開始し--i > 0の間）行う。これはシンボリックリンクが通常ファイルに変更された場合のレースコンディションに対応する。

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

トランザクション処理は不要。ただし、mkdirpはEEXISTを許容することで並行処理の競合を安全に処理する。

## パフォーマンス要件

特になし。パス操作は軽量な文字列処理であり、ファイルI/Oはユーザ提供のfsインスタンスに委譲される。

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

- パストラバーサル攻撃の防止は、ファイルシステム抽象化層の上位（webpack設定のバリデーション等）で行われる
- シンボリックリンクの解決はlstatReadlinkAbsoluteが担うが、循環リンクの検出は行わない

## 備考

- webpack 6ではIntermediateFileSystemExtrasのメソッドの一部がInputFileSystem/OutputFileSystemに移動される予定（コード内TODOコメント参照）
- FSインスタンスのjoin/relative/dirnameメソッドは非標準であり、webpack固有の拡張。enhanced-resolveが提供する拡張FSで使用される

---

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

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

### 推奨読解順序

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

ファイルシステムインターフェースの型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | fs.js | `lib/util/fs.js` | 286-305行目: InputFileSystem型定義。readFile/readdir/stat/readlink/realpath/readJson等の読み取り操作 |
| 1-2 | fs.js | `lib/util/fs.js` | 356-369行目: OutputFileSystem型定義。mkdir/writeFile/stat/unlink等の書き込み操作 |
| 1-3 | fs.js | `lib/util/fs.js` | 460-467行目: IntermediateFileSystemExtras型定義。mkdirSync/createWriteStream/open/read/close/rename |
| 1-4 | fs.js | `lib/util/fs.js` | 469行目: IntermediateFileSystem = InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras |
| 1-5 | fs.js | `lib/util/fs.js` | 371-374行目: WatchFileSystem型定義。watchメソッド |

**読解のコツ**: fs.jsはJSDocの型定義（typedef）が大部分を占める。TypeScriptの型定義に近い記法で、Node.jsのfsモジュールのシグネチャを模倣している。IStats/IBigIntStats/Dirent等の補助型も定義されている。

#### Step 2: ユーティリティ関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | fs.js | `lib/util/fs.js` | 477-488行目: relative関数。fs.relative優先、なければPOSIX/Windows自動判定 |
| 2-2 | fs.js | `lib/util/fs.js` | 496-507行目: join関数。relative関数と同じパターン |
| 2-3 | fs.js | `lib/util/fs.js` | 514-525行目: dirname関数。relative関数と同じパターン |
| 2-4 | fs.js | `lib/util/fs.js` | 665-666行目: isAbsolute関数。posix.isAbsolute OR win32.isAbsolute |

**主要処理フロー**:
- **477-488行目**: relative(fs, rootPath, targetPath) - 3段階フォールバック: fs.relative → path.posix.relative → path.win32.relative → Error
- **496-507行目**: join(fs, rootPath, filename) - 同上パターン
- **514-525行目**: dirname(fs, absPath) - 同上パターン

#### Step 3: ディレクトリ作成処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | fs.js | `lib/util/fs.js` | 533-569行目: mkdirp関数。非同期再帰的ディレクトリ作成 |
| 3-2 | fs.js | `lib/util/fs.js` | 576-595行目: mkdirpSync関数。同期版 |

**主要処理フロー**:
- **534行目**: fs.mkdir(p)を試行
- **536-559行目**: ENOENT → dirname(p)に対してmkdirpを再帰呼び出し → 再度mkdir(p)（EEXISTは成功扱い）
- **560-562行目**: EEXIST → callback()で成功
- **564行目**: その他のエラー → callback(err)

#### Step 4: JSON読み込みとシンボリックリンク解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | fs.js | `lib/util/fs.js` | 603-620行目: readJson関数。fs.readJson優先、なければreadFile+JSON.parse |
| 4-2 | fs.js | `lib/util/fs.js` | 628-659行目: lstatReadlinkAbsolute関数。シンボリックリンクの絶対パス解決 |

**主要処理フロー**:
- **629行目**: リトライカウンタ i=3 で初期化
- **630-641行目**: doReadLink内部関数。readlinkエラー時にi-1回までdoStatを再試行
- **642-656行目**: doStat内部関数。lstatでシンボリックリンクならdoReadLink、それ以外はstatsを返す
- **639行目**: readlink成功時、joinとdirnameを使ってターゲットの絶対パスを算出

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

```
lib/util/fs.js (エクスポート関数群)
    |
    +-- relative(fs, rootPath, targetPath)
    |   +-- fs.relative() || path.posix.relative() || path.win32.relative()
    |
    +-- join(fs, rootPath, filename)
    |   +-- fs.join() || path.posix.join() || path.win32.join()
    |
    +-- dirname(fs, absPath)
    |   +-- fs.dirname() || path.posix.dirname() || path.win32.dirname()
    |
    +-- mkdirp(fs, p, callback)
    |   +-- fs.mkdir(p)
    |   +-- [ENOENT] mkdirp(fs, dirname(p))  ← 再帰
    |       +-- fs.mkdir(p)                    ← 再試行
    |
    +-- mkdirpSync(fs, p)
    |   +-- fs.mkdirSync(p)
    |   +-- [ENOENT] mkdirpSync(fs, dirname(p))  ← 再帰
    |
    +-- readJson(fs, p, callback)
    |   +-- fs.readJson(p) || fs.readFile(p) + JSON.parse()
    |
    +-- lstatReadlinkAbsolute(fs, p, callback)
    |   +-- fs.lstat(p)
    |   +-- [isSymbolicLink] fs.readlink(p)
    |   +-- join(fs, dirname(fs, p), target)
    |
    +-- isAbsolute(pathname)
        +-- path.posix.isAbsolute() || path.win32.isAbsolute()
```

### データフロー図

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

ファイルシステム(fs)  ------> relative/join/dirname -------> パス文字列
パス文字列                    POSIX/Win32 自動判定

ディレクトリパス     ------> mkdirp                 -------> ディレクトリ作成
                              再帰的mkdir + EEXIST許容

ファイルパス         ------> readJson               -------> JsonObject
                              readFile + JSON.parse

ファイルパス         ------> lstatReadlinkAbsolute  -------> IStats | 絶対パス
                              lstat + readlink + join          (symlink解決)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| fs.js | `lib/util/fs.js` | ソース | ファイルシステムインターフェース型定義とユーティリティ関数群 |
| Compiler.js | `lib/Compiler.js` | ソース | inputFileSystem/outputFileSystem/intermediateFileSystemを保持する主要消費者 |
| ResolverFactory.js | `lib/ResolverFactory.js` | ソース | enhanced-resolveにInputFileSystemを提供 |
