# 機能設計書 40-jest-haste-map

## 概要

本ドキュメントは、jest-haste-mapパッケージの機能設計を記載する。jest-haste-mapは、ファイルシステムをクロールしてモジュールマップとファイルメタデータを構築・キャッシュし、高速なモジュール解決と依存関係追跡を実現する。

### 本機能の処理概要

jest-haste-mapは、Facebook's hasteモジュールシステムのJavaScript実装であり、大規模コードベースにおける高性能なファイルシステムキャッシュを提供するライブラリである。

**業務上の目的・背景**：大規模なコードベース（数十万ファイル）でのテスト実行において、ファイルシステムの走査は起動時間の大きなボトルネックとなる。jest-haste-mapは、ファイルメタデータとモジュールマッピングをキャッシュすることで、テストの起動時間を大幅に短縮する。また、Watchman連携によりファイル変更の検知を効率化し、ウォッチモードでのインクリメンタルな再ビルドを実現する。

**機能の利用シーン**：
- テスト実行時のモジュール解決
- ウォッチモードでのファイル変更検知
- 依存関係グラフの構築
- React Nativeプロジェクトでのhasteモジュール解決
- CI/CDでのキャッシュ活用

**主要な処理内容**：
1. HasteMapクラス: ファイルシステムクロールとキャッシュ管理
2. HasteFS: ファイルシステムの仮想表現
3. ModuleMap: モジュール名からファイルパスへのマッピング
4. ファイルクローラー（node/watchman）
5. ワーカープロセスでの並列メタデータ抽出
6. ウォッチモードでのリアルタイム更新

**関連システム・外部連携**：
- jest-worker: ワーカープロセス管理
- jest-util: ユーティリティ関数
- jest-regex-util: 正規表現ユーティリティ
- fb-watchman: Watchmanクライアント
- fsevents: macOSネイティブファイルウォッチャー

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI出力 | 情報表示 | キャッシュ状況、ファイル変更通知 |

## 機能種別

ファイルシステムキャッシュ / モジュールマッピング / 基盤ユーティリティ

## 入力仕様

### 入力パラメータ

#### HasteMap.create Options

| パラメータ名 | 型 | 必須 | 説明 | デフォルト値 |
|-------------|-----|-----|------|-------------|
| id | string | Yes | キャッシュ識別子 | - |
| rootDir | string | Yes | プロジェクトルートディレクトリ | - |
| roots | Array<string> | Yes | スキャン対象ディレクトリ | - |
| extensions | Array<string> | Yes | 対象ファイル拡張子 | - |
| platforms | Array<string> | Yes | プラットフォームリスト | - |
| maxWorkers | number | Yes | ワーカー数 | - |
| retainAllFiles | boolean | Yes | node_modulesを含めるか | - |
| cacheDirectory | string | No | キャッシュディレクトリ | os.tmpdir() |
| computeDependencies | boolean | No | 依存関係を計算するか | true |
| computeSha1 | boolean | No | SHA-1を計算するか | false |
| dependencyExtractor | string | No | カスタム依存関係抽出器 | null |
| enableSymlinks | boolean | No | シンボリックリンクを追跡 | false |
| forceNodeFilesystemAPI | boolean | No | Node.js FSを強制使用 | false |
| hasteImplModulePath | string | No | カスタムhasteモジュール | undefined |
| ignorePattern | RegExp | No | 無視パターン | VCSディレクトリ |
| mocksPattern | string | No | モックファイルパターン | undefined |
| resetCache | boolean | No | キャッシュをリセット | false |
| skipPackageJson | boolean | No | package.jsonをスキップ | false |
| throwOnModuleCollision | boolean | No | 衝突時に例外 | false |
| useWatchman | boolean | No | Watchmanを使用 | true |
| watch | boolean | No | ウォッチモード | false |
| workerThreads | boolean | No | ワーカースレッドを使用 | undefined |

### 入力データソース

- ファイルシステム: ソースファイル、package.json
- Watchman: ファイル変更デルタ
- キャッシュファイル: 前回の走査結果

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| hasteFS | IHasteFS | ファイルシステムの仮想表現 |
| moduleMap | IModuleMap | モジュールマッピング |

### IHasteFS インターフェース

| メソッド名 | 戻り値 | 説明 |
|-----------|--------|------|
| exists(path) | boolean | ファイルの存在確認 |
| getAllFiles() | Array<string> | 全ファイル一覧 |
| getDependencies(file) | Array<string> | 依存ファイル一覧 |
| getSize(path) | number | ファイルサイズ |
| matchFiles(pattern) | Array<string> | パターンマッチ |
| matchFilesWithGlob(globs, root) | Set<string> | Globマッチ |
| getModuleName(file) | string | モジュール名 |

### IModuleMap インターフェース

| メソッド名 | 戻り値 | 説明 |
|-----------|--------|------|
| getModule(name, platform) | string | モジュールパス |
| getPackage(name, platform) | string | パッケージパス |
| getMockModule(name) | string | モックモジュールパス |
| getRawModuleMap() | RawModuleMap | 生データ |
| toJSON() | SerializableModuleMap | シリアライズ |

### 出力先

- メモリ: hasteFS、moduleMapオブジェクト
- ファイルシステム: キャッシュファイル

## 処理フロー

### 処理シーケンス

```
1. HasteMap.create
   └─ オプションの初期化とキャッシュパス設定
2. build()
   └─ _buildFileMap()
      └─ キャッシュの読み込みまたは空マップ生成
      └─ _crawl()でファイルシステム走査
         └─ Watchman使用可能時: watchmanCrawl
         └─ それ以外: nodeCrawl
   └─ _buildHasteMap()
      └─ 変更ファイルのメタデータ抽出（ワーカー並列）
      └─ モジュールマップとモックマップの構築
   └─ _persist()でキャッシュ保存
   └─ HasteFS、ModuleMapの生成
   └─ _watch()でウォッチャー設定（オプション）
3. read()
   └─ キャッシュファイルからデシリアライズ
4. end()
   └─ ウォッチャーの終了
```

### フローチャート

```mermaid
flowchart TD
    A[create開始] --> B[オプション初期化]
    B --> C[キャッシュパス計算]
    C --> D[build呼び出し]
    D --> E{キャッシュ存在?}
    E -->|Yes| F[キャッシュ読み込み]
    E -->|No| G[空マップ生成]
    F --> H[_crawl]
    G --> H
    H --> I{Watchman使用可能?}
    I -->|Yes| J[watchmanCrawl]
    I -->|No| K[nodeCrawl]
    J --> L[_buildHasteMap]
    K --> L
    L --> M{変更あり?}
    M -->|Yes| N[ワーカーでメタデータ抽出]
    M -->|No| O[キャッシュをそのまま使用]
    N --> P[_persist]
    O --> Q[HasteFS/ModuleMap生成]
    P --> Q
    Q --> R{watchモード?}
    R -->|Yes| S[_watch]
    R -->|No| T[完了]
    S --> T
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | VCSディレクトリ除外 | .git、.hg、.slディレクトリを無視 | 常時 |
| BR-02 | node_modules処理 | retainAllFilesがfalseの場合はnode_modulesを無視 | retainAllFiles設定に依存 |
| BR-03 | モジュール衝突処理 | 同名モジュールが複数ある場合、duplicatesマップに記録 | 衝突検出時 |
| BR-04 | プラットフォーム拡張子 | .ios.js、.android.jsなどをプラットフォーム固有として扱う | platforms設定に依存 |
| BR-05 | Watchmanフォールバック | Watchman失敗時はnodeCrawlにフォールバック | Watchmanエラー時 |
| BR-06 | キャッシュ無効化 | オプション変更時はキャッシュパスが変わり自動的に無効化 | 設定変更時 |
| BR-07 | シンボリックリンク | enableSymlinksがtrueの場合、Watchmanは使用不可 | enableSymlinks=true |

### 計算ロジック

**キャッシュパス生成**：
```typescript
const cachePath = getCacheFilePath(
  cacheDirectory,
  `haste-map-${id}-${rootDirHash}`,
  VERSION,
  id,
  roots.join(':'),
  extensions.join(':'),
  platforms.join(':'),
  computeSha1.toString(),
  mocksPattern,
  ignorePattern.toString(),
  hasteImplHash,
  dependencyExtractorHash,
  computeDependencies.toString()
);
```

**ファイルメタデータ構造**（空間効率のため配列で保存）：
```typescript
// [id, mtime, size, visited, dependencies, sha1]
type FileMetaData = [string, number, number, 0|1, string, string|null|undefined];
```

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

該当なし（ファイルベースのキャッシュ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | DuplicateError | 同名モジュールの衝突 | throwOnModuleCollisionがtrueの場合に例外 |
| - | DuplicateHasteCandidatesError | モジュール解決時の重複 | エラーメッセージで候補を表示 |
| - | Error | Watchmanクロール失敗 | nodeCrawlにフォールバック |
| - | TypeError | ignorePatternがRegExpでない | エラーメッセージを表示 |

### リトライ仕様

- Watchmanクロール失敗時、自動的にnodeCrawlで再試行

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

該当なし

## パフォーマンス要件

- jest-workerによる並列メタデータ抽出
- v8のserialize/deserializeによる高速キャッシュI/O
- 配列ベースのデータ構造による空間効率
- Watchmanによる差分更新

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

- hasteImplModulePath、dependencyExtractorはrequireで読み込まれるため信頼できるパスのみを指定

## 備考

- React NativeのHasteモジュールシステムに対応
- ウォッチャー優先順位: WatchmanWatcher > FSEventsWatcher > NodeWatcher

---

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

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

### 推奨読解順序

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

まず、型定義と定数から理解を始める。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/jest-haste-map/src/types.ts` | 主要な型定義 |
| 1-2 | constants.ts | `packages/jest-haste-map/src/constants.ts` | メタデータインデックス定数 |

**主要処理フロー（types.ts）**:
1. **22-41行目**: IModuleMap型（モジュールマップインターフェース）
2. **43-55行目**: IHasteFS型（ファイルシステムインターフェース）
3. **57-60行目**: IHasteMap型（HasteMapインターフェース）
4. **105-114行目**: FileMetaData型（配列形式のファイルメタデータ）
5. **125-131行目**: InternalHasteMap型（内部データ構造）
6. **145-146行目**: ModuleMapItem、ModuleMetaData型
7. **148-162行目**: HType型（定数キー定義）

**主要処理フロー（constants.ts）**:
1. **20-43行目**: 定数マップ（ID, MTIME, SIZE, VISITED, DEPENDENCIES, SHA1等）

#### Step 2: メインクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/jest-haste-map/src/index.ts` | HasteMapクラス |

**主要処理フロー**:
1. **58-83行目**: Options型（入力オプション）
2. **85-108行目**: InternalOptions型（内部オプション）
3. **216-295行目**: HasteMapクラスコンストラクタ
   - **246-270行目**: オプションの初期化
   - **273-286行目**: ignorePatternの設定（VCSディレクトリ追加）
   - **288-294行目**: enableSymlinksとWatchmanの排他チェック
4. **225-230行目**: getStatic（カスタムHasteMap取得）
5. **232-242行目**: create（ファクトリメソッド）
6. **297-340行目**: setupCachePath（キャッシュパス生成）
7. **342-352行目**: getCacheFilePath（静的メソッド）
8. **362-403行目**: build（メインビルドメソッド）
   - **365行目**: _buildFileMap呼び出し
   - **375行目**: _buildHasteMap呼び出し
   - **376行目**: _persist呼び出し
   - **382-391行目**: HasteFS、ModuleMap生成
   - **394行目**: _watch呼び出し
9. **408-418行目**: read（キャッシュ読み込み）
10. **433-446行目**: _buildFileMap（ファイルマップ構築）
11. **451-662行目**: _processFile（ファイル処理）
    - **460-521行目**: setModule内部関数（モジュール登録、衝突処理）
    - **574-589行目**: node_modulesファイルの処理
    - **591-622行目**: モックファイルの処理
    - **652-661行目**: ワーカー呼び出し
12. **664-721行目**: _buildHasteMap（HasteMap構築）
13. **736-738行目**: _persist（キャッシュ保存）
14. **743-761行目**: _getWorker（ワーカー取得/生成）
15. **763-805行目**: _crawl（クローラー実行）
16. **810-1020行目**: _watch（ウォッチモード）
    - **821-825行目**: ウォッチャー優先順位
    - **875-1012行目**: onChange処理
17. **1030-1076行目**: _recoverDuplicates（重複解消）
18. **1078-1090行目**: end（終了処理）
19. **1118-1126行目**: _createEmptyMap（空マップ生成）

#### Step 3: HasteFSクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | HasteFS.ts | `packages/jest-haste-map/src/HasteFS.ts` | ファイルシステム仮想表現 |

**主要処理フロー**:
1. **13-20行目**: HasteFS クラス定義とコンストラクタ
2. **22-25行目**: getModuleName（モジュール名取得）
3. **27-30行目**: getSize（ファイルサイズ取得）
4. **32-42行目**: getDependencies（依存取得）
5. **49-51行目**: exists（存在確認）
6. **53-55行目**: getAllFiles（全ファイル取得）
7. **61-65行目**: getAbsoluteFileIterator（絶対パスイテレータ）
8. **67-78行目**: matchFiles（正規表現マッチ）
9. **80-91行目**: matchFilesWithGlob（Globマッチ）

#### Step 4: ModuleMapクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ModuleMap.ts | `packages/jest-haste-map/src/ModuleMap.ts` | モジュールマッピング |

**主要処理フロー**:
1. **22-53行目**: ModuleMapクラス定義
2. **55-74行目**: getModule（モジュール取得）
   - **64-68行目**: _getModuleMetadata呼び出し
   - **69-71行目**: パス解決
3. **76-82行目**: getPackage（パッケージ取得）
4. **84-88行目**: getMockModule（モック取得）
5. **99-111行目**: toJSON（シリアライズ）
6. **113-122行目**: fromJSON（デシリアライズ）
7. **132-171行目**: _getModuleMetadata（メタデータ取得）
   - プラットフォーム優先順位による解決
8. **173-197行目**: _assertNoDuplicates（重複チェック）
9. **199-206行目**: create（ファクトリメソッド）
10. **209-241行目**: DuplicateHasteCandidatesError（エラークラス）

#### Step 5: ワーカーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | worker.ts | `packages/jest-haste-map/src/worker.ts` | ワーカー処理 |

**主要処理フロー**:
1. **28-97行目**: worker関数
   - **49-61行目**: package.jsonの処理（PACKAGE型）
   - **62-89行目**: モジュールファイルの処理（MODULE型）
   - **68-83行目**: 依存関係抽出
   - **92-94行目**: SHA-1計算
2. **99-110行目**: getSha1関数

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

```
HasteMap (index.ts)
    │
    ├─ create (ファクトリメソッド)
    │      └─ setupCachePath
    │             └─ getCacheFilePath
    │
    ├─ build
    │      │
    │      ├─ _buildFileMap
    │      │      │
    │      │      ├─ read (キャッシュ読み込み)
    │      │      │      └─ deserialize (v8)
    │      │      │
    │      │      └─ _crawl
    │      │             │
    │      │             ├─ watchmanCrawl (crawlers/watchman.ts)
    │      │             │
    │      │             └─ nodeCrawl (crawlers/node.ts)
    │      │
    │      ├─ _buildHasteMap
    │      │      │
    │      │      └─ _processFile (各ファイル)
    │      │             │
    │      │             └─ _getWorker
    │      │                    │
    │      │                    ├─ worker (worker.ts)
    │      │                    │      │
    │      │                    │      └─ dependencyExtractor
    │      │                    │
    │      │                    └─ getSha1 (worker.ts)
    │      │
    │      ├─ _persist
    │      │      └─ serialize (v8)
    │      │
    │      └─ _watch
    │             │
    │             ├─ WatchmanWatcher
    │             │
    │             ├─ FSEventsWatcher
    │             │
    │             └─ NodeWatcher
    │
    └─ 出力
           │
           ├─ HasteFS (HasteFS.ts)
           │      │
           │      ├─ exists
           │      ├─ getAllFiles
           │      ├─ getDependencies
           │      └─ matchFiles
           │
           └─ ModuleMap (ModuleMap.ts)
                  │
                  ├─ getModule
                  ├─ getPackage
                  └─ getMockModule
```

### データフロー図

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

ファイルシステム ───────────▶ crawl()
                                  │
                                  ├─ Watchman利用可能?
                                  │      │
fb-watchman ──────────────────▶   │      └─ watchmanCrawl
                                  │             │
                                  │             └─ デルタ取得
                                  │
                                  └─ nodeCrawl
                                         │
                                         └─ walker / fs.readdir
                                                │
                                                ▼
                                         FileData (files Map)

files Map ─────────────────▶ _buildHasteMap()
                                  │
                                  └─ _processFile (並列)
                                         │
jest-worker ─────────────────▶        │
                                         ├─ hasteImpl.getHasteName
                                         │
                                         ├─ dependencyExtractor.extract
                                         │
                                         └─ sha1計算
                                                │
                                                ▼
                                         InternalHasteMap
                                         {
                                           clocks: WatchmanClocks,
                                           duplicates: DuplicatesIndex,
                                           files: FileData,
                                           map: ModuleMapData,
                                           mocks: MockData
                                         }
                                                │
                                         ┌──────┴──────┐
                                         │             │
                                         ▼             ▼
v8.serialize ─────────────────▶  キャッシュ    HasteFS + ModuleMap
                                  (binary)     (メモリ)
                                    │             │
                                    ▼             ▼
                              ファイル保存    build()戻り値
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/jest-haste-map/src/index.ts` | ソース | HasteMapクラス（メイン） |
| types.ts | `packages/jest-haste-map/src/types.ts` | ソース | 型定義 |
| constants.ts | `packages/jest-haste-map/src/constants.ts` | ソース | 定数定義 |
| HasteFS.ts | `packages/jest-haste-map/src/HasteFS.ts` | ソース | ファイルシステム表現 |
| ModuleMap.ts | `packages/jest-haste-map/src/ModuleMap.ts` | ソース | モジュールマッピング |
| worker.ts | `packages/jest-haste-map/src/worker.ts` | ソース | ワーカー処理 |
| getMockName.ts | `packages/jest-haste-map/src/getMockName.ts` | ソース | モック名抽出 |
| blacklist.ts | `packages/jest-haste-map/src/blacklist.ts` | ソース | 無視する拡張子リスト |
| crawlers/node.ts | `packages/jest-haste-map/src/crawlers/node.ts` | ソース | Node.jsクローラー |
| crawlers/watchman.ts | `packages/jest-haste-map/src/crawlers/watchman.ts` | ソース | Watchmanクローラー |
| watchers/FSEventsWatcher.ts | `packages/jest-haste-map/src/watchers/FSEventsWatcher.ts` | ソース | FSEventsウォッチャー |
| watchers/NodeWatcher.js | `packages/jest-haste-map/src/watchers/NodeWatcher.js` | ソース | Nodeウォッチャー |
| watchers/WatchmanWatcher.js | `packages/jest-haste-map/src/watchers/WatchmanWatcher.js` | ソース | Watchmanウォッチャー |
| lib/dependencyExtractor.ts | `packages/jest-haste-map/src/lib/dependencyExtractor.ts` | ソース | 依存関係抽出 |
| lib/fast_path.ts | `packages/jest-haste-map/src/lib/fast_path.ts` | ソース | 高速パス操作 |
| lib/getPlatformExtension.ts | `packages/jest-haste-map/src/lib/getPlatformExtension.ts` | ソース | プラットフォーム拡張子取得 |
| lib/isWatchmanInstalled.ts | `packages/jest-haste-map/src/lib/isWatchmanInstalled.ts` | ソース | Watchman検出 |
| lib/normalizePathSep.ts | `packages/jest-haste-map/src/lib/normalizePathSep.ts` | ソース | パス区切り正規化 |
| package.json | `packages/jest-haste-map/package.json` | 設定 | パッケージ定義 |
