# 機能設計書：Import Map生成

## 1. 概要

### 1.1 機能の目的
Import Map生成機能は、Payload CMSの設定からカスタムコンポーネントのインポートマップ（importMap.js）を自動生成する。React Server Components環境でカスタムUIコンポーネントを動的に解決するために、コンポーネントパスとインポート文のマッピングを構築する。

### 1.2 機能の範囲
- 設定からPayloadComponent参照の抽出
- コンポーネントパスの正規化
- インポート識別子の生成（MD5ハッシュ）
- importMap.jsファイルの生成
- コレクション・グローバル・フィールドのコンポーネント走査
- カスタムadminコンポーネントの収集
- RichTextエディタのコンポーネント収集

### 1.3 関連画面
- Admin UI全般（カスタムコンポーネント使用時）

### 1.4 関連機能
| 機能ID | 機能名 | 関連内容 |
|--------|--------|----------|
| 37 | 設定サニタイズ | サニタイズ済み設定が入力 |
| 06 | フィールド基盤 | フィールドコンポーネント抽出 |
| 29 | リッチテキストエディタ | エディタコンポーネント抽出 |

---

## 2. 機能種別

- [ ] API（REST）
- [ ] バッチ処理
- [ ] 画面機能
- [ ] 帳票出力
- [ ] 通知機能
- [x] システム内部機能

---

## 3. 入力仕様

### 3.1 generateImportMap関数入力

| パラメータ | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| config | SanitizedConfig | Yes | サニタイズ済み設定 |
| options.force | boolean | No | 強制上書きフラグ |
| options.ignoreResolveError | boolean | No | 解決エラー無視フラグ |
| options.log | boolean | No | ログ出力フラグ（デフォルト: true） |

### 3.2 環境変数

| 変数名 | 説明 | デフォルト |
|--------|------|-----------|
| ROOT_DIR | プロジェクトルートディレクトリ | process.cwd() |

### 3.3 設定オプション（config.admin.importMap）

| オプション | 型 | デフォルト | 説明 |
|-----------|-----|-----------|------|
| baseDir | string | process.cwd() | コンポーネント解決のベースディレクトリ |
| importMapFile | string | auto | importMapファイルパス |
| generators | function[] | - | カスタムジェネレータ関数 |

### 3.4 PayloadComponent形式

```typescript
// 文字列形式
type PayloadComponent = string  // 'path/to/component' or 'path/to/component#ExportName'

// オブジェクト形式
type PayloadComponent = {
  path: string              // コンポーネントパス
  exportName?: string       // エクスポート名（デフォルト: 'default'）
  clientProps?: object      // クライアントに渡すprops
  serverProps?: object      // サーバーで使用するprops
}
```

---

## 4. 出力仕様

### 4.1 importMap.js構造

```javascript
import { ComponentA as ComponentA_abc123 } from './components/ComponentA'
import { default as ComponentB_def456 } from '@mypackage/ComponentB'

export const importMap = {
  "./components/ComponentA#ComponentA": ComponentA_abc123,
  "@mypackage/ComponentB#default": ComponentB_def456
}
```

### 4.2 出力ファイル配置

| 優先順位 | パス | 条件 |
|---------|------|------|
| 1 | config.admin.importMap.importMapFile | 明示指定時 |
| 2 | `{rootDir}/app/(payload){adminRoute}/importMap.js` | appディレクトリ存在時 |
| 3 | `{rootDir}/src/app/(payload){adminRoute}/importMap.js` | src/appディレクトリ存在時 |

### 4.3 InternalImportMap型

```typescript
type InternalImportMap = {
  [userPath: string]: string  // 'path#exportName' -> 'exportName_md5hash'
}
```

### 4.4 Imports型

```typescript
type Imports = {
  [identifier: string]: {
    path: string       // 調整済みインポートパス
    specifier: string  // エクスポート名
  }
}
```

---

## 5. 処理フロー

### 5.1 Import Map生成全体フロー

```mermaid
flowchart TD
    A[generateImportMap] --> B[resolveImportMapFilePath]
    B --> C{Path resolved?}
    C -->|Error| D{ignoreResolveError?}
    D -->|Yes| E[Return silently]
    D -->|No| F[Throw error]
    C -->|Success| G[getImportMapToBaseDirPath]
    G --> H[iterateConfig]
    H --> I[writeImportMap]
    I --> J{force or changed?}
    J -->|No change| K[Skip write]
    J -->|Changed| L[Write file]
```

### 5.2 iterateConfig処理フロー

```mermaid
flowchart TD
    A[iterateConfig] --> B[iterateCollections]
    B --> C[iterateGlobals]
    C --> D{config.blocks?}
    D -->|Yes| E[iterateFields for blocks]
    D -->|No| F[Continue]
    E --> F
    F --> G[Add admin.avatar.Component]
    G --> H[Add admin.components.*]
    H --> I[Add dashboard widgets]
    I --> J{generators?}
    J -->|Yes| K[Run custom generators]
    J -->|No| L[Continue]
    K --> L
    L --> M[Add admin.dependencies]
```

### 5.3 iterateFields処理フロー

```mermaid
flowchart TD
    A[genImportMapIterateFields] --> B[For each field]
    B --> C{has fields?}
    C -->|Yes| D[Recursive iterate]
    C -->|No| E{type === blocks?}
    E -->|Yes| F[Iterate block fields]
    E -->|No| G{type === tabs?}
    G -->|Yes| H[Iterate tab fields]
    G -->|No| I{type === richText?}
    I -->|Yes| J[Call editor.generateImportMap]
    I -->|No| K{type === ui?}
    K -->|Yes| L[Add extra components]
    K -->|No| M[Continue]
    D --> N[Add field.admin.components.*]
    F --> N
    H --> N
    J --> N
    L --> N
    M --> N
    N --> O{More fields?}
    O -->|Yes| B
    O -->|No| P[Done]
```

### 5.4 コンポーネント追加フロー

```mermaid
sequenceDiagram
    participant Add as addToImportMap
    participant Parse as parsePayloadComponent
    participant Util as addPayloadComponentToImportMap
    participant Map as importMap/imports

    Add->>Parse: parsePayloadComponent(component)
    Parse-->>Add: { path, exportName }
    Add->>Util: addPayloadComponentToImportMap(...)
    Util->>Util: Check if already exists
    alt Already exists
        Util-->>Add: null
    else New component
        Util->>Util: Generate MD5 identifier
        Util->>Util: Adjust path if relative
        Util->>Map: Add to importMap and imports
        Util-->>Add: { path, specifier }
    end
```

---

## 6. ビジネスルール

### 6.1 パス解析ルール

| ID | ルール | 説明 |
|----|--------|------|
| BR-01 | #区切り | 'path#exportName'形式でエクスポート名指定 |
| BR-02 | デフォルトエクスポート | #なしの場合はexportName='default' |
| BR-03 | オブジェクト優先 | PayloadComponent.exportNameがあればそちらを使用 |

### 6.2 パス調整ルール

| ID | ルール | 説明 |
|----|--------|------|
| BR-04 | 相対パス | '.'または'/'で始まるパスは相対パスとして調整 |
| BR-05 | パッケージ/エイリアス | '@'等で始まるパスはそのまま使用 |
| BR-06 | './プレフィックス | importMapToBaseDirPathが'./'で始まる場合は保持 |

### 6.3 識別子生成ルール

| ID | ルール | 説明 |
|----|--------|------|
| BR-07 | 一意識別子 | exportName + '_' + MD5(componentPath) |
| BR-08 | 重複スキップ | 同一path#exportNameは追加しない |

### 6.4 ファイル書き込みルール

| ID | ルール | 説明 |
|----|--------|------|
| BR-09 | 差分チェック | 内容が同一なら書き込みスキップ |
| BR-10 | force指定 | force=trueなら常に書き込み |
| BR-11 | 自動作成 | ファイルが存在しなければ作成 |

---

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

本機能はデータベース操作を行わない。

---

## 8. エラー処理

### 8.1 エラー種別

| エラー | 発生条件 | 説明 |
|--------|----------|------|
| Import map folder not found | app/(payload)ディレクトリが見つからない | パス解決エラー |
| Invalid PayloadComponent | オブジェクトでも文字列でもない | 型エラー |
| File write error | 書き込み失敗 | ファイルシステムエラー |

### 8.2 ignoreResolveErrorオプション

パス解決エラー時に例外をスローせず、静かに終了する。開発時やオプショナルな生成で使用。

---

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

本機能にトランザクション制御は含まれない。

---

## 10. パフォーマンス要件

| 項目 | 要件 | 備考 |
|------|------|------|
| 差分チェック | 変更なしならスキップ | ファイル内容比較 |
| 重複排除 | 同一コンポーネントは1回のみ | importMap[key]でチェック |
| ビルド時実行 | 開発/ビルド時のみ | ランタイムでは実行しない |

---

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

| 項目 | 対応 |
|------|------|
| パストラバーサル | 相対パス正規化 |
| 任意コード実行 | インポート文のみ生成、実行は行わない |

---

## 12. 備考

### 12.1 React Server Components対応

Payload 3.xはRSC環境で動作するため、クライアントコンポーネントは明示的なインポートが必要。Import Mapはこれを自動化する。

### 12.2 カスタムジェネレータ

`config.admin.importMap.generators`で独自のコンポーネント収集ロジックを追加可能。

```typescript
generators: [
  ({ addToImportMap, config }) => {
    addToImportMap('./custom/Component#MyComponent')
  }
]
```

### 12.3 RichTextエディタ統合

RichTextフィールドのエディタは`editor.generateImportMap`メソッドでコンポーネントを登録する。

---

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

### 13.1 推奨読解順序

1. **エントリーポイント**（`index.ts`）- 全体フローと型定義
2. **設定走査**（`iterateConfig.ts`）- 設定からのコンポーネント抽出
3. **フィールド走査**（`iterateFields.ts`）- フィールドコンポーネント抽出
4. **パス解決**（`utilities/resolveImportMapFilePath.ts`）- ファイルパス決定
5. **コンポーネント追加**（`utilities/addPayloadComponentToImportMap.ts`）- マップ構築

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

```
generateImportMap (index.ts)
├── resolveImportMapFilePath() → utilities/resolveImportMapFilePath.ts
├── getImportMapToBaseDirPath() → utilities/getImportMapToBaseDirPath.ts
├── iterateConfig() → iterateConfig.ts
│   ├── iterateCollections() → iterateCollections.ts
│   │   └── genImportMapIterateFields() → iterateFields.ts
│   ├── iterateGlobals() → iterateGlobals.ts
│   │   └── genImportMapIterateFields()
│   ├── genImportMapIterateFields() [blocks]
│   │   ├── [recursive for nested fields]
│   │   └── editor.generateImportMap() [richText]
│   ├── addToImportMap() [admin components]
│   │   └── addPayloadComponentToImportMap() → utilities/addPayloadComponentToImportMap.ts
│   │       └── parsePayloadComponent() → utilities/parsePayloadComponent.ts
│   ├── generators[].()  [custom generators]
│   └── addToImportMap() [dependencies]
└── writeImportMap()
```

### 13.3 データフロー図

```
[SanitizedConfig]
         ↓
┌─────────────────────────────────┐
│  resolveImportMapFilePath()     │
│  - app/(payload)/adminRoute検索 │
│  - importMap.js パス決定        │
└─────────────────────────────────┘
         ↓
┌─────────────────────────────────┐
│  iterateConfig()                │
│  - collections走査              │
│  - globals走査                  │
│  - blocks走査                   │
│  - admin.components走査         │
│  - dashboard.widgets走査        │
│  - generators実行               │
│  - dependencies走査             │
└─────────────────────────────────┘
         ↓
┌─────────────────────────────────┐
│  addPayloadComponentToImportMap │
│  - parsePayloadComponent()      │
│  - MD5識別子生成                │
│  - パス調整（相対/絶対）        │
│  - importMap/imports追加        │
└─────────────────────────────────┘
         ↓
┌─────────────────────────────────┐
│  writeImportMap()               │
│  - import文生成                 │
│  - exportオブジェクト生成       │
│  - 差分チェック                 │
│  - ファイル書き込み             │
└─────────────────────────────────┘
         ↓
[importMap.js]
```

### 13.4 関連ファイル一覧

| ファイル | 行数 | 概要 |
|---------|------|------|
| `packages/payload/src/bin/generateImportMap/index.ts` | 1-180 | エントリーポイント、writeImportMap |
| `packages/payload/src/bin/generateImportMap/iterateConfig.ts` | 1-134 | 設定走査 |
| `packages/payload/src/bin/generateImportMap/iterateCollections.ts` | - | コレクション走査 |
| `packages/payload/src/bin/generateImportMap/iterateGlobals.ts` | - | グローバル走査 |
| `packages/payload/src/bin/generateImportMap/iterateFields.ts` | 1-116 | フィールド走査 |
| `packages/payload/src/bin/generateImportMap/utilities/addPayloadComponentToImportMap.ts` | 1-87 | コンポーネント追加 |
| `packages/payload/src/bin/generateImportMap/utilities/parsePayloadComponent.ts` | 1-29 | コンポーネントパース |
| `packages/payload/src/bin/generateImportMap/utilities/resolveImportMapFilePath.ts` | 1-59 | ファイルパス解決 |
| `packages/payload/src/bin/generateImportMap/utilities/getImportMapToBaseDirPath.ts` | - | ベースパス計算 |
| `packages/payload/src/bin/generateImportMap/utilities/getFromImportMap.ts` | - | インポートマップ取得 |

### 13.5 重要な実装ポイント

#### 13.5.1 generateImportMap（index.ts:43-131）
```typescript
// 43-131行目: Import Map生成メイン関数
export async function generateImportMap(config, options): Promise<void> {
  const importMap: InternalImportMap = {}
  const imports: Imports = {}

  const importMapFilePath = await resolveImportMapFilePath({
    adminRoute: config.routes.admin,
    importMapFile: config?.admin?.importMap?.importMapFile,
    rootDir,
  })

  const addToImportMap: AddToImportMap = (payloadComponent) => {
    addPayloadComponentToImportMap({
      importMap,
      importMapToBaseDirPath,
      imports,
      payloadComponent,
    })
  }

  iterateConfig({ addToImportMap, config, ... })
  await writeImportMap({ componentMap: importMap, importMap: imports, ... })
}
```

#### 13.5.2 parsePayloadComponent（parsePayloadComponent.ts:3-29）
```typescript
// 3-29行目: PayloadComponent解析
export function parsePayloadComponent(PayloadComponent): { exportName, path } {
  const pathAndMaybeExport = typeof PayloadComponent === 'string'
    ? PayloadComponent
    : PayloadComponent.path

  if (pathAndMaybeExport.includes('#')) {
    [path, exportName] = pathAndMaybeExport.split('#', 2)
  } else {
    path = pathAndMaybeExport
    exportName = 'default'
  }
  // オブジェクトのexportNameがあれば上書き
}
```

#### 13.5.3 識別子生成（addPayloadComponentToImportMap.ts:58-59）
```typescript
// 58-59行目: MD5ハッシュで一意識別子生成
const importIdentifier =
  exportName + '_' + crypto.createHash('md5').update(componentPath).digest('hex')
```

#### 13.5.4 フィールドコンポーネント走査（iterateFields.ts:19-116）
```typescript
// 19-116行目: フィールドのコンポーネント収集
export function genImportMapIterateFields({ addToImportMap, fields, ... }) {
  for (const field of fields) {
    if ('fields' in field) {
      // 再帰的に走査
    } else if (field.type === 'richText') {
      field.editor.generateImportMap({ addToImportMap, ... })
    }
    // field.admin.components.* を追加
    hasKey(field?.admin?.components, 'Field') && addToImportMap(field.admin.components.Field)
    hasKey(field?.admin?.components, 'Cell') && addToImportMap(field.admin.components.Cell)
    // ...
  }
}
```

#### 13.5.5 差分チェックと書き込み（index.ts:163-179）
```typescript
// 163-179行目: 差分がある場合のみ書き込み
if (!force) {
  const currentImportMap = await fs.readFile(importMapFilePath, 'utf-8')
  if (currentImportMap?.trim() === importMapOutputFile?.trim()) {
    console.log('No new imports found, skipping writing import map')
    return
  }
}
await fs.writeFile(importMapFilePath, importMapOutputFile)
```
