# 帳票設計書 11-SSR SimpleStream出力

## 概要

本ドキュメントは、Vue.jsのサーバーサイドレンダリング（SSR）パッケージ `@vue/server-renderer` における `renderToSimpleStream` 関数の出力仕様を定義する帳票設計書である。この関数は、push/destroyインターフェースを持つ任意のSimpleReadableオブジェクトにHTML文字列を逐次出力する基盤関数であり、`renderToNodeStream` や `renderToWebStream` の内部基盤として使用される。

### 本帳票の処理概要

**業務上の目的・背景**：SSRにおけるストリーミングレンダリングは、大規模なHTMLを一括ではなく逐次的に送出することで、TTFB（Time to First Byte）の改善とメモリ使用量の最適化を実現する。`renderToSimpleStream` はこのストリーミングレンダリングの最も低レベルな基盤APIであり、プラットフォーム非依存のインターフェースを提供することで、Node.jsストリームやWeb Streams APIなど様々な出力先に対応可能とする。

**帳票の利用シーン**：`renderToSimpleStream` は以下の場面で利用される。(1) `renderToNodeStream` がNode.js Readableストリームを構築する際の内部呼び出し先として、(2) `renderToWebStream` がWeb ReadableStreamを構築する際の内部呼び出し先として、(3) `pipeToNodeWritable` がNode.js Writableストリームにパイプする際の内部呼び出し先として、(4) `pipeToWebWritable` がWeb WritableStreamにパイプする際の内部呼び出し先として、(5) カスタムのSimpleReadable実装を使用して独自のストリーミング処理を行う場合に直接呼び出される。

**主要な出力内容**：
1. VueアプリケーションまたはVNodeから生成されたHTML文字列の逐次チャンク（push経由）
2. Teleportコンテンツの解決結果（SSRContext.teleports経由）
3. レンダリング完了シグナル（push(null)による終端通知）
4. エラー発生時のエラーオブジェクト（destroy経由）

**帳票の出力タイミング**：renderToSimpleStream関数が呼び出された直後に非同期でレンダリングが開始され、VNodeツリーの再帰的なレンダリングが完了するまでHTML文字列チャンクが逐次push()される。すべてのコンポーネント（非同期コンポーネント、Suspenseを含む）のレンダリングが完了し、Teleportが解決された後にpush(null)で終了が通知される。

**帳票の利用者**：Vueフレームワーク内部のストリーミングAPI（renderToNodeStream、renderToWebStream、pipeToNodeWritable、pipeToWebWritable）、およびカスタムストリーミング実装を行うサーバーサイド開発者。

## 帳票種別

ストリーミングHTML出力（プログラムインターフェース出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | サーバーサイド処理 | N/A | renderToSimpleStream関数呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML文字列ストリーム（テキスト） |
| 用紙サイズ | N/A（プログラム出力） |
| 向き | N/A |
| ファイル名 | N/A（ストリーム出力） |
| 出力方法 | SimpleReadableインターフェース経由のpush/destroyコールバック |
| 文字コード | UTF-8 |

### PDF固有設定

N/A（本帳票はHTMLストリーム出力であるため該当しない）

### Excel固有設定

N/A（本帳票はHTMLストリーム出力であるため該当しない）

## 帳票レイアウト

### レイアウト概要

出力はVueコンポーネントツリーをSSRでレンダリングしたHTML文字列であり、レイアウトはアプリケーションのコンポーネント構成に依存する。SimpleReadableインターフェースを通じて以下の構造でデータが出力される。

```
┌─────────────────────────────────────┐
│   SSRBuffer（再帰的バッファ構造）      │
│   ┌─────────────────────────────┐   │
│   │ 文字列チャンク1               │   │
│   ├─────────────────────────────┤   │
│   │ 文字列チャンク2               │   │
│   ├─────────────────────────────┤   │
│   │ 子バッファ（ネスト）           │   │
│   │   ├─ 文字列チャンク3          │   │
│   │   └─ 文字列チャンク4          │   │
│   ├─────────────────────────────┤   │
│   │ Promise<SSRBuffer>（非同期）   │   │
│   └─────────────────────────────┘   │
│         │                           │
│         ▼ unrollBuffer              │
│   push(chunk) × N回                 │
│   push(null) ─ 終了シグナル          │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | HTMLチャンク | VNodeツリーから生成されたHTML文字列断片 | renderComponentVNode → SSRBuffer | 文字列 |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | push(content) | HTML文字列チャンクの逐次出力 | SSRBuffer各要素 | string | N/A |
| 2 | push(null) | ストリーム終了シグナル | レンダリング完了後 | null | N/A |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Teleportコンテンツ | SSRContext.teleportsに格納されるTeleport解決結果 | resolveTeleports(context) | Record<string, string> |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| input | Vueアプリケーションインスタンス（App）またはVNode | Yes |
| context | SSRContextオブジェクト | Yes |
| stream | SimpleReadableインターフェースを実装したオブジェクト | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | VNodeツリーの深さ優先順序 | ツリー走査順 |

### 改ページ条件

N/A（ストリーム出力のため改ページの概念はない。チャンクはSSRBufferの要素単位で分割される）

## データベース参照仕様

### 参照テーブル一覧

N/A（データベースアクセスは行わない。Vueアプリケーションのコンポーネントツリーからインメモリでレンダリングを行う）

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| SSRBuffer展開 | SSRBufferの再帰的な展開（unrollBuffer/unrollBufferSync） | N/A | hasAsyncフラグにより同期/非同期の最適な展開方法を選択 |
| Teleport解決 | context.__teleportBuffersの各エントリをPromise.allで解決 | N/A | resolveTeleports関数で処理 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[renderToSimpleStream呼び出し] --> B{入力がVNodeか?}
    B -->|Yes| C[createAppでラップして再帰呼び出し]
    B -->|No| D[createVNodeでルートVNode生成]
    D --> E[appContextを設定]
    E --> F[ssrContextKeyをprovide]
    F --> G[renderComponentVNode実行]
    G --> H[unrollBuffer実行]
    H --> I{buffer.hasAsync?}
    I -->|Yes| J[非同期展開: awaitで各要素を解決しpush]
    I -->|No| K[同期展開: unrollBufferSyncでpush]
    J --> L[resolveTeleports実行]
    K --> L
    L --> M[watcherHandlesのクリーンアップ]
    M --> N[push null で終了通知]
    G -->|エラー| O[stream.destroy err]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| レンダリングエラー | コンポーネントのsetupやrender中に例外が発生 | エラーオブジェクトがstream.destroy(error)に渡される | stream実装側でエラーハンドリングを行う |
| 非同期コンポーネントエラー | Promiseがrejectされた場合 | エラーオブジェクトがstream.destroy(error)に渡される | Promiseチェーンのcatchでstream.destroyが呼ばれる |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | コンポーネントツリーのサイズに依存（制限なし） |
| 目標出力時間 | アプリケーション規模に依存（ストリーミングによりTTFBを最小化） |
| 同時出力数上限 | サーバーリソースに依存（関数自体に制限なし） |

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

- SSRContextは信頼できるサーバーサイドコードからのみアクセスされる前提で設計されている
- HTMLエスケープはレンダリング処理（renderComponentVNode内）で適切に行われる
- ユーザー入力は `@vue/shared` の `escapeHtml` 関数でサニタイズされる

## 備考

- `renderToSimpleStream` はVue 3.xのserver-rendererパッケージで導入された低レベルAPIである
- SimpleReadableインターフェースはNode.jsのStreamやWeb Streams APIに依存しない軽量な抽象化を提供する
- 入力がVNodeの場合、内部でcreateAppによりAppインスタンスにラップされて処理される（renderToStream.ts 61-68行目）
- watcherHandlesのクリーンアップにより、SSR中に作成されたwatcherがリークしないよう配慮されている（renderToStream.ts 80-84行目）

---

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

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

### 推奨読解順序

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

まず、SSRレンダリングで使用されるバッファ構造とインターフェースの定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 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行目）、SSRContext型（47-58行目）の定義。SSRBufferはhasAsyncフラグを持つ配列型 |

**読解のコツ**: SSRBufferは `SSRBufferItem[] & { hasAsync?: boolean }` という型で、文字列、ネストされたバッファ、Promiseの3種類の要素を持ちうる再帰的なデータ構造である。hasAsyncフラグは同期/非同期展開の分岐に使われる最適化ポイントである。

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

処理の起点となるrenderToSimpleStream関数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream関数（56-92行目）。入力の型判定、VNode生成、Promiseチェーンの構築 |

**主要処理フロー**:
1. **61行目**: VNode入力の場合、createAppでラップして再帰呼び出し
2. **71行目**: createVNodeでルートVNodeを生成
3. **72行目**: appContextを設定
4. **74行目**: ssrContextKeyをprovide
5. **76-89行目**: renderComponentVNode → unrollBuffer → resolveTeleports → watcherクリーンアップ → push(null)のPromiseチェーン
6. **87-89行目**: エラー時のcatchでstream.destroy(error)

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

SSRBufferをSimpleReadableに展開する処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBuffer関数（21-42行目）とunrollBufferSync関数（44-54行目） |

**主要処理フロー**:
- **25行目**: hasAsyncフラグによる分岐。非同期バッファはawaitで逐次解決
- **28-29行目**: Promise要素はawaitで解決
- **31-32行目**: 文字列要素はそのままpush
- **33-34行目**: ネストバッファは再帰呼び出し
- **40行目**: 同期バッファはunrollBufferSyncで高速展開（不要なawait tickを回避）

#### Step 4: Teleport解決処理を理解する

レンダリング完了後のTeleportコンテンツ解決を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | renderToString.ts | `packages/server-renderer/src/renderToString.ts` | resolveTeleports関数（99-110行目） |

**主要処理フロー**:
- **100行目**: context.__teleportBuffersの存在確認
- **102-107行目**: 各Teleportバッファをawaitで解決し、context.teleportsに文字列として格納

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

VNodeからSSRBufferを生成するコアレンダリングロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | render.ts | `packages/server-renderer/src/render.ts` | renderComponentVNode関数（91-123行目）。コンポーネントインスタンスの作成、セットアップ、サブツリーレンダリング |
| 5-2 | render.ts | `packages/server-renderer/src/render.ts` | createBuffer関数（66-88行目）。バッファの生成とpush/getBufferインターフェース |

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

```
renderToSimpleStream(input, context, stream)
    │
    ├─ [VNode入力時] createApp({ render: () => input })
    │      └─ renderToSimpleStream(app, context, stream)  ← 再帰呼び出し
    │
    ├─ createVNode(input._component, input._props)
    ├─ input.provide(ssrContextKey, context)
    │
    └─ Promise.resolve(renderComponentVNode(vnode))
           │
           ├─ renderComponentVNode(vnode)
           │      ├─ createComponentInstance()
           │      ├─ setupComponent(instance, true)
           │      └─ renderComponentSubTree(instance)
           │             └─ createBuffer() → SSRBuffer
           │
           ├─ unrollBuffer(buffer, stream)
           │      ├─ [hasAsync] unrollBuffer() ← 非同期再帰
           │      └─ [sync] unrollBufferSync() ← 同期再帰
           │             └─ stream.push(item)
           │
           ├─ resolveTeleports(context)
           │      └─ unrollBuffer(teleportBuffer)
           │
           ├─ [watcherHandles cleanup]
           │
           ├─ stream.push(null) ← 完了通知
           │
           └─ [catch] stream.destroy(error) ← エラー通知
```

### データフロー図

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

App | VNode ──────▶ renderToSimpleStream ─────▶ SimpleReadable.push(chunk)
                        │                            │
SSRContext ─────────▶    ├─ renderComponentVNode      ├─ push(string) × N回
                        │     └─ SSRBuffer生成        │
                        ├─ unrollBuffer               ├─ push(null) 終了
                        │     └─ バッファ展開          │
                        ├─ resolveTeleports           ├─ SSRContext.teleports
                        │     └─ Teleport解決         │
                        └─ catch                     └─ destroy(error)
```

### 関連ファイル一覧

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