# 帳票設計書 9-SSR Node.jsストリーム

## 概要

本ドキュメントは、Vue.jsのserver-rendererパッケージにおける`renderToNodeStream`関数が出力するNode.js Readableストリームの設計仕様を記述する。VueアプリケーションをNode.jsのReadableストリームとして逐次的にHTMLを送出する。

### 本帳票の処理概要

server-rendererの`renderToNodeStream`関数は、Vue AppインスタンスまたはVNodeを入力として受け取り、Node.jsのReadableストリームを生成して返す。内部では`renderToSimpleStream`を使用してSSRBufferを逐次的に展開し、HTMLチャンクをストリームに送出する。

**業務上の目的・背景**：大規模なHTMLページをSSRで生成する場合、全てのHTMLが揃うまで待つrenderToStringでは、TTFB（Time to First Byte）が遅延する。renderToNodeStreamはHTMLを逐次的にストリーミングすることで、ブラウザが早期にHTMLの解析・表示を開始でき、ユーザー体感速度を改善する。

**帳票の利用シーン**：Node.jsサーバーでVueアプリケーションのSSRレンダリングを行い、レスポンスをストリーミングで返したい場面。CJSビルドでのみ利用可能であり、ESMビルドではpipeToNodeWritableを使用する。

**主要な出力内容**：
1. Node.js Readableストリーム - HTMLチャンクを逐次的に含むストリーム

**帳票の出力タイミング**：サーバーサイドでHTTPリクエストを受信し、ストリーミングSSRレンダリングが必要な場面で実行される。

**帳票の利用者**：SSRフレームワーク（Nuxt.js等）、カスタムNode.jsサーバー。

## 帳票種別

レンダリング結果（ストリーム出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | SSRサーバー（Node.js） | 全てのルート | HTTPリクエスト受信時に実行 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Node.js Readable ストリーム（HTMLチャンク） |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | インメモリ（HTTPレスポンスにパイプ） |
| 出力方法 | 関数戻り値（Readable）をres.pipe等で送出 |
| 文字コード | UTF-8 |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

出力はNode.js Readableストリームで、複数のHTMLチャンクが逐次的にpushされる。

```
┌─────────────────────────────────────┐
│  Readable (Node.js Stream)          │
│                                     │
│  chunk1: "<div id=\"app\">"         │
│  chunk2: "<h1>Hello</h1>"          │
│  chunk3: "</div>"                   │
│  null (終了シグナル)                 │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | stream | Node.js Readableストリーム | new Readable({ read() {} }) | Readable |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | chunks | HTMLチャンク（文字列単位で逐次push） | unrollBuffer -> stream.push | string | - |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | null | ストリーム終了シグナル | stream.push(null) | null |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| input | Vue AppインスタンスまたはVNode | Yes |
| context | SSRContext | No（デフォルト: {}） |

### ソート順

該当なし

### 改ページ条件

該当なし

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

該当なし（インメモリレンダリング）

### 参照テーブル一覧

該当なし

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

該当なし

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| stream | new (require('node:stream').Readable)({ read() {} }) | - | CJSビルドでのみ利用可能 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[renderToNodeStream呼び出し] --> B{CJSビルド?}
    B -->|Yes| C[new Readable生成]
    B -->|No| D[エラーをスロー]
    C --> E[renderToSimpleStream呼び出し]
    E --> F[stream返却（即座に）]
    F --> G[非同期:renderComponentVNode]
    G --> H[非同期:unrollBuffer]
    H --> I[各チャンクをstream.push]
    I --> J[resolveTeleports]
    J --> K[watcherHandlesクリーンアップ]
    K --> L[stream.push(null) - 終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ESMビルドエラー | ESMビルドでrenderToNodeStreamを呼び出し | ESM build of renderToStream() does not support renderToNodeStream(). Use pipeToNodeWritable() ... | pipeToNodeWritableを使用 |
| レンダリングエラー | コンポーネント内でエラー発生 | stream.destroy(error) | コンポーネントのエラーを修正 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1リクエストにつき1回 |
| 目標出力時間 | TTFBの改善（チャンク単位で逐次送出） |
| 同時出力数上限 | サーバーリソースに依存 |

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

renderToStringと同様のセキュリティ考慮事項が適用される。ストリーミングの場合、途中でエラーが発生すると部分的なHTMLがクライアントに送信される可能性があるため、エラーハンドリングが重要。

## 備考

- `renderToNodeStream`はCJSビルド（`__CJS__`フラグ）でのみ利用可能。ESMビルドでは`pipeToNodeWritable`を使用する必要がある。
- 内部では`renderToSimpleStream`がSSRBufferの展開とストリームへのプッシュを行う。
- `renderToStream`は非推奨であり、`renderToNodeStream`への移行が推奨される。
- ストリーム終了時に`push(null)`が呼ばれ、エラー時は`stream.destroy(error)`が呼ばれる。

---

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

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

### 推奨読解順序

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

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToNodeStream関数（107-124行目）がエントリーポイント |

**主要処理フロー**:
1. **111-113行目**: CJSビルドでnew Readableを生成
2. **115-121行目**: CJSビルドでない場合はエラーをスロー
3. **123行目**: renderToSimpleStream(input, context, stream)を呼び出し

#### Step 3: 基盤処理（renderToSimpleStream）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream関数（56-92行目）- 全ストリーム系APIの共通基盤 |

**主要処理フロー**:
- **61-67行目**: VNode入力時の再帰呼び出し
- **71行目**: createVNodeでルートvnodeを生成
- **74行目**: ssrContextKeyでSSRContextを提供
- **76-89行目**: Promiseチェーンで非同期処理: renderComponentVNode -> unrollBuffer -> resolveTeleports -> watcherCleanup -> push(null)
- **87-89行目**: エラー時はstream.destroy(error)

#### Step 4: バッファのストリーム展開を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBuffer関数（21-42行目）- SSRBufferをストリームに展開 |
| 4-2 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | unrollBufferSync関数（44-54行目）- 同期バッファの効率的展開 |

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

```
renderToNodeStream (renderToStream.ts:107)
    |
    +-- new Readable({ read() {} }) [CJSのみ]
    |
    +-- renderToSimpleStream (renderToStream.ts:56)
            |
            +-- [VNode入力時] createApp -> 再帰呼び出し
            |
            +-- createVNode(input._component, input._props)
            +-- input.provide(ssrContextKey, context)
            |
            +-- renderComponentVNode (render.ts)
            |       +-- SSRBuffer を生成
            |
            +-- unrollBuffer (renderToStream.ts:21)
            |       +-- stream.push(item) [各チャンク]
            |       +-- unrollBufferSync [同期バッファ最適化]
            |
            +-- resolveTeleports (renderToString.ts)
            |
            +-- stream.push(null) [終了]
            |
            +-- [エラー時] stream.destroy(error)
```

### データフロー図

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

App / VNode          ──▶  renderToSimpleStream     ──▶  Readable (Node.js)
                                |
SSRContext                 renderComponentVNode      ──▶  SSRBuffer
                                |
                           unrollBuffer             ──▶  stream.push(chunk)
                                |                        stream.push(chunk)
                           resolveTeleports               stream.push(chunk)
                                |                        ...
                           ────────────────────────────▶  stream.push(null) [終了]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | renderToNodeStream、renderToSimpleStream、unrollBuffer |
| renderToString.ts | `packages/server-renderer/src/renderToString.ts` | ソース | resolveTeleports関数 |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | renderComponentVNode、SSRBuffer/SSRContext型 |
