# 機能設計書 66-normalizeClass() / normalizeStyle()

## 概要

本ドキュメントは、Vue 3の`@vue/shared`パッケージが提供する`normalizeClass()`と`normalizeStyle()`関数の設計仕様を記述する。これらの関数は、クラス名やスタイル定義の様々な入力形式（文字列、配列、オブジェクト）を統一的な形式に正規化する処理を担う。

### 本機能の処理概要

`normalizeClass()`と`normalizeStyle()`は、Vueテンプレート内の`:class`バインディングや`:style`バインディングで指定される多様な入力形式を、DOMに適用可能な統一的な形式に変換する関数である。Vue 3のランタイムレンダラー、SSRレンダラー、テンプレートコンパイラから広く利用される。

**業務上の目的・背景**：Vueでは、クラスやスタイルのバインディングに文字列・配列・オブジェクトの3形式を許容している。例えば`:class="['foo', { bar: true }]"`のように混在形式も有効である。これらを最終的にDOM操作（`element.className`や`element.style`）に適用するためには、統一的な文字列形式やオブジェクト形式への正規化が必要となる。

**機能の利用シーン**：
- ランタイムレンダラーでのDOM要素へのクラス/スタイル適用時
- SSRレンダラーでのHTML属性文字列生成時
- コンポーネントpropsのマージ処理時

**主要な処理内容**：
1. `normalizeClass()`: 文字列・配列・オブジェクト形式のクラス定義を単一の文字列に正規化
2. `normalizeStyle()`: 文字列・配列・オブジェクト形式のスタイル定義を`NormalizedStyle`オブジェクトまたは文字列に正規化
3. `parseStringStyle()`: CSS文字列をプロパティ名-値のオブジェクトに解析
4. `stringifyStyle()`: NormalizedStyleオブジェクトをCSS文字列に変換
5. `normalizeProps()`: propsオブジェクト内のclass/styleを正規化

**関連システム・外部連携**：`@vue/runtime-dom`（patchClass/patchStyle）、`@vue/server-renderer`（ssrRenderClass/ssrRenderStyle）、`@vue/runtime-core`（props正規化）。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | フレームワーク内部ユーティリティ。直接的な画面関連なし |

## 機能種別

データ変換ユーティリティ

## 入力仕様

### 入力パラメータ

#### normalizeClass()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | `unknown` | Yes | クラス定義。文字列、配列、オブジェクトのいずれか | - |

#### normalizeStyle()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | `unknown` | Yes | スタイル定義。文字列、配列、オブジェクトのいずれか | - |

#### parseStringStyle()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| cssText | `string` | Yes | CSS文字列（例: "color: red; font-size: 14px"） | - |

#### stringifyStyle()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| styles | `NormalizedStyle \| string \| undefined` | Yes | スタイルオブジェクトまたは文字列 | - |

### 入力データソース

テンプレート内の`:class`/`:style`バインディング、コンポーネントprops、SSRレンダリング処理から呼び出される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| normalizeClass戻り値 | `string` | スペース区切りのクラス名文字列（末尾トリム済み） |
| normalizeStyle戻り値 | `NormalizedStyle \| string \| undefined` | 正規化済みスタイルオブジェクトまたは文字列 |
| parseStringStyle戻り値 | `NormalizedStyle` | プロパティ名-値のオブジェクト |
| stringifyStyle戻り値 | `string` | CSS文字列（例: "color:red;font-size:14px;"） |

### 出力先

呼び出し元への戻り値。

## 処理フロー

### 処理シーケンス

#### normalizeClass()
```
1. 入力値の型を判定
   ├─ 文字列 → そのまま返却
   ├─ 配列 → 各要素を再帰的にnormalizeClass()で処理し、スペースで連結
   └─ オブジェクト → truthyなキーをスペースで連結
2. 結果をtrim()して返却
```

#### normalizeStyle()
```
1. 入力値の型を判定
   ├─ 配列 → 各要素を再帰的に正規化し、オブジェクトにマージ
   │      └─ 文字列要素 → parseStringStyle()で解析
   ├─ 文字列 → そのまま返却
   └─ オブジェクト → そのまま返却
```

#### parseStringStyle()
```
1. CSSコメントを除去（styleCommentRE）
2. セミコロンで分割（括弧内のセミコロンは除外: listDelimiterRE）
3. 各プロパティをコロンで分割（propertyDelimiterRE）
4. プロパティ名と値をtrimしてオブジェクトに格納
```

### フローチャート

```mermaid
flowchart TD
    subgraph normalizeClass
        A1[入力value] --> B1{型判定}
        B1 -->|string| C1[return value]
        B1 -->|array| D1[各要素をnormalizeClass再帰呼び出し]
        D1 --> E1[スペースで連結]
        B1 -->|object| F1[truthyキーをスペースで連結]
        E1 --> G1[trim後返却]
        F1 --> G1
    end

    subgraph normalizeStyle
        A2[入力value] --> B2{型判定}
        B2 -->|array| C2[各要素を正規化してマージ]
        B2 -->|string| D2[return value]
        B2 -->|object| E2[return value]
        C2 --> F2[NormalizedStyle返却]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-66-01 | クラスのfalsy除外 | normalizeClass()でオブジェクト形式の場合、falsyな値のキーはクラス名に含まれない | オブジェクト形式のclass |
| BR-66-02 | クラスの末尾トリム | normalizeClass()の結果は末尾のスペースがtrim()される | 常時 |
| BR-66-03 | スタイル配列のマージ | normalizeStyle()で配列の場合、後の要素が前の要素を上書きする（同一プロパティ） | 配列形式のstyle |
| BR-66-04 | CSSコメント除去 | parseStringStyle()ではCSS文字列内のコメント（/* ... */）を除去する | parseStringStyle呼び出し時 |
| BR-66-05 | CSSカスタムプロパティ | stringifyStyle()で`--`プレフィックスのプロパティはhyphenate変換せずそのまま出力 | カスタムプロパティ |
| BR-66-06 | 数値/文字列値のみ出力 | stringifyStyle()では値がstring型またはnumber型の場合のみ出力する | stringifyStyle |

### 計算ロジック

`parseStringStyle()`の正規表現:
- `listDelimiterRE = /;(?![^(]*\))/g` - 括弧内のセミコロンを除外してCSS宣言を分割
- `propertyDelimiterRE = /:([^]+)/` - 最初のコロンでプロパティ名と値を分割
- `styleCommentRE = /\/\*[^]*?\*\//g` - CSSコメントを除去

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

該当なし。

## エラー処理

エラーは発生しない。不正な入力に対しては空文字列またはundefinedを返す。

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- `normalizeClass()`は配列入力時に再帰呼び出しを行うが、通常はネストが浅いため問題ない
- `parseStringStyle()`は正規表現ベースのパースで高速に動作する
- `stringifyStyle()`はhyphenate()にcacheStringFunction()によるキャッシュが適用される

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

- クラス名/スタイル値のサニタイズはこの関数では行わない。SSR出力時はescapeHtml()による追加のエスケープが必要

## 備考

- `NormalizedStyle`は`Record<string, string | number>`型として定義される
- `normalizeProps()`はpropsオブジェクト内のclass/styleフィールドを一括で正規化するヘルパー
- stringifyStyle()は末尾にセミコロンを付加するが、各プロパティ間のスペースは付加しない

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | NormalizedStyle型（3行目）: Record<string, string \| number> |

**読解のコツ**: NormalizedStyleはCSSプロパティ名をキー、値を文字列または数値とするシンプルなオブジェクト型。

#### Step 2: normalizeClass()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | normalizeClass()関数（63-82行目） |

**主要処理フロー**:
- **64行目**: 結果文字列resを初期化
- **65-66行目**: 文字列入力 → resにそのまま代入
- **67-73行目**: 配列入力 → 各要素を再帰的にnormalizeClass()で処理し、スペース付きで連結
- **74-79行目**: オブジェクト入力 → truthyな値のキーをスペース付きで連結
- **81行目**: res.trim()で末尾スペースを除去して返却

#### Step 3: normalizeStyle()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | normalizeStyle()関数（5-25行目） |

**主要処理フロー**:
- **8-21行目**: 配列入力 → 各要素を正規化（文字列はparseStringStyle()）し、結果オブジェクトにマージ
- **22-24行目**: 文字列/オブジェクト → そのまま返却

#### Step 4: parseStringStyle()とstringifyStyle()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | 正規表現定義（27-29行目）: listDelimiterRE、propertyDelimiterRE、styleCommentRE |
| 4-2 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | parseStringStyle()（31-43行目）: CSS文字列パース |
| 4-3 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | stringifyStyle()（45-61行目）: オブジェクト→CSS文字列変換 |

**主要処理フロー**:
- **33-42行目**: コメント除去→セミコロン分割→コロン分割→オブジェクトに格納
- **52-58行目**: キーを反復、`--`プレフィックスはそのまま、それ以外はhyphenate()変換。値がstring/numberの場合のみ出力

#### Step 5: normalizeProps()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | normalizeProps()関数（84-96行目）: propsのclass/styleを一括正規化 |

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

```
normalizeClass(value)
    │
    ├─ [string] return value
    ├─ [array]  normalizeClass(item) → 再帰呼び出し
    └─ [object] truthy keys → join(' ')

normalizeStyle(value)
    │
    ├─ [array]  parseStringStyle(item) / normalizeStyle(item) → マージ
    ├─ [string] return value
    └─ [object] return value

parseStringStyle(cssText)
    │
    └─ cssText.replace(commentRE).split(listDelimiterRE).split(propertyDelimiterRE)

stringifyStyle(styles)
    │
    └─ for key in styles → hyphenate(key) : value ;

normalizeProps(props)
    │
    ├─ normalizeClass(props.class)
    └─ normalizeStyle(props.style)
```

### データフロー図

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

':class="[a, {b:true}]"' ──▶ normalizeClass() ──────────▶ "a b"

':style="[str, obj]"' ──────▶ normalizeStyle() ─────────▶ NormalizedStyle

"color:red;size:14px" ──────▶ parseStringStyle() ───────▶ {color:"red", size:"14px"}

{color:"red"} ──────────────▶ stringifyStyle() ─────────▶ "color:red;"
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| normalizeProp.ts | `packages/shared/src/normalizeProp.ts` | ソース | normalizeClass/normalizeStyle/parseStringStyle/stringifyStyle/normalizePropsの定義 |
| general.ts | `packages/shared/src/general.ts` | ソース | hyphenate()、isArray()、isString()、isObject()の定義元 |
| index.ts | `packages/shared/src/index.ts` | ソース | パッケージエクスポート |
