# 機能設計書 13-defineAsyncComponent()

## 概要

本ドキュメントは、非同期コンポーネントを定義するAPI `defineAsyncComponent()` の機能設計書である。

### 本機能の処理概要

`defineAsyncComponent()` は、コンポーネントを非同期にロードする仕組みを提供する関数である。コード分割（code splitting）による遅延ロード、ローディング/エラー状態のUI制御、タイムアウト、リトライ、SSRハイドレーション戦略の制御を含む包括的な非同期コンポーネント機構を実装している。

**業務上の目的・背景**：大規模Webアプリケーションにおいて、初期バンドルサイズを削減し初期表示速度を改善するため、コンポーネントを必要なタイミングで動的にインポートする仕組みが求められる。また、ロード中・エラー時のUX制御も重要な要件である。

**機能の利用シーン**：ルートベースのコード分割（Vue Router連携）、条件付きレンダリングされるコンポーネントの遅延ロード、重いライブラリを含むコンポーネントの分割、SSR環境でのハイドレーション制御で使用される。

**主要な処理内容**：
1. ローダー関数（`() => import('./Component.vue')`）の受け取り
2. 内部的に `defineComponent` でラッパーコンポーネント（AsyncComponentWrapper）を生成
3. ロード状態の管理（loaded, error, delayed の3つのリアクティブref）
4. タイムアウト・遅延表示・エラーハンドリング・リトライ機構の提供
5. Suspenseコンポーネントとの連携（suspensible オプション）
6. SSRハイドレーション戦略の制御（__asyncHydrate メソッド）
7. KeepAliveとの連携（親がKeepAliveの場合の名前更新）

**関連システム・外部連携**：Suspenseコンポーネント、KeepAliveコンポーネント、SSRハイドレーション機構と連携する。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | defineAsyncComponent()は特定の画面に限定されず、コンポーネント定義時に使用 |

## 機能種別

非同期処理 / コンポーネントローダー / コード分割

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| source | AsyncComponentLoader\<T\> \| AsyncComponentOptions\<T\> | Yes | ローダー関数、またはオプションオブジェクト | 関数の場合は{ loader: source }に正規化 |

#### AsyncComponentOptions の詳細

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| loader | AsyncComponentLoader\<T\> | Yes | コンポーネントを返すPromiseを返す関数 | - |
| loadingComponent | Component | No | ロード中に表示するコンポーネント | - |
| errorComponent | Component | No | エラー時に表示するコンポーネント | - |
| delay | number | No | ローディングコンポーネント表示までの遅延（ms）。デフォルト: 200 | - |
| timeout | number | No | タイムアウト（ms）。デフォルト: undefined（無限） | - |
| suspensible | boolean | No | Suspenseと連携するか。デフォルト: true | - |
| hydrate | HydrationStrategy | No | SSRハイドレーション戦略 | - |
| onError | (error, retry, fail, attempts) => any | No | エラー時のカスタムハンドラ | - |

### 入力データソース

開発者がコンポーネント定義時に直接呼び出す。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| component | T（キャスト） | AsyncComponentWrapperコンポーネント定義 |

### 出力先

呼び出し元に返却。コンポーネント登録やテンプレート内での参照に使用される。

## 処理フロー

### 処理シーケンス

```
1. 入力の正規化
   └─ 関数の場合は { loader: source } に変換
2. オプションの分解（loader, loadingComponent, errorComponent, delay, timeout, suspensible, onError）
3. defineComponent で AsyncComponentWrapper を構築
   ├─ __asyncLoader: load関数を設定
   ├─ __asyncHydrate: SSRハイドレーション制御を設定
   └─ setup():
       ├─ resolvedComp が既にある場合: 即座に内部コンポーネントVNodeを返却
       ├─ Suspense/SSR環境の場合: load().then() のPromiseを返却
       └─ 通常環境の場合:
           ├─ loaded, error, delayed の3つのrefを初期化
           ├─ delay指定: setTimeout で delayed.value = false
           ├─ timeout指定: setTimeout でタイムアウトエラー
           └─ load() を実行、結果に応じて表示切り替え
```

### フローチャート

```mermaid
flowchart TD
    A[defineAsyncComponent呼び出し] --> B{sourceは関数?}
    B -->|Yes| C[source = loader: source に正規化]
    B -->|No| C2[sourceをそのまま使用]
    C --> D[load関数を定義]
    C2 --> D
    D --> E[defineComponent AsyncComponentWrapper]
    E --> F[setup実行]
    F --> G{resolvedComp あり?}
    G -->|Yes| H[即座にcreateInnerComp]
    G -->|No| I{Suspense/SSR?}
    I -->|Yes| J[load.then で返却]
    I -->|No| K[loaded/error/delayed ref初期化]
    K --> L[delayタイマー設定]
    K --> M[timeoutタイマー設定]
    K --> N[load実行]
    N --> O{ロード成功?}
    O -->|Yes| P[loaded.value=true]
    O -->|No| Q[error.value=err]
    P --> R[render: createInnerComp]
    Q --> S[render: errorComponent]
    L --> T[render: loadingComponent]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-13-01 | デフォルト遅延 | ローディングコンポーネントの表示は200ms遅延がデフォルト | delay未指定時 |
| BR-13-02 | リトライ機構 | onErrorコールバックのretry/failで再試行・失敗を制御 | onErrorが指定されている場合 |
| BR-13-03 | ESモジュール互換 | __esModuleまたはSymbol.toStringTag==='Module'の場合はdefaultエクスポートを使用 | ローダーの返却値がESモジュールの場合 |
| BR-13-04 | KeepAlive更新 | 親がKeepAliveの場合、ロード完了時にparent.update()を呼んでキャッシュ名を更新 | instance.parent && isKeepAlive(instance.parent.vnode)の場合 |
| BR-13-05 | Suspense連携 | suspensible=trueかつSuspenseコンテキスト内の場合、Promiseを返してSuspenseに制御を委譲 | suspensible && instance.suspense の場合 |
| BR-13-06 | ハイドレーション制御 | hydrateStrategyが指定された場合、カスタムハイドレーション戦略を適用 | SSR環境かつhydrateオプション指定時 |

### 計算ロジック

特に複雑な計算ロジックは存在しない。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ASYNC_COMPONENT_LOADER | ランタイムエラー | ローダーPromiseがrejectされた場合 | onErrorハンドラでretry/failを制御、またはerrorComponentを表示 |
| - | タイムアウトエラー | timeout指定時間内にロードが完了しない場合 | errorComponentの表示、timeoutを長く設定 |
| - | 警告（DEV） | ローダーがundefinedを解決した場合 | retry()使用時にreturn値を確認 |
| - | 例外（DEV） | ロード結果がオブジェクトでも関数でもない場合 | 正しいコンポーネント定義を返すローダーを使用 |

### リトライ仕様

`onError` コールバックが指定されている場合、以下のリトライ機構が利用可能：
- `retry()`: リトライ回数をインクリメントし、pendingRequestをリセットして再ロード
- `fail()`: Promiseをrejectして失敗確定
- `attempts`: 現在のリトライ試行回数（retries + 1）

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

該当なし。

## パフォーマンス要件

- `@__NO_SIDE_EFFECTS__` アノテーションにより、未使用の非同期コンポーネント定義はtree-shakingで除外される。
- delay（デフォルト200ms）により、高速ロード時にローディングUIのフラッシュを防止する。
- pendingRequestのキャッシュにより、同一コンポーネントへの重複リクエストを防止する。

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

- ローダー関数で外部URLからコンポーネントを動的にロードする場合、信頼できるソースからのみロードすること。

## 備考

- `createInnerComp()` ヘルパーは、ラッパーコンポーネントのref、props、children、ceをロードされたコンポーネントのVNodeに引き継ぐ。
- `markAsyncBoundary(instance)` はuseId()のための非同期境界マーキングを行う。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | AsyncComponentOptions インターフェース（27-41行目） |
| 1-2 | apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | AsyncComponentLoader, AsyncComponentResolveResult 型（21-25行目） |

**読解のコツ**: `AsyncComponentResolveResult` は `T | { default: T }` のunion型で、ESモジュールのdefaultエクスポートにも対応している。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | defineAsyncComponent関数（47-252行目） |

**主要処理フロー**:
1. **49-52行目**: 入力が関数の場合、`{ loader: source }` に正規化
2. **54-63行目**: オプションの分解（loader, loadingComponent, errorComponent, delay=200, timeout, suspensible=true, onError）
3. **65-66行目**: pendingRequest, resolvedComp の状態変数
4. **69-73行目**: retry関数（retriesインクリメント、pendingRequestリセット、再ロード）
5. **75-117行目**: load関数（ローダー呼び出し、エラーハンドリング、ESモジュール互換、キャッシュ）
6. **119-251行目**: defineComponentによるAsyncComponentWrapperの構築

#### Step 3: setup関数の内部処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | setup関数（161-250行目） |

**主要処理フロー**:
- **163行目**: markAsyncBoundary(instance) で非同期境界をマーク
- **166-168行目**: 既にresolvedCompがある場合は即座にcreateInnerCompを返却
- **180-198行目**: Suspense/SSR環境での処理（Promiseを返却）
- **200-203行目**: loaded, error, delayed のリアクティブref初期化
- **204-208行目**: delay指定時のsetTimeout
- **210-220行目**: timeout指定時のsetTimeout
- **222-234行目**: load()実行と結果ハンドリング
- **236-249行目**: レンダリング関数（loaded/error/delayedの状態に応じた表示切り替え）

#### Step 4: ハイドレーション処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | __asyncHydrate メソッド（124-155行目） |

**主要処理フロー**:
- **126行目**: patchedフラグでハイドレーション前の更新を検知
- **127行目**: instance.buにフック追加（更新時にpatched=trueを設定）
- **140-149行目**: hydrateStrategyが指定されている場合はカスタム戦略を適用
- **150-154行目**: resolvedCompがある場合は即座にハイドレーション、なければload後に実行

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

```
defineAsyncComponent(source)
    │
    ├─ load()
    │      ├─ loader() → Promise<Component>
    │      ├─ .catch() → onError? retry/fail : throw
    │      └─ .then() → ESモジュール互換処理 → resolvedComp設定
    │
    └─ defineComponent({ name: 'AsyncComponentWrapper', setup() })
           │
           ├─ markAsyncBoundary(instance)
           ├─ [Suspense/SSR] load().then() → createInnerComp
           └─ [通常] ref(loaded, error, delayed)
                  ├─ setTimeout(delay)
                  ├─ setTimeout(timeout)
                  ├─ load().then() → loaded.value = true
                  └─ render関数
                         ├─ loaded → createInnerComp(resolvedComp)
                         ├─ error → createVNode(errorComponent)
                         └─ !delayed → createInnerComp(loadingComponent)
```

### データフロー図

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

loader関数 ──────────▶ load() ────────────────────▶ resolvedComp (Component)
                          │
loadingComponent ────▶ setup() render関数 ────────▶ VNode (loadingComponent)
errorComponent ──────▶ setup() render関数 ────────▶ VNode (errorComponent)
                          │
delay/timeout ───────▶ setTimeout制御 ────────────▶ delayed/error ref
onError ─────────────▶ retry/fail制御 ────────────▶ リトライまたは失敗
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| apiAsyncComponent.ts | `packages/runtime-core/src/apiAsyncComponent.ts` | ソース | defineAsyncComponentの実装本体 |
| apiDefineComponent.ts | `packages/runtime-core/src/apiDefineComponent.ts` | ソース | ラッパーコンポーネント定義に使用 |
| vnode.ts | `packages/runtime-core/src/vnode.ts` | ソース | createVNode関数（VNode生成） |
| errorHandling.ts | `packages/runtime-core/src/errorHandling.ts` | ソース | ErrorCodes, handleError |
| component.ts | `packages/runtime-core/src/component.ts` | ソース | currentInstance, isInSSRComponentSetup |
| components/KeepAlive.ts | `packages/runtime-core/src/components/KeepAlive.ts` | ソース | isKeepAlive判定 |
| helpers/useId.ts | `packages/runtime-core/src/helpers/useId.ts` | ソース | markAsyncBoundary |
| hydrationStrategies.ts | `packages/runtime-core/src/hydrationStrategies.ts` | ソース | HydrationStrategy型, forEachElement |
