@wcstack/state 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -76,6 +76,7 @@ CDNのスクリプトはカスタム要素の定義を登録するだけで、
76
76
  - **SVG サポート** — `<svg>` 要素内でのフルバインディング対応
77
77
  - **ライフサイクルフック** — `$connectedCallback` / `$disconnectedCallback` / `$updatedCallback`、Web Component 用 `$stateReadyCallback`
78
78
  - **TypeScript サポート** — `defineState()` によるドットパス自動補完付き型付き状態定義([詳細](docs/define-state.ja.md))
79
+ - **サーバーサイドレンダリング** — `enable-ssr` 属性 + `@wcstack/server` でフル SSR と自動ハイドレーション
79
80
  - **依存ゼロ** — ランタイム依存なし
80
81
 
81
82
  ## インストール
@@ -1037,7 +1038,7 @@ customElements.define("my-component", MyComponent);
1037
1038
  timer: null,
1038
1039
  count: 0,
1039
1040
 
1040
- // <wcs-state> が DOM に接続された時に呼ばれる(async 対応)
1041
+ // <wcs-state> が DOM に接続された時に呼ばれる
1041
1042
  async $connectedCallback() {
1042
1043
  const res = await fetch("/api/initial-count");
1043
1044
  this.count = await res.json();
@@ -1055,16 +1056,16 @@ customElements.define("my-component", MyComponent);
1055
1056
 
1056
1057
  | フック | タイミング | 非同期 |
1057
1058
  |---|---|---|
1058
- | `$connectedCallback` | 初回接続時は状態初期化後、再接続時は毎回呼び出し | 可(`async` 対応) |
1059
+ | `$connectedCallback` | 初回接続時は状態初期化後、再接続時は毎回呼び出し | 可(await される) |
1059
1060
  | `$disconnectedCallback` | 要素が DOM から削除された時 | 不可(同期のみ) |
1060
- | `$updatedCallback(paths, indexesListByPath)` | 状態変更が適用された後に呼び出し | 戻り値は未使用(待機されない) |
1061
+ | `$updatedCallback(paths, indexesListByPath)` | 状態変更が適用された後に呼び出し | 可(await されない) |
1061
1062
 
1062
- リアクティブ Proxy はすべてのプロパティへの代入を変更として検知します。そのため、標準の `async/await` による処理とプロパティへの直接代入だけで非同期ロジックが完結します。ローディングフラグの切り替え、取得したデータの格納、エラーメッセージの更新といった処理もすべて単なるプロパティ代入で行えるため、非同期状態を管理するための複雑な抽象化機能は必要ありません。
1063
+ `$disconnectedCallback` を除くすべてのフックで `async` を使用できます。リアクティブ Proxy はすべてのプロパティへの代入を変更として検知します。そのため、標準の `async/await` による処理とプロパティへの直接代入だけで非同期ロジックが完結します。ローディングフラグの切り替え、取得したデータの格納、エラーメッセージの更新といった処理もすべて単なるプロパティ代入で行えるため、非同期状態を管理するための複雑な抽象化機能は必要ありません。
1063
1064
 
1064
1065
  - フック内の `this` は読み書き可能な状態プロキシです。
1065
1066
  - `$connectedCallback` は要素が接続される**たびに**呼ばれます(一度削除された後の再接続も含みます)。再確立が必要なセットアップ処理に適しています。
1066
1067
  - `$disconnectedCallback` は同期的に呼び出されます。タイマーのクリア、イベントリスナーの削除、リソースの解放といったクリーンアップ処理に使用してください。
1067
- - `$updatedCallback(paths, indexesListByPath)` は更新された状態パスの一覧を受け取ります。ワイルドカードをもつパスが更新された場合は、`indexesListByPath` から対象のインデックス情報も取得可能です。
1068
+ - `$updatedCallback(paths, indexesListByPath)` は更新された状態パスの一覧を受け取ります。ワイルドカードをもつパスが更新された場合は、`indexesListByPath` から対象のインデックス情報も取得可能です。`async` を使用できますが、戻り値は await されません。
1068
1069
  - Web Component を使用している場合は、コンポーネント側に `async $stateReadyCallback(stateProp)` を定義おくことで、`bind-component` でバインドした状態が利用可能になった瞬間にフックとして呼び出されます。
1069
1070
 
1070
1071
  ## 設定
@@ -1190,6 +1191,60 @@ buildBindings(root)
1190
1191
  - **StateAddress** — PathInfo + ListIndex の組み合わせ
1191
1192
  - **AbsoluteStateAddress** — 状態名 + StateAddress(クロス状態参照用)
1192
1193
 
1194
+ ## サーバーサイドレンダリング
1195
+
1196
+ `@wcstack/state` は [`@wcstack/server`](../server/) パッケージと連携して SSR をサポートしています。クライアント用に書いたテンプレートがそのままサーバーでレンダリングされます — 変更不要。
1197
+
1198
+ ### クイックセットアップ
1199
+
1200
+ 1. `<wcs-state>` に `enable-ssr` を追加:
1201
+
1202
+ ```html
1203
+ <wcs-state enable-ssr>
1204
+ <script type="module">
1205
+ export default {
1206
+ items: [],
1207
+ async $connectedCallback() {
1208
+ const res = await fetch("/api/items");
1209
+ this.items = await res.json();
1210
+ }
1211
+ };
1212
+ </script>
1213
+ </wcs-state>
1214
+ <template data-wcs="for: items">
1215
+ <div data-wcs="textContent: items.*.name"></div>
1216
+ </template>
1217
+ ```
1218
+
1219
+ 2. サーバーでレンダリング:
1220
+
1221
+ ```javascript
1222
+ import { renderToString } from "@wcstack/server";
1223
+
1224
+ const html = await renderToString(template, {
1225
+ baseUrl: "http://localhost:3000"
1226
+ });
1227
+ ```
1228
+
1229
+ これだけです。クライアント側の `@wcstack/state` は `<wcs-ssr>` 要素を自動検出し、JSON スナップショットから状態を復元し��再レンダリングなしでリアクティビティを再開します。
1230
+
1231
+ ### 仕組み
1232
+
1233
+ | フェーズ | 動作 |
1234
+ |---------|------|
1235
+ | **サーバー** | `renderToString()` が happy-dom でテンプレートを実行、`$connectedCallback`(`fetch()` 含む)を実行し、全バインディングを適用、ハイドレーションデータを含む `<wcs-ssr>` 要素付きのレンダリング済み HTML を出力 |
1236
+ | **クライアント** | `<wcs-state enable-ssr>` が `<wcs-ssr>` の JSON から状態をロード、`$connectedCallback` をスキップ、`hydrateBindings()` が既存の DOM にリアクティビティを接続 |
1237
+ | **フォールバック** | ���ーバー/クライアントのバージョン不一致時、SSR DOM をクリーンアップして `buildBindings()` でフルクライアントサイドレンダリングを実行 |
1238
+
1239
+ ### `enable-ssr` の動作
1240
+
1241
+ | コンテキスト | 動作 |
1242
+ |------------|------|
1243
+ | **サーバー**(`renderToString`) | 状態 JSON、テンプレートフラグメント、プロパティデータを含む `<wcs-ssr>` を生成 |
1244
+ | **クラ��アント**(ハイドレーション) | `<wcs-ssr>` を読み取り、状態を復元、`$connectedCallback` をスキップ、既存 DOM のバイン���ィングをハイドレート |
1245
+
1246
+ API の詳細は [`@wcstack/server` README](../server/README.ja.md) を参照してください。
1247
+
1193
1248
  ## ライセンス
1194
1249
 
1195
1250
  MIT
package/README.md CHANGED
@@ -76,6 +76,7 @@ That's it. No build, no bootstrap code, no framework.
76
76
  - **SVG support** — full binding support inside `<svg>` elements
77
77
  - **Lifecycle hooks** — `$connectedCallback` / `$disconnectedCallback` / `$updatedCallback`, plus `$stateReadyCallback` for Web Components
78
78
  - **TypeScript support** — `defineState()` for typed state definitions with dot-path autocompletion ([details](docs/define-state.md))
79
+ - **Server-Side Rendering** — `enable-ssr` attribute + `@wcstack/server` for full SSR with automatic hydration
79
80
  - **Zero dependencies** — no runtime dependencies
80
81
 
81
82
  ## Installation
@@ -1037,7 +1038,7 @@ State objects can define `$connectedCallback`, `$disconnectedCallback`, and `$up
1037
1038
  timer: null,
1038
1039
  count: 0,
1039
1040
 
1040
- // Called when <wcs-state> is connected to the DOM (supports async)
1041
+ // Called when <wcs-state> is connected to the DOM
1041
1042
  async $connectedCallback() {
1042
1043
  const res = await fetch("/api/initial-count");
1043
1044
  this.count = await res.json();
@@ -1055,16 +1056,16 @@ State objects can define `$connectedCallback`, `$disconnectedCallback`, and `$up
1055
1056
 
1056
1057
  | Hook | Timing | Async |
1057
1058
  |---|---|---|
1058
- | `$connectedCallback` | After state initialization on first connect; on every reconnect thereafter | Yes (`async` supported) |
1059
+ | `$connectedCallback` | After state initialization on first connect; on every reconnect thereafter | Yes (awaited) |
1059
1060
  | `$disconnectedCallback` | When the element is removed from the DOM | No (sync only) |
1060
- | `$updatedCallback(paths, indexesListByPath)` | After state updates are applied | Return value is ignored (not awaited) |
1061
+ | `$updatedCallback(paths, indexesListByPath)` | After state updates are applied | Yes (not awaited) |
1061
1062
 
1062
- Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments, without requiring additional abstractions for async state management.
1063
+ All hooks except `$disconnectedCallback` support `async` — you can use `async/await` in any of them. Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments, without requiring additional abstractions for async state management.
1063
1064
 
1064
1065
  - `this` inside hooks is the state proxy with full read/write access
1065
1066
  - `$connectedCallback` is called **every time** the element is connected (including re-insertion after removal), making it suitable for setup that should be re-established
1066
1067
  - `$disconnectedCallback` is called synchronously — use it for cleanup such as clearing timers, removing event listeners, or releasing resources
1067
- - `$updatedCallback(paths, indexesListByPath)` receives the updated path list. For wildcard updates, `indexesListByPath` contains the updated index sets
1068
+ - `$updatedCallback(paths, indexesListByPath)` receives the updated path list. For wildcard updates, `indexesListByPath` contains the updated index sets. Can be `async`, but the return value is not awaited
1068
1069
  - In Web Components, define `async $stateReadyCallback(stateProp)` to receive a hook when the bound state becomes available via `bind-component`
1069
1070
 
1070
1071
  ## Configuration
@@ -1190,6 +1191,60 @@ Paths like `users.*.name` are decomposed into:
1190
1191
  - **StateAddress** — combination of PathInfo + ListIndex
1191
1192
  - **AbsoluteStateAddress** — state name + StateAddress (for cross-state references)
1192
1193
 
1194
+ ## Server-Side Rendering
1195
+
1196
+ `@wcstack/state` supports SSR via the companion [`@wcstack/server`](../server/) package. The same templates you write for the client render on the server — no changes needed.
1197
+
1198
+ ### Quick Setup
1199
+
1200
+ 1. Add `enable-ssr` to your `<wcs-state>` element:
1201
+
1202
+ ```html
1203
+ <wcs-state enable-ssr>
1204
+ <script type="module">
1205
+ export default {
1206
+ items: [],
1207
+ async $connectedCallback() {
1208
+ const res = await fetch("/api/items");
1209
+ this.items = await res.json();
1210
+ }
1211
+ };
1212
+ </script>
1213
+ </wcs-state>
1214
+ <template data-wcs="for: items">
1215
+ <div data-wcs="textContent: items.*.name"></div>
1216
+ </template>
1217
+ ```
1218
+
1219
+ 2. Render on the server:
1220
+
1221
+ ```javascript
1222
+ import { renderToString } from "@wcstack/server";
1223
+
1224
+ const html = await renderToString(template, {
1225
+ baseUrl: "http://localhost:3000"
1226
+ });
1227
+ ```
1228
+
1229
+ That's it. The client-side `@wcstack/state` automatically detects the `<wcs-ssr>` element, restores state from the JSON snapshot, and resumes reactivity without re-rendering.
1230
+
1231
+ ### How It Works
1232
+
1233
+ | Phase | What happens |
1234
+ |-------|-------------|
1235
+ | **Server** | `renderToString()` runs your template in happy-dom, executes `$connectedCallback` (including `fetch()`), applies all bindings, and outputs rendered HTML with a `<wcs-ssr>` element containing hydration data |
1236
+ | **Client** | `<wcs-state enable-ssr>` loads state from `<wcs-ssr>` JSON, skips `$connectedCallback`, and `hydrateBindings()` wires up reactivity on the existing DOM |
1237
+ | **Fallback** | If server/client versions mismatch, the SSR DOM is cleaned up and `buildBindings()` runs a full client-side render |
1238
+
1239
+ ### What `enable-ssr` Does
1240
+
1241
+ | Context | Behavior |
1242
+ |---------|----------|
1243
+ | **Server** (`renderToString`) | Generates `<wcs-ssr>` with state JSON, template fragments, and property data |
1244
+ | **Client** (hydration) | Reads `<wcs-ssr>`, restores state, skips `$connectedCallback`, hydrates bindings on existing DOM |
1245
+
1246
+ See [`@wcstack/server` README](../server/README.md) for full API documentation.
1247
+
1193
1248
  ## License
1194
1249
 
1195
1250
  MIT
package/dist/index.d.ts CHANGED
@@ -1,5 +1,25 @@
1
+ interface IState {
2
+ [key: string]: any;
3
+ }
4
+ interface ITagNames {
5
+ readonly state: string;
6
+ readonly ssr: string;
7
+ }
1
8
  interface IWritableTagNames {
2
9
  state?: string;
10
+ ssr?: string;
11
+ }
12
+ interface IConfig {
13
+ readonly bindAttributeName: string;
14
+ readonly commentTextPrefix: string;
15
+ readonly commentForPrefix: string;
16
+ readonly commentIfPrefix: string;
17
+ readonly commentElseIfPrefix: string;
18
+ readonly commentElsePrefix: string;
19
+ readonly tagNames: ITagNames;
20
+ readonly locale: string;
21
+ readonly debug: boolean;
22
+ readonly enableMustache: boolean;
3
23
  }
4
24
  interface IWritableConfig {
5
25
  bindAttributeName?: string;
@@ -16,6 +36,82 @@ interface IWritableConfig {
16
36
 
17
37
  declare function bootstrapState(config?: IWritableConfig): void;
18
38
 
39
+ declare function getConfig(): IConfig;
40
+
41
+ /**
42
+ * 指定された rootNode のバインディング初期化が完了するまで待機する Promise を返す。
43
+ */
44
+ declare function getBindingsReady(rootNode: Node): Promise<void>;
45
+
46
+ interface ISsrElement {
47
+ readonly name: string;
48
+ readonly version: string;
49
+ readonly stateData: IState;
50
+ readonly templates: Map<string, HTMLTemplateElement>;
51
+ readonly hydrateProps: Record<string, Record<string, unknown>>;
52
+ getTemplate(uuid: string): HTMLTemplateElement | null;
53
+ verifyVersion(): boolean;
54
+ }
55
+ declare class Ssr extends HTMLElement implements ISsrElement {
56
+ private _stateData;
57
+ private _templates;
58
+ private _hydrateProps;
59
+ get name(): string;
60
+ get version(): string;
61
+ get stateData(): IState;
62
+ get templates(): Map<string, HTMLTemplateElement>;
63
+ get hydrateProps(): Record<string, Record<string, unknown>>;
64
+ getTemplate(uuid: string): HTMLTemplateElement | null;
65
+ /**
66
+ * サーバーの SSR バージョンとクライアントの state バージョンを検証する。
67
+ * メジャー・マイナーバージョンが一致すればtrue。
68
+ * version 属性がない場合は検証スキップ(true)。
69
+ */
70
+ verifyVersion(): boolean;
71
+ setStateData(data: IState): void;
72
+ setHydrateProps(props: Record<string, Record<string, unknown>>): void;
73
+ private _loadStateData;
74
+ private _loadTemplates;
75
+ private _loadHydrateProps;
76
+ static findByName(root: Node, name: string): ISsrElement | null;
77
+ /**
78
+ * stateData と構造テンプレート・プロパティから <wcs-ssr> の中身を構築する。
79
+ * server パッケージの renderToString から呼ばれる。
80
+ */
81
+ /**
82
+ * wcs-state 要素から $ プレフィックスや関数を除いたデータを抽出する。
83
+ */
84
+ static extractStateData(stateEl: Element): Record<string, any>;
85
+ static buildContent(ssrEl: Element, stateData: Record<string, any>): void;
86
+ /**
87
+ * SSR ブロック境界コメント (@@wcs-*-start/end) を除去する
88
+ */
89
+ static removeBlockBoundaryComments(root: Node): void;
90
+ /**
91
+ * SSR の構造プレースホルダーコメント (@@wcs-for:uuid 等) を除去する
92
+ */
93
+ static removeStructuralComments(root: Node): void;
94
+ /**
95
+ * SSR テキストバインディングコメントを復元する。
96
+ * <!--@@wcs-text-start:path-->text<!--@@wcs-text-end:path-->
97
+ * → <!--@@: path--> (バインディングシステムが認識する形式)
98
+ */
99
+ static restoreTextBindings(root: Node): void;
100
+ /**
101
+ * SSR DOM をクリーンアップし、buildBindings が動作できる状態に戻す。
102
+ * バージョン不一致時のフォールバック用。
103
+ *
104
+ * 1. SSR ブロック境界コメント間のレンダリング済みノードを除去
105
+ * 2. SSR テキストバインディングを @@: 形式に復元
106
+ * 3. プレースホルダーコメントを <wcs-ssr> 内のテンプレートで差し替え
107
+ * 4. data-wcs-ssr-id 属性を除去
108
+ * 5. <wcs-ssr> を除去
109
+ */
110
+ static cleanupDom(root: Document): void;
111
+ }
112
+
113
+ declare function buildBindings(root: Document | ShadowRoot): Promise<void>;
114
+
19
115
  /**
20
116
  * defineState.ts
21
117
  *
@@ -232,5 +328,7 @@ type WcsThis<T> = T & WcsStateApi & WcsPathAccessor<T>;
232
328
  */
233
329
  declare function defineState<T extends Record<string, any>>(definition: T & ThisType<WcsThis<T>>): T;
234
330
 
235
- export { bootstrapState, defineState };
236
- export type { IWritableConfig, IWritableTagNames, WcsPathValue, WcsPaths, WcsStateApi, WcsThis };
331
+ declare const VERSION: string;
332
+
333
+ export { Ssr, VERSION, bootstrapState, buildBindings, defineState, getBindingsReady, getConfig };
334
+ export type { ISsrElement, IWritableConfig, IWritableTagNames, WcsPathValue, WcsPaths, WcsStateApi, WcsThis };