# 機能設計書 61-renderToWebStream()

## 概要

本ドキュメントは、Vue 3の`@vue/server-renderer`パッケージが提供する`renderToWebStream()`関数の設計仕様を記述する。この関数は、VueコンポーネントをWeb Streams APIの`ReadableStream`としてストリーミングレンダリングする機能を提供する。

### 本機能の処理概要

`renderToWebStream()`は、VueアプリケーションまたはVNodeを入力として受け取り、HTML文字列をWeb Streams APIの`ReadableStream`オブジェクトとしてストリーミング出力する関数である。ブラウザ環境やエッジコンピューティング環境（Cloudflare Workers等）で動作するSSR（サーバーサイドレンダリング）を実現する。

**業務上の目的・背景**：従来の`renderToString()`は、コンポーネントツリー全体のレンダリングが完了するまで結果を返さないため、大規模なページでは初期応答時間（TTFB: Time To First Byte）が長くなる問題がある。`renderToWebStream()`はストリーミング出力によりレンダリング済みの部分から逐次クライアントに送信でき、TTFBを短縮する。特にWeb Streams APIに準拠しているため、ブラウザ環境やエッジランタイム（Deno、Cloudflare Workers等）でネイティブに動作する。

**機能の利用シーン**：エッジコンピューティング環境（Cloudflare Workers、Deno Deploy等）でのSSR処理、Service Worker内でのSSR、Web Streams APIを標準サポートする環境でのサーバーサイドレンダリングに使用される。

**主要な処理内容**：
1. `ReadableStream`コンストラクタの存在を確認する
2. `TextEncoder`を生成し、文字列をバイナリ（`Uint8Array`）に変換する準備をする
3. 内部で`renderToSimpleStream()`を呼び出し、コンポーネントツリーをレンダリングする
4. レンダリング結果のHTML文字列チャンクを`TextEncoder`でエンコードし、`ReadableStream`のコントローラにenqueueする
5. レンダリング完了時にストリームをcloseする
6. キャンセル処理をサポートし、クライアント切断時にレンダリングを停止する

**関連システム・外部連携**：Web Streams API（`ReadableStream`）、`TextEncoder`。内部的には`renderToSimpleStream()`に依存する。

**権限による制御**：特になし。サーバーサイドで実行されるユーティリティ関数であり、権限制御は呼び出し側が管理する。

## 関連画面

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

## 機能種別

データ変換処理（VNodeツリー -> HTML文字列ストリーム）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | `App \| VNode` | Yes | レンダリング対象のVueアプリケーションインスタンスまたはVNode | VNodeの場合は内部でcreateApp()でラップされる |
| context | `SSRContext` | No | SSRコンテキストオブジェクト。デフォルトは空オブジェクト`{}` | オブジェクト型であること |

### 入力データソース

- Vueアプリケーションインスタンス（`createApp()`または`createSSRApp()`で生成）
- 直接のVNode（`h()`や`createVNode()`で生成）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | `ReadableStream` | HTMLコンテンツをバイナリチャンク（`Uint8Array`）としてストリーミングするReadableStream |

### 出力先

Web Streams APIの`ReadableStream`オブジェクトとして返却。HTTPレスポンスのbodyとして使用されることが一般的。

## 処理フロー

### 処理シーケンス

```
1. ReadableStreamコンストラクタの存在確認
   └─ グローバルスコープにReadableStreamが存在しない場合はエラーをスロー
2. TextEncoderの生成
   └─ 文字列からUint8Arrayへの変換用
3. キャンセルフラグの初期化
   └─ cancelled = false
4. ReadableStreamの生成・返却
   └─ start()コールバック内でrenderToSimpleStream()を呼び出し
      ├─ push(content): contentがnullでなければencodeしてenqueue、nullならclose
      └─ destroy(err): controller.error(err)
5. cancel()コールバック
   └─ cancelled = true に設定し、以降のpush呼び出しを無視
```

### フローチャート

```mermaid
flowchart TD
    A[renderToWebStream呼び出し] --> B{ReadableStreamが存在?}
    B -->|No| C[Error throw]
    B -->|Yes| D[TextEncoder生成]
    D --> E[cancelled = false]
    E --> F[new ReadableStream生成]
    F --> G[start: renderToSimpleStream呼び出し]
    G --> H{push呼び出し}
    H --> I{cancelled?}
    I -->|Yes| J[return - 何もしない]
    I -->|No| K{content != null?}
    K -->|Yes| L[encoder.encode後 controller.enqueue]
    K -->|No| M[controller.close]
    G --> N{エラー発生?}
    N -->|Yes| O[controller.error]
    F --> P[ReadableStream返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-61-01 | ReadableStream存在チェック | グローバルスコープにReadableStreamコンストラクタが存在しない場合はエラーをスローする | 常時 |
| BR-61-02 | TextEncoderによるエンコード | HTML文字列はTextEncoderでUint8Arrayにエンコードしてからストリームにenqueueする | 常時 |
| BR-61-03 | キャンセルサポート | ストリームがキャンセルされた場合、以降のchunk送信を停止する | cancel()呼び出し時 |
| BR-61-04 | null push時のストリーム終了 | push(null)が呼ばれた時点でcontroller.close()によりストリームを終了する | レンダリング完了時 |

### 計算ロジック

特になし。

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

### 操作別データベース影響一覧

該当なし（データベース操作は行わない）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | ReadableStreamコンストラクタがグローバルスコープに存在しない | pipeToWebWritable()の使用を推奨するメッセージ付きエラーをスロー |
| - | レンダリングエラー | コンポーネントのレンダリング中にエラーが発生 | controller.error(err)でストリームにエラーを伝播 |

### リトライ仕様

リトライは行わない。エラー発生時はストリームにエラーを伝播し、呼び出し側で処理する。

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

該当なし。

## パフォーマンス要件

- ストリーミング出力により、レンダリング完了前からクライアントへのデータ送信を開始できる
- TTFBの短縮が主要な性能メリット
- TextEncoderによる文字列→バイナリ変換のオーバーヘッドが発生する

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

- `escapeHtml()`によるXSS対策は内部のレンダリングエンジン（`render.ts`）で実施される
- SSRコンテキストにユーザーデータを含める場合は、呼び出し側でサニタイズが必要

## 備考

- `renderToWebStream()`は`renderToSimpleStream()`のラッパーであり、Web Streams API固有のインターフェースを提供する
- Node.js環境ではNode.js 18以降でWeb Streams APIがネイティブサポートされている
- Cloudflare WorkersやDeno等のエッジランタイムでの利用が主なユースケース

---

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

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

### 推奨読解順序

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

SSRバッファの構造とSSRコンテキストの型定義を理解することが出発点となる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | render.ts | `packages/server-renderer/src/render.ts` | SSRBuffer型（42-44行目）、SSRContext型（47-58行目）の定義。バッファは文字列・子バッファ・Promiseの配列 |
| 1-2 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | SimpleReadableインターフェース（16-19行目）。push(chunk)とdestroy(err)の2メソッド |

**読解のコツ**: `SSRBuffer`は再帰的な配列構造であり、`hasAsync`フラグで非同期要素の有無を追跡している。このフラグにより同期バッファの展開を最適化している。

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

`renderToWebStream()`関数の実装を読む。

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

**主要処理フロー**:
1. **149-155行目**: ReadableStreamコンストラクタの存在チェック。存在しない場合はpipeToWebWritable()を代替として提案するエラーメッセージをスロー
2. **157行目**: TextEncoderの生成
3. **158行目**: cancelledフラグの初期化（false）
4. **160-179行目**: ReadableStreamの生成。start()内でrenderToSimpleStream()を呼び出し
5. **163-169行目**: push()コールバック。cancelledチェック後、contentがnullでなければencode→enqueue、nullならclose
6. **171-173行目**: destroy()コールバック。controller.error()でエラー伝播
7. **176-178行目**: cancel()コールバック。cancelledフラグをtrueに設定

#### Step 3: 内部ストリーミング基盤を理解する

`renderToSimpleStream()`の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream()関数（56-92行目）。VNodeの場合はcreateApp()でラップ、AppならVNodeを生成してrenderComponentVNode()を呼び出す |
| 3-2 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBuffer()関数（21-42行目）。SSRBufferをSimpleReadableに展開。非同期要素がある場合はawaitしながら展開 |

**主要処理フロー**:
- **61-68行目**: VNode入力の場合、createApp()でラップして再帰呼び出し
- **71-74行目**: Appインスタンスからコンポーネント情報を取得し、SSRコンテキストをprovide
- **76-89行目**: renderComponentVNode()→unrollBuffer()→resolveTeleports()→ウォッチャー停止→push(null)のPromiseチェーン

#### 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` | renderVNode()関数（223-279行目）。VNodeの種別に応じたHTML生成 |

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

```
renderToWebStream(input, context)
    │
    └─ new ReadableStream({ start(controller) { ... } })
           │
           └─ renderToSimpleStream(input, context, { push, destroy })
                  │
                  ├─ createVNode(input._component, input._props)
                  │
                  ├─ renderComponentVNode(vnode)
                  │      │
                  │      ├─ createComponentInstance(vnode, parent, null)
                  │      ├─ setupComponent(instance, true)
                  │      └─ renderComponentSubTree(instance)
                  │             │
                  │             ├─ createBuffer()
                  │             ├─ renderVNode(push, subTree, instance)
                  │             │      ├─ escapeHtml() [Text]
                  │             │      ├─ renderElementVNode() [Element]
                  │             │      ├─ renderComponentVNode() [Component]
                  │             │      └─ ssrRenderTeleport() [Teleport]
                  │             └─ getBuffer()
                  │
                  ├─ unrollBuffer(buffer, stream)
                  │      └─ stream.push(item) → controller.enqueue(encoder.encode(item))
                  │
                  ├─ resolveTeleports(context)
                  │
                  └─ stream.push(null) → controller.close()
```

### データフロー図

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

App / VNode ───▶ renderToWebStream() ───▶ ReadableStream
                        │
                        ├─ renderToSimpleStream()
                        │      │
                        │      └─ renderComponentVNode()
                        │             │
                        │             └─ SSRBuffer (string[])
                        │
                        ├─ unrollBuffer()
                        │      │
                        │      └─ string chunks
                        │
                        └─ TextEncoder.encode()
                               │
                               └─ Uint8Array chunks → controller.enqueue()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | renderToWebStream()の実装。SimpleReadable、unrollBuffer()も含む |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | SSRBuffer型定義、renderComponentVNode()、renderVNode()等のコアレンダリングロジック |
| renderToString.ts | `packages/server-renderer/src/renderToString.ts` | ソース | resolveTeleports()関数の定義元 |
| index.ts | `packages/server-renderer/src/index.ts` | ソース | パッケージのエクスポート定義 |
