# 機能設計書 60-renderToNodeStream()

## 概要

本ドキュメントは、@vue/server-rendererパッケージにおけるrenderToNodeStream()関数の処理について記述する。VueアプリケーションまたはVNodeをNode.jsのReadableStreamとしてストリーミングレンダリングする関数である。

### 本機能の処理概要

renderToNodeStream()は、Vueコンポーネントツリーをレンダリングし、結果をNode.jsのReadableStreamとして返却する。バッファを介さないストリーミング出力により、大きなページのTime to First Byte（TTFB）を削減し、メモリ使用量を抑制する。内部的にはrenderToSimpleStream()を使用し、SimpleReadableインターフェースを通じてNode.js Streamにデータをプッシュする。

**業務上の目的・背景**：renderToString()はHTML全体をメモリに保持してから返却するため、大きなページではメモリ使用量が増大し、最初のバイトが送信されるまでの時間が長くなる。renderToNodeStream()はストリーミング出力により、レンダリングが完了する前からクライアントへのデータ送信を開始でき、TTFBの短縮とメモリ効率の向上を実現する。

**機能の利用シーン**：Node.jsサーバー（Express、Fastify等）でのSSRにおいて、HTTPレスポンスにパイプして使用する。大規模なページやデータ量の多いアプリケーションで特に効果的。CJSビルドでのみ利用可能。

**主要な処理内容**：
1. Node.js ReadableStreamインスタンスの生成
2. renderToSimpleStream()による内部レンダリング処理
3. SSRバッファのストリーミング展開
4. 非同期バッファの解決とストリームへのプッシュ
5. Teleportバッファの解決
6. ウォッチャーハンドルのクリーンアップ
7. ストリームの終了（null push）

**関連システム・外部連携**：Node.jsのstream.Readableモジュール。renderToSimpleStream()による共通のストリーミング基盤。renderToWebStream()、pipeToNodeWritable()等の関連API。

**権限による制御**：該当なし（サーバー実行時処理）

## 関連画面

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

## 機能種別

サーバーサイドストリーミングレンダリング処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | App \| VNode | Yes | レンダリング対象のAppインスタンスまたはVNode | isVNode判定で自動分岐 |
| context | SSRContext | No | SSRコンテキスト | デフォルト: {} |

### 入力データソース

createApp()で生成されたVueアプリケーションインスタンス、またはcreateVNode()/h()で生成されたVNode。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| stream | Readable | Node.jsのReadableStreamインスタンス |

### 出力先

Node.jsのReadableStreamとして返却される。HTTPレスポンスにパイプして使用する（res.pipe(stream)）。

## 処理フロー

### 処理シーケンス

```
1. ReadableStreamの生成
   └─ CJSビルド: require('node:stream').Readableをインスタンス化
   └─ ESMビルド: エラーをスロー（ESMではpipeToNodeWritable()を使用）

2. renderToSimpleStream()の呼び出し
   └─ input, context, streamを渡す
   └─ SimpleReadableインターフェースとしてstreamを渡す

3. renderToSimpleStream()の内部処理
   └─ VNode入力の場合: createApp()でラップ
   └─ createVNodeでコンポーネントVNode生成
   └─ SSRコンテキスト注入

4. 非同期レンダリングチェーン
   └─ renderComponentVNode → SSRバッファ生成
   └─ unrollBuffer → バッファをストリームにプッシュ
   └─ resolveTeleports → Teleport解決
   └─ ウォッチャークリーンアップ
   └─ stream.push(null) → ストリーム終了

5. エラーハンドリング
   └─ catchでエラーキャプチャ → stream.destroy(error)
```

### フローチャート

```mermaid
flowchart TD
    A[開始: renderToNodeStream] --> B{CJSビルド?}
    B -->|Yes| C[new Readable生成]
    B -->|No| D[ESM未サポートエラー]
    C --> E[renderToSimpleStream]
    E --> F{VNode入力?}
    F -->|Yes| G[createAppラップ]
    F -->|No| H[App使用]
    G --> I[再帰呼び出し]
    H --> J[renderComponentVNode]
    J --> K[unrollBuffer → stream.push]
    K --> L[resolveTeleports]
    L --> M[ウォッチャークリーンアップ]
    M --> N[stream.push null]
    N --> O[ストリーム終了]
    D --> P[終了]
    O --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-60-01 | CJSのみ | renderToNodeStream()はCJSビルドでのみ利用可能 | ESMビルドではエラー |
| BR-60-02 | ESM代替 | ESMビルドではpipeToNodeWritable()を使用すべき | ESMビルド時 |
| BR-60-03 | VNode自動ラップ | VNode入力はcreateApp()で自動的にAppにラップ | isVNode(input) === true |
| BR-60-04 | 非同期バッファ | hasAsync=trueのバッファはawaitで解決してからプッシュ | バッファにPromiseが含まれる場合 |
| BR-60-05 | エラー伝播 | レンダリングエラーはstream.destroy(error)で伝播 | レンダリング中にエラーが発生した場合 |

### 計算ロジック

ストリーミングバッファ展開:
- `unrollBuffer(buffer, stream)` でバッファ内容をストリームにプッシュ
- 文字列アイテム: `stream.push(item)` で直接プッシュ
- Promiseアイテム: `await item` で解決後にプッシュ
- サブバッファ: 再帰的に展開
- hasAsync=falseの場合: unrollBufferSyncで同期的に展開（awaitティックを回避）

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

該当なし（サーバーサイドレンダリング処理）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | ESMビルドでrenderToNodeStream()を呼び出し | pipeToNodeWritable()を使用する |
| - | Error | レンダリング中のエラー | stream.on('error')でハンドリング |

### リトライ仕様

該当なし（エラーはstream.destroy(error)で伝播）

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

該当なし

## パフォーマンス要件

- ストリーミング出力によりTTFBを短縮
- メモリ使用量の削減（全HTMLをメモリに保持しない）
- hasAsyncフラグによる同期バッファの高速展開
- 連続する文字列の直接プッシュ（バッファリング回避）

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

- テキストコンテンツはescapeHtml()でHTMLエスケープされる（XSS対策）
- renderToString()と同等のセキュリティ対策が適用される

## 備考

- renderToStream()は非推奨であり、renderToNodeStream()に置き換えられた
- 関連API: renderToWebStream()（Web Streams API）、pipeToNodeWritable()（既存Writable使用）、pipeToWebWritable()（WritableStream使用）
- renderToSimpleStream()はすべてのストリーミングAPIの共通基盤として機能する
- CloudFlare Workers等のエッジ環境ではrenderToWebStream()が推奨される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | SimpleReadableインターフェース（push, destroy） |
| 1-2 | render.ts | `packages/server-renderer/src/render.ts` | SSRBuffer型、SSRContext型 |

**読解のコツ**: SimpleReadableインターフェースはpush(content)とdestroy(err)の2メソッドのみを持つ最小限のストリームインターフェースであり、Node.js Stream、Web Streams、カスタムストリームすべてに共通の抽象化を提供する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToNodeStream()関数 |

**主要処理フロー**:
1. **107-109行目**: 関数シグネチャ。input(App|VNode)とcontext(SSRContext)を受け取る
2. **111-113行目**: CJSビルドでReadableインスタンスを生成。read()メソッドは空（プッシュ型のため）
3. **115-121行目**: ESMビルドの場合はエラーをスロー
4. **123行目**: renderToSimpleStream(input, context, stream)を呼び出して返却

#### Step 3: renderToSimpleStream関数の詳細

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream関数 |

**主要処理フロー**:
- **56-68行目**: VNode入力のcreateAppラップと再帰呼び出し
- **71-74行目**: createVNode、appContext設定、SSRコンテキスト注入
- **76-91行目**: 非同期チェーン（renderComponentVNode→unrollBuffer→resolveTeleports→ウォッチャークリーンアップ→push(null)→catch(destroy)）

#### Step 4: ストリーミングバッファ展開

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBuffer/unrollBufferSync関数 |

**主要処理フロー**:
- **21-42行目**: unrollBuffer - hasAsync判定。非同期バッファはPromiseをawaitして解決
- **44-54行目**: unrollBufferSync - 同期バッファの高速展開

#### Step 5: 関連ストリーミングAPI

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | pipeToNodeWritable、renderToWebStream、pipeToWebWritable |

**主要処理フロー**:
- **126-143行目**: pipeToNodeWritable - 既存のWritableストリームにパイプ
- **145-180行目**: renderToWebStream - Web Streams APIのReadableStreamを生成
- **182-214行目**: pipeToWebWritable - Web Streams APIのWritableStreamにパイプ

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

```
renderToNodeStream (エントリーポイント)
    │
    ├─ new Readable (Node.js Stream)
    │
    └─ renderToSimpleStream (共通基盤)
           │
           ├─ createApp / createVNode
           │
           ├─ renderComponentVNode
           │      └─ SSRBuffer生成
           │
           ├─ unrollBuffer(buffer, stream)
           │      ├─ stream.push(文字列)
           │      └─ await Promise → 再帰
           │
           ├─ resolveTeleports
           │
           ├─ ウォッチャークリーンアップ
           │
           ├─ stream.push(null) → 終了
           │
           └─ catch → stream.destroy(error)
```

### データフロー図

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

App / VNode        ───▶ renderToNodeStream       ───▶ Readable Stream
                         │                            │
                         ├─ new Readable              ├─ .push("chunk1")
                         │                            ├─ .push("chunk2")
                         ├─ renderToSimpleStream      ├─ .push("chunk3")
                         │      │                     └─ .push(null) [完了]
                         │      ├─ renderComponentVNode
                         │      │      → SSRBuffer     HTTPレスポンス
                         │      │                      │
                         │      └─ unrollBuffer        ├─ chunk1 送信
                         │             │               ├─ chunk2 送信
                         │             └─ stream.push  └─ chunk3 送信
                         │
                         └─ stream返却
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | ストリーミングレンダリングの全API実装 |
| renderToString.ts | `packages/server-renderer/src/renderToString.ts` | ソース | 文字列レンダリング（resolveTeleportsの定義元） |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | SSRバッファ、renderComponentVNode、renderVNode |
| index.ts | `packages/server-renderer/src/index.ts` | ソース | パッケージのエクスポート定義 |
| internal.ts | `packages/server-renderer/src/internal.ts` | ソース | 内部ランタイムヘルパーのエクスポート |
