# 帳票設計書 12-SSR NodeWritable出力

## 概要

本ドキュメントは、Vue.jsのサーバーサイドレンダリング（SSR）パッケージ `@vue/server-renderer` における `pipeToNodeWritable` 関数の出力仕様を定義する帳票設計書である。この関数は、Vueアプリケーションのレンダリング結果を既存のNode.js Writableストリームにパイプ出力する。ESMビルドでNode.jsストリームを使用する場合の推奨手段として位置づけられている。

### 本帳票の処理概要

**業務上の目的・背景**：`renderToNodeStream` 関数はCJSビルドでのみ利用可能であり、ESMビルドではNode.jsのstreamモジュールを直接requireできないという制約がある。`pipeToNodeWritable` はこの制約を回避するために設計されたAPIであり、ユーザーが自分で作成したNode.js Writableストリームインスタンスにレンダリング結果をパイプする形式を採用している。これにより、ESM環境でもNode.jsストリームベースのSSRストリーミングが可能となる。

**帳票の利用シーン**：(1) Express.jsやKoa等のNode.jsフレームワークで、レスポンスオブジェクト（WritableStream）にSSRレンダリング結果を直接パイプする場合、(2) ESMビルドのVueアプリケーションでNode.jsストリームを使用したSSRを行う場合、(3) カスタムのNode.js Writableストリーム（ファイル書き込み、ログ出力等）にレンダリング結果を出力する場合。

**主要な出力内容**：
1. VueアプリケーションまたはVNodeから生成されたHTML文字列がWritableストリームのwrite()メソッドを通じて逐次出力される
2. レンダリング完了時にWritableストリームのend()メソッドが呼ばれてストリームが正常終了する
3. エラー発生時にWritableストリームのdestroy(err)メソッドが呼ばれてストリームが異常終了する

**帳票の出力タイミング**：pipeToNodeWritable関数が呼び出された直後に非同期でレンダリングが開始される。内部でrenderToSimpleStreamを呼び出し、SimpleReadableインターフェースのアダプターを経由してNode.js WritableストリームにHTMLチャンクが逐次書き込まれる。

**帳票の利用者**：Node.jsサーバーサイドアプリケーション開発者、SSRフレームワーク（Nuxt.js等）の開発者、ESMビルド環境でSSRストリーミングを利用する開発者。

## 帳票種別

ストリーミングHTML出力（Node.js Writableストリームへのパイプ出力）

## 利用画面

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

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML文字列ストリーム（Node.js Writableストリーム経由） |
| 用紙サイズ | N/A（プログラム出力） |
| 向き | N/A |
| ファイル名 | N/A（ストリーム出力） |
| 出力方法 | Node.js Writableストリームのwrite()/end()メソッド経由 |
| 文字コード | UTF-8 |

### PDF固有設定

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

### Excel固有設定

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

## 帳票レイアウト

### レイアウト概要

出力はVueコンポーネントツリーをSSRでレンダリングしたHTML文字列であり、Node.js Writableストリームに逐次書き込まれる。pipeToNodeWritable関数はSimpleReadableインターフェースのアダプターを構築し、renderToSimpleStreamに渡すことで動作する。

```
┌─────────────────────────────────────┐
│    pipeToNodeWritable               │
│    ┌─────────────────────────────┐  │
│    │ SimpleReadableアダプター      │  │
│    │  push(content)               │  │
│    │    ├─ content != null        │  │
│    │    │   → writable.write()    │  │
│    │    └─ content == null        │  │
│    │        → writable.end()      │  │
│    │  destroy(err)                │  │
│    │    → writable.destroy(err)   │  │
│    └─────────────────────────────┘  │
│              │                      │
│              ▼                      │
│    renderToSimpleStream             │
│    （内部基盤API）                    │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | HTMLチャンク | VNodeツリーから生成されたHTML文字列断片 | renderToSimpleStream → SimpleReadable.push | writable.write(content) |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | writable.write(content) | HTML文字列チャンクのWritableストリームへの書き込み | push(content) where content != null | string | N/A |
| 2 | writable.end() | ストリーム終了 | push(null) | N/A | N/A |

### フッター部

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

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| input | Vueアプリケーションインスタンス（App）またはVNode | Yes |
| context | SSRContextオブジェクト（デフォルト: {}） | No（デフォルト値あり） |
| writable | Node.js Writableストリームインスタンス | Yes |

### ソート順

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

### 改ページ条件

N/A（ストリーム出力のため改ページの概念はない）

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

### 参照テーブル一覧

N/A（データベースアクセスは行わない）

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

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| SimpleReadable→Writableアダプター変換 | push(content) → content != null ? writable.write(content) : writable.end() | N/A | 126-143行目のインラインオブジェクトリテラルで定義 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[pipeToNodeWritable呼び出し] --> B[SimpleReadableアダプター構築]
    B --> C[renderToSimpleStream呼び出し]
    C --> D{push content}
    D -->|content != null| E[writable.write content]
    D -->|content == null| F[writable.end]
    C -->|エラー| G[writable.destroy err]
    E --> D
    F --> H[終了]
    G --> H
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| レンダリングエラー | コンポーネントのsetupやrender中に例外が発生 | エラーオブジェクトがwritable.destroy(err)に渡される | Writableストリームの'error'イベントリスナーでハンドリング |
| 非同期コンポーネントエラー | Promiseがrejectされた場合 | エラーオブジェクトがwritable.destroy(err)に渡される | Writableストリームの'error'イベントリスナーでハンドリング |

## パフォーマンス要件

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

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

- SSRContextは信頼できるサーバーサイドコードからのみアクセスされる前提
- HTMLエスケープはレンダリング処理（renderComponentVNode内）で適切に行われる
- Node.js Writableストリームの権限管理はアプリケーション側の責任

## 備考

- `pipeToNodeWritable` はESMビルドでNode.jsストリームを使用する場合の推奨APIである（renderToStream.ts 117-119行目のエラーメッセージに記載）
- contextパラメータのデフォルト値は `{}` である（128行目）
- `renderToNodeStream` がCJSビルド限定であるのに対し、`pipeToNodeWritable` はビルド形式に依存しない
- 関数の戻り値はvoid（非同期処理はrenderToSimpleStream内部で完結する）

---

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

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

### 推奨読解順序

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

Node.js WritableストリームとSimpleReadableインターフェースの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | SimpleReadableインターフェース（16-19行目）。push/destroyの2メソッド |
| 1-2 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | import文（11行目）でNode.jsのWritable型をインポート |

**読解のコツ**: pipeToNodeWritableは、SimpleReadableインターフェースをNode.js Writableストリームに橋渡しするアダプターパターンである。push(content)をwritable.write(content)に、push(null)をwritable.end()に、destroy(err)をwritable.destroy(err)にマッピングするだけのシンプルな構造。

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

pipeToNodeWritable関数の実装を理解する。

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

**主要処理フロー**:
1. **126行目**: 関数シグネチャ。input（App \| VNode）、context（SSRContext）、writable（Writable）の3引数
2. **131行目**: renderToSimpleStreamを呼び出し、SimpleReadableアダプターを第3引数として渡す
3. **132-137行目**: push(content)の実装。contentがnullでなければwritable.write(content)、nullならwritable.end()
4. **139-141行目**: destroy(err)の実装。writable.destroy(err)に委譲

#### Step 3: 内部基盤APIを理解する

renderToSimpleStreamの動作を理解する（No.11の帳票設計書を参照）。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToSimpleStream関数（56-92行目）。VNodeツリーのレンダリングとバッファ展開 |

#### Step 4: renderToNodeStreamとの違いを理解する

ESMビルドでの制約を理解し、pipeToNodeWritableが推奨される理由を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | renderToNodeStream関数（107-124行目）。__CJS__フラグによるCJSビルド限定の制約とエラーメッセージ |

**主要処理フロー**:
- **111行目**: `__CJS__`フラグでCJSビルドかチェック。CJSならrequire('node:stream').Readableを生成
- **115-121行目**: ESMビルドではstreamがnullとなりエラーをスロー。メッセージ内でpipeToNodeWritableの使用を推奨

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

```
pipeToNodeWritable(input, context, writable)
    │
    └─ renderToSimpleStream(input, context, adapter)
           │
           │  adapter = {
           │    push(content) {
           │      content != null
           │        → writable.write(content)
           │      content == null
           │        → writable.end()
           │    },
           │    destroy(err) {
           │      → writable.destroy(err)
           │    }
           │  }
           │
           ├─ renderComponentVNode(vnode)
           │      └─ SSRBuffer生成
           │
           ├─ unrollBuffer(buffer, adapter)
           │      └─ adapter.push(chunk) → writable.write(chunk)
           │
           ├─ resolveTeleports(context)
           │
           ├─ adapter.push(null) → writable.end()
           │
           └─ [catch] adapter.destroy(error) → writable.destroy(error)
```

### データフロー図

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

App | VNode ──────▶ pipeToNodeWritable ──────▶ Node.js Writable
                        │                            │
SSRContext ─────────▶    ├─ renderToSimpleStream      ├─ writable.write(chunk)
                        │     └─ SimpleReadable       │
Node.js Writable ───▶   │        アダプター経由         ├─ writable.end()
                        │                            │
                        └─ catch                     └─ writable.destroy(err)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | pipeToNodeWritable関数（126-143行目）、renderToSimpleStream関数、SimpleReadableインターフェース定義 |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | SSRBuffer/SSRContext型定義、renderComponentVNode関数 |
| renderToString.ts | `packages/server-renderer/src/renderToString.ts` | ソース | resolveTeleports関数 |
| index.ts | `packages/server-renderer/src/index.ts` | ソース | pipeToNodeWritableのpublicエクスポート（10行目） |
