# 機能設計書 62-renderToSimpleStream()

## 概要

本ドキュメントは、Vue 3の`@vue/server-renderer`パッケージが提供する`renderToSimpleStream()`関数の設計仕様を記述する。この関数は、SSRストリーミングレンダリングの内部基盤として、シンプルなインターフェース（`push`/`destroy`）を持つストリームにVueコンポーネントのHTML出力を送出する。

### 本機能の処理概要

`renderToSimpleStream()`は、VueアプリケーションまたはVNodeを入力として受け取り、`SimpleReadable`インターフェースを実装した任意のストリームオブジェクトにHTML文字列チャンクを逐次出力する関数である。`renderToNodeStream()`、`renderToWebStream()`、`pipeToNodeWritable()`、`pipeToWebWritable()`の全てがこの関数を基盤として利用しており、SSRストリーミングの中核的な実装である。

**業務上の目的・背景**：Vue 3のSSRストリーミングレンダリング機能群は、様々なストリーム規格（Node.js Streams、Web Streams等）をサポートする必要がある。`renderToSimpleStream()`は最小限のインターフェース（`push`と`destroy`の2メソッド）を定義し、各ストリーム規格へのアダプター実装を単純化する設計パターンを実現している。これにより、新しいストリーム規格への対応が容易になる。

**機能の利用シーン**：直接的には、カスタムストリーム実装へのレンダリング出力が必要な場合に使用される。通常は`renderToWebStream()`や`pipeToNodeWritable()`等の高レベルAPIから間接的に利用される。

**主要な処理内容**：
1. VNode入力の場合はcreateApp()でアプリケーションにラップする
2. Appインスタンスからルートコンポーネントを取得しVNodeを生成する
3. SSRコンテキストをアプリケーションツリーにprovideする
4. `renderComponentVNode()`でコンポーネントツリー全体をSSRBufferにレンダリングする
5. `unrollBuffer()`でSSRBufferを展開し、各文字列チャンクをストリームにpushする
6. Teleport内容を解決し、ウォッチャーを停止する
7. 最後に`push(null)`でストリーム終了を通知する
8. エラー発生時は`destroy(err)`でストリームにエラーを伝播する

**関連システム・外部連携**：内部的にVueのランタイムコア（`createApp`、`createVNode`、`ssrUtils`）に依存する。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | サーバーサイドで実行されるため直接的な画面関連なし |

## 機能種別

データ変換処理（VNodeツリー -> ストリーミングHTML文字列出力）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | `App \| VNode` | Yes | レンダリング対象のVueアプリケーションインスタンスまたはVNode | VNodeの場合は内部でcreateApp()でラップ |
| context | `SSRContext` | Yes | SSRコンテキストオブジェクト | オブジェクト型 |
| stream | `T extends SimpleReadable` | Yes | 出力先ストリーム。push()とdestroy()メソッドを持つオブジェクト | SimpleReadableインターフェースを実装 |

### 入力データソース

- Vueアプリケーションインスタンス（`createApp()`または`createSSRApp()`で生成）
- 直接のVNode
- `SimpleReadable`を実装したストリームオブジェクト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | `T` | 入力で渡されたストリームオブジェクトをそのまま返却 |
| push(content) | - | ストリームにHTML文字列チャンクが逐次pushされる |
| push(null) | - | レンダリング完了時にnullがpushされストリーム終了を通知 |
| destroy(err) | - | エラー発生時にエラーオブジェクトがdestroy()で通知 |

### 出力先

引数で渡された`SimpleReadable`インターフェース実装オブジェクト。

## 処理フロー

### 処理シーケンス

```
1. 入力がVNodeか判定
   └─ VNodeの場合: createApp({ render: () => input })でラップして再帰呼び出し
2. Appインスタンスからルートコンポーネント情報を取得
   └─ createVNode(input._component, input._props)
3. appContextの設定
   └─ vnode.appContext = input._context
4. SSRコンテキストをprovide
   └─ input.provide(ssrContextKey, context)
5. コンポーネントのレンダリング（非同期）
   └─ Promise.resolve(renderComponentVNode(vnode))
6. SSRBufferの展開
   └─ unrollBuffer(buffer, stream)
7. Teleportの解決
   └─ resolveTeleports(context)
8. ウォッチャーの停止
   └─ context.__watcherHandles のunwatch()を全て呼び出し
9. ストリーム終了の通知
   └─ stream.push(null)
10. エラーハンドリング
    └─ stream.destroy(error)
```

### フローチャート

```mermaid
flowchart TD
    A[renderToSimpleStream呼び出し] --> B{inputはVNode?}
    B -->|Yes| C[createAppでラップして再帰呼び出し]
    B -->|No| D[createVNodeでルートVNode生成]
    D --> E[appContext設定]
    E --> F[SSRコンテキストをprovide]
    F --> G[renderComponentVNode実行]
    G --> H[unrollBuffer: SSRBufferをストリームに展開]
    H --> I[resolveTeleports: Teleport解決]
    I --> J{__watcherHandles?}
    J -->|Yes| K[全ウォッチャーを停止]
    J -->|No| L[stream.push null]
    K --> L
    L --> M[完了]
    G --> N{エラー発生?}
    N -->|Yes| O[stream.destroy error]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-62-01 | VNodeラップ | VNode入力時はcreateApp()でアプリケーションにラップしてからレンダリングする | inputがVNodeの場合 |
| BR-62-02 | 非同期処理チェーン | レンダリング→バッファ展開→Teleport解決→ウォッチャー停止→ストリーム終了の順で処理する | 常時 |
| BR-62-03 | ストリーム終了通知 | レンダリング完了時はpush(null)でストリーム終了を通知する | 正常完了時 |
| BR-62-04 | エラー伝播 | エラー発生時はdestroy(err)でストリームにエラーを伝播する | エラー発生時 |
| BR-62-05 | ストリーム返却 | 入力で渡されたストリームオブジェクトをそのまま返却する | 常時 |

### 計算ロジック

特になし。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | レンダリングエラー | コンポーネントのsetup/render中にエラー発生 | stream.destroy(error)でストリームにエラーを伝播 |

### リトライ仕様

リトライは行わない。

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

該当なし。

## パフォーマンス要件

- `unrollBuffer()`は`hasAsync`フラグに基づき、同期バッファの場合は`unrollBufferSync()`で不要なawaitを回避する最適化を実施
- ストリーミング出力により、バッファリング完了を待たずにクライアントへの送信を開始可能

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

- HTMLエスケープは内部のレンダリングエンジンで実施される
- SSRコンテキストへのユーザーデータ格納時は呼び出し側でサニタイズが必要

## 備考

- `SimpleReadable`は最小限の2メソッド（`push`/`destroy`）のみを要求するインターフェース
- `renderToNodeStream()`、`renderToWebStream()`、`pipeToNodeWritable()`、`pipeToWebWritable()`は全てこの関数のラッパーとして実装されている
- `unrollBuffer()`は非同期バッファの場合にawaitしながら再帰的にバッファを展開する

---

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

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

### 推奨読解順序

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

SimpleReadableインターフェースとSSRBuffer構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | SimpleReadableインターフェース（16-19行目）。push(string\|null)とdestroy(err)の2メソッド |
| 1-2 | render.ts | `packages/server-renderer/src/render.ts` | SSRBuffer型（42行目）、SSRBufferItem型（43行目）、PushFn型（44行目）。バッファの再帰的構造を理解 |
| 1-3 | render.ts | `packages/server-renderer/src/render.ts` | SSRContext型（47-58行目）。teleportsとwatcherHandlesのフィールドに注目 |

**読解のコツ**: `SSRBuffer`は`SSRBufferItem[]`であり、各要素は`string`、`SSRBuffer`（子バッファ）、`Promise<SSRBuffer>`（非同期子バッファ）のいずれか。`hasAsync`プロパティで非同期要素の有無を追跡し、同期時の最適化に使用する。

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

renderToSimpleStream()関数本体を読む。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream()関数（56-92行目） |

**主要処理フロー**:
1. **61-68行目**: VNode入力チェック。isVNode(input)の場合、createApp({ render: () => input })でラップして再帰呼び出し
2. **71行目**: createVNode(input._component, input._props)でルートVNodeを生成
3. **72行目**: vnode.appContext = input._contextでアプリケーションコンテキストを設定
4. **74行目**: input.provide(ssrContextKey, context)でSSRコンテキストをツリーにprovide
5. **76-89行目**: Promise.resolve(renderComponentVNode(vnode))から始まるPromiseチェーン
6. **77行目**: .then(buffer => unrollBuffer(buffer, stream))でバッファをストリームに展開
7. **78行目**: .then(() => resolveTeleports(context))でTeleport内容を解決
8. **79-84行目**: ウォッチャーハンドル停止処理
9. **86行目**: .then(() => stream.push(null))でストリーム終了を通知
10. **87-89行目**: .catch(error => { stream.destroy(error) })でエラーハンドリング
11. **91行目**: return streamで入力ストリームをそのまま返却

#### Step 3: バッファ展開処理を理解する

unrollBuffer()の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBuffer()関数（21-42行目）。hasAsyncフラグで分岐 |
| 3-2 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBufferSync()関数（44-54行目）。同期バッファの効率的な展開 |

**主要処理フロー**:
- **25行目**: `if (buffer.hasAsync)` で非同期要素の有無を判定
- **26-36行目**: 非同期の場合、各要素をawaitで解決しながらstream.push()
- **40行目**: 同期の場合、unrollBufferSync()で効率的に展開
- **45-53行目**: unrollBufferSync内で文字列はstream.push()、子バッファは再帰的に展開

#### Step 4: コンポーネントレンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | render.ts | `packages/server-renderer/src/render.ts` | renderComponentVNode()（91-123行目）。コンポーネントのセットアップとレンダリング |
| 4-2 | render.ts | `packages/server-renderer/src/render.ts` | createBuffer()（66-89行目）。バッファ生成とhasAsyncフラグ管理 |

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

```
renderToSimpleStream(input, context, stream)
    │
    ├─ [VNode] createApp({ render: () => input }) → 再帰呼び出し
    │
    ├─ createVNode(input._component, input._props)
    │
    ├─ input.provide(ssrContextKey, context)
    │
    └─ Promise chain:
           │
           ├─ renderComponentVNode(vnode)
           │      ├─ createComponentInstance()
           │      ├─ setupComponent(instance, true)
           │      └─ renderComponentSubTree(instance)
           │             ├─ createBuffer()
           │             ├─ renderVNode() / ssrRender()
           │             └─ getBuffer()
           │
           ├─ unrollBuffer(buffer, stream)
           │      ├─ [async] await item → stream.push(item)
           │      └─ [sync] unrollBufferSync() → stream.push(item)
           │
           ├─ resolveTeleports(context)
           │
           ├─ unwatch() for __watcherHandles
           │
           └─ stream.push(null)
```

### データフロー図

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

App / VNode ───▶ renderToSimpleStream()
                        │
                        ├─ renderComponentVNode()
                        │      └─ SSRBuffer
                        │            (string[] + Promise[])
                        │
                        ├─ unrollBuffer()
                        │      └─ string chunks ───▶ stream.push(chunk)
                        │
                        ├─ resolveTeleports()
                        │      └─ context.teleports
                        │
                        └─ stream.push(null) ───▶ ストリーム終了
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | renderToSimpleStream()本体、SimpleReadableインターフェース、unrollBuffer() |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | SSRBuffer/SSRContext型、renderComponentVNode()、createBuffer()、renderVNode() |
| renderToString.ts | `packages/server-renderer/src/renderToString.ts` | ソース | resolveTeleports()の定義元 |
| index.ts | `packages/server-renderer/src/index.ts` | ソース | パッケージエクスポート |
