@wcstack/server 1.31.0 → 1.32.0

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
@@ -75,10 +75,19 @@ console.log(renderer.html);
75
75
 
76
76
  ## API リファレンス
77
77
 
78
- ### `renderToString(html: string): Promise<string>`
78
+ ### `renderToString(html: string, options?: RenderOptions): Promise<string>`
79
79
 
80
80
  `@wcstack/state` テンプレートを含む HTML 文字列をレンダリングします。`<wcs-state enable-ssr>` を持つ要素のハイドレーションデータ付きのレンダリング済み HTML を返します。
81
81
 
82
+ **オプション:**
83
+
84
+ | オプション | 説明 |
85
+ |--------|-------------|
86
+ | `url` | このリクエストの完全 URL(例 `"http://localhost:3000/products/1"`)。`window.location` / `document.baseURI` に反映される。URL でルーティングするコンポーネントのサーバーレンダリングに必要。 |
87
+ | `baseHref` | `<head>` へ注入する `<base href>` の値。`url` 指定時の既定は `"/"`。サブパス配備では明示する。 |
88
+ | `baseUrl` | 相対 fetch URL の解決に使うベース URL。既定は `url` の origin。 |
89
+ | `bootstraps` | bootstrap 関数の配列(既定は `bootstrapState`)。非同期ローダーも渡せる — モジュールスコープで `HTMLElement` を継承するクラスを持つパッケージは純 Node でトップレベル import できないため、`async () => (await import('@wcstack/fetch')).bootstrapFetch()` のように渡す(ローダーは DOM グローバル設置後に実行される)。 |
90
+
82
91
  **レンダリングパイプライン:**
83
92
  1. happy-dom ウィンドウを作成し、ブラウザグローバルをインストール
84
93
  2. HTML をパースし、すべての `<wcs-state>` 要素の `connectedCallback` を発火
@@ -285,6 +294,28 @@ createServer(async (req, res) => {
285
294
  - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
286
295
  - 安定化ループ: await 後に DOM を再走査し、`$connectedCallback` 中に動的追加されたカスタム要素も待機(最大 10 回)
287
296
 
297
+ ### Router SSR
298
+
299
+ `<wcs-router>` に `enable-ssr` を付け、リクエストの `url` を渡します(設計: [docs/ssr-router-design.md](https://github.com/wcstack/wcstack/blob/main/docs/ssr-router-design.md)):
300
+
301
+ ```javascript
302
+ const body = await renderToString(template, {
303
+ url: `http://localhost:3000${req.url}`,
304
+ bootstraps: [
305
+ bootstrapState,
306
+ // HTMLElement を継承するクラスは純 Node でトップレベル import できないため
307
+ // 非同期ローダーで渡す(DOM グローバル設置後にモジュール評価される)
308
+ async () => (await import('@wcstack/router')).bootstrapRouter(),
309
+ ],
310
+ });
311
+ ```
312
+
313
+ - リクエスト URL の初期ルートがサーバーで描画される — 型付きパラメータ・ネストルート・ルート内容中の構造テンプレート(`for:` / `if:`)を含む。
314
+ - クライアント側 router はサーバー描画済み DOM を再描画せずに**採用(adopt)**する。採用ノード上で state のバインディングは生きたまま。サーバー出力が検証に通らない場合(URL 不一致・template 変更等)は静かに通常のクライアント描画へフォールバックする。
315
+ - `<wcs-ssr>` スナップショットはサーバー主導の最終パス(ssr-snapshot プロトコル)で生成され、読み込み順に関係なくルート内容がハイドレーションデータに載る。
316
+ - `<wcs-link>` の anchor はサーバーで描画され(`active` / `aria-current` 付き)、クライアントが採用する。
317
+ - サブパス配備は `baseHref` を渡し、クライアントにも同じ `<base href>` を配信する。
318
+
288
319
  ## SSR でできないこと
289
320
 
290
321
  - `<head>` 内の `<script src="...">` や `<link>` の自動実行
package/README.md CHANGED
@@ -75,10 +75,19 @@ console.log(renderer.html);
75
75
 
76
76
  ## API Reference
77
77
 
78
- ### `renderToString(html: string): Promise<string>`
78
+ ### `renderToString(html: string, options?: RenderOptions): Promise<string>`
79
79
 
80
80
  Renders an HTML string containing `@wcstack/state` templates. Returns fully-rendered HTML with hydration data for any `<wcs-state enable-ssr>` elements.
81
81
 
82
+ **Options:**
83
+
84
+ | Option | Description |
85
+ |--------|-------------|
86
+ | `url` | Full URL of the request (e.g. `"http://localhost:3000/products/1"`). Reflected in `window.location` / `document.baseURI`. Required for server-rendering components that route on the URL. |
87
+ | `baseHref` | Value for the `<base href>` injected into `<head>`. Defaults to `"/"` when `url` is given. Set it explicitly for sub-path deployments. |
88
+ | `baseUrl` | Base URL for resolving relative fetch URLs. Defaults to the origin of `url`. |
89
+ | `bootstraps` | Array of bootstrap functions (defaults to `bootstrapState`). Async loaders are allowed — packages whose classes extend `HTMLElement` at module scope cannot be imported top-level in plain Node, so pass e.g. `async () => (await import('@wcstack/fetch')).bootstrapFetch()`; the loader runs after DOM globals are installed. |
90
+
82
91
  **Rendering pipeline:**
83
92
  1. Creates a happy-dom window and installs browser globals
84
93
  2. Parses HTML and triggers `connectedCallback` on all `<wcs-state>` elements
@@ -285,6 +294,28 @@ createServer(async (req, res) => {
285
294
  - Automatically awaits all custom elements with `static hasConnectedCallbackPromise = true`
286
295
  - Uses a stabilization loop: after awaiting, re-scans the DOM for newly added custom elements (up to 10 iterations), ensuring elements dynamically inserted during `$connectedCallback` are also awaited
287
296
 
297
+ ### Router SSR
298
+
299
+ Put `enable-ssr` on `<wcs-router>` and pass the request `url` (design: [docs/ssr-router-design.md](https://github.com/wcstack/wcstack/blob/main/docs/ssr-router-design.md) (ja)):
300
+
301
+ ```javascript
302
+ const body = await renderToString(template, {
303
+ url: `http://localhost:3000${req.url}`,
304
+ bootstraps: [
305
+ bootstrapState,
306
+ // classes extending HTMLElement cannot be imported top-level in plain
307
+ // Node — the async loader runs after DOM globals are installed
308
+ async () => (await import('@wcstack/router')).bootstrapRouter(),
309
+ ],
310
+ });
311
+ ```
312
+
313
+ - The initial route of the request URL is rendered on the server — typed params, nested routes, and structural templates (`for:` / `if:`) inside route content included.
314
+ - The client-side router **adopts** the server-rendered DOM instead of re-rendering it; state bindings hydrated on those nodes stay live. If anything about the server output doesn't verify (URL mismatch, edited templates), the client silently falls back to normal client-side rendering.
315
+ - `<wcs-ssr>` snapshots are built as a server-orchestrated final pass (the ssr-snapshot protocol), so hydration data always includes route content regardless of load order.
316
+ - `<wcs-link>` anchors are server-rendered (with `active` / `aria-current`) and adopted on the client.
317
+ - Sub-path deployments: pass `baseHref` and serve a matching `<base href>` to the client.
318
+
288
319
  ## What SSR Cannot Do
289
320
 
290
321
  - Execute `<script src="...">` or `<link>` in `<head>`
@@ -292,6 +323,7 @@ createServer(async (req, res) => {
292
323
  - Render Shadow DOM (Declarative Shadow DOM not supported)
293
324
  - Register event handlers (restored via client-side hydration)
294
325
  - Load components dynamically via `<wcs-autoloader>`
326
+ - Server-render guarded routes (by design — the guard is an authorization point that runs client-side; the outlet is left empty), `<wcs-layout>` routes (adopted pages fall back to client-side rendering), or `<wcs-head>` contents (reflection targets `document.head`, which a body-only render cannot carry)
295
327
 
296
328
  ## HTML Splitting Pattern
297
329
 
package/dist/index.d.ts CHANGED
@@ -5,12 +5,33 @@ declare function installGlobals(window: Window): () => void;
5
5
  declare function installBaseUrl(baseUrl: string): () => void;
6
6
  /** @deprecated Use Ssr.extractStateData() from @wcstack/state instead */
7
7
  declare function extractStateData(stateEl: any): Record<string, any>;
8
- type BootstrapFunction = () => void;
8
+ /**
9
+ * 同期の bootstrap 関数、または非同期ローダー。
10
+ * `HTMLElement` を継承するクラスはモジュール評価時にグローバルの `HTMLElement` を
11
+ * 参照するため、純 Node 環境ではトップレベル import できないパッケージがある。
12
+ * その場合は `async () => (await import('@wcstack/router')).bootstrapRouter()` の
13
+ * ように非同期ローダーを渡す — 呼び出しは installGlobals の後なので、モジュール
14
+ * 評価時にはグローバルが揃っている(docs/ssr-router-design.md §3.1)。
15
+ */
16
+ type BootstrapFunction = () => void | Promise<void>;
9
17
  interface RenderOptions {
10
- /** 相対 URL を解決するベース URL (例: "http://localhost:3001") */
18
+ /** 相対 URL を解決するベース URL (例: "http://localhost:3001")。省略時は `url` の origin */
11
19
  baseUrl?: string;
12
20
  /** bootstrap 関数の配列。省略時は @wcstack/state を自動ロード */
13
21
  bootstraps?: BootstrapFunction[];
22
+ /**
23
+ * このリクエストの完全 URL (例: "http://localhost:3000/products/1")。
24
+ * `window.location` / `document.baseURI` に反映される。ルーティングする
25
+ * コンポーネント(@wcstack/router 等)のサーバーレンダリングに必要
26
+ * (docs/ssr-router-design.md §3.1)。
27
+ */
28
+ url?: string;
29
+ /**
30
+ * `<head>` へ注入する `<base href>` の値。`url` 指定時の既定は "/"。
31
+ * ブラウザで `<base>` を置く SPA と同じ条件をサーバー内に再現する
32
+ * (深い URL での basename 誤認を防ぐ)。サブパス配備では明示する。
33
+ */
34
+ baseHref?: string;
14
35
  }
15
36
  /**
16
37
  * HTML 文字列を SSR レンダリングして返す。
@@ -87,12 +108,20 @@ interface RenderOptions {
87
108
  * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
88
109
  * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)
89
110
  *
111
+ * ### router SSR
112
+ * - `<wcs-router enable-ssr>` + `url` オプションで初期ルートをサーバー描画。
113
+ * クライアント側 router は描画済み DOM を採用(adopt)する。
114
+ * 詳細は README「Router SSR」/ docs/ssr-router-design.md
115
+ *
90
116
  * ## SSR でできないこと
91
117
  * - `<head>` 内の `<script src="...">` や `<link>` の自動実行
92
118
  * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)
93
119
  * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)
94
120
  * - イベントハンドラの登録(クライアント側のハイドレーションで復元)
95
121
  * - `<wcs-autoloader>` による動的コンポーネント読み込み
122
+ * - guard 付きルートのサーバー描画(設計上・クライアントで guard 実行)、
123
+ * `<wcs-layout>` ルートの採用(クライアント描画へフォールバック)、
124
+ * `<wcs-head>` のサーバー反映(body のみの出力に head は載らない)
96
125
  *
97
126
  * ## HTML の分割パターン
98
127
  * ```
package/dist/index.esm.js CHANGED
@@ -1,5 +1,67 @@
1
1
  import { Window } from 'happy-dom';
2
2
 
3
+ // ===========================================================================
4
+ // AUTO-GENERATED FILE - DO NOT EDIT.
5
+ // Generated from /protocol/ssr-snapshot.ts by scripts/sync-protocol-types.mjs.
6
+ // Run `node scripts/sync-protocol-types.mjs` after editing the source.
7
+ // ===========================================================================
8
+ // ssr-snapshot protocol — how the SSR renderer asks whoever owns reactive
9
+ // state to build hydration snapshots (<wcs-ssr>) as a final pass, after every
10
+ // DOM inserter (router route content, late custom elements) has settled.
11
+ //
12
+ // Without this, the snapshot is built inside <wcs-state>'s connectedCallback
13
+ // and races DOM inserted by other packages: whether a route's structural
14
+ // templates make it into the snapshot depends on document order and state's
15
+ // load mechanism (docs/ssr-router-design.md §5).
16
+ //
17
+ // The provider (@wcstack/state) installs itself on a well-known global symbol
18
+ // at bootstrap. The renderer (@wcstack/server) looks the builder up after
19
+ // running bootstraps: if present it announces orchestration by setting
20
+ // `data-wcs-server="orchestrated"` on the document element BEFORE parsing, and
21
+ // calls build() right before serialization. The provider keeps its inline
22
+ // per-element fallback whenever the attribute value is anything else, so:
23
+ // - old renderer + new provider -> inline build, yesterday's behavior
24
+ // - new renderer + old provider -> no builder found, attribute stays "",
25
+ // the old provider builds inline as before
26
+ // - new renderer + new provider -> orchestrated: snapshots are built last
27
+ // and therefore always see settled DOM
28
+ //
29
+ // The symbol (rather than a package import) also pins the builder to the state
30
+ // copy that actually runs on the page — its module-scoped fragment registries
31
+ // are the ones the snapshot must read.
32
+ //
33
+ // SINGLE SOURCE OF TRUTH: edit only this file (/protocol/ssr-snapshot.ts), then
34
+ // run `node scripts/sync-protocol-types.mjs` to regenerate the per-package
35
+ // copies (packages/<pkg>/src/protocol/ssrSnapshot.ts). Those copies are
36
+ // generated — do not edit them.
37
+ /**
38
+ * Global key the snapshot builder installs itself under. `Symbol.for` so
39
+ * independently loaded copies of this file (state's and server's) still agree.
40
+ */
41
+ const SSR_SNAPSHOT_BUILDER_KEY = Symbol.for("wcstack.ssr.snapshotBuilder");
42
+ /**
43
+ * `data-wcs-server` attribute value announcing that the renderer will call the
44
+ * builder as a final pass. Providers must skip their inline per-element build
45
+ * when they see this value, and keep it for any other value (including "").
46
+ */
47
+ const SSR_ORCHESTRATED_VALUE = "orchestrated";
48
+ /**
49
+ * The installed builder, or null when there is none or it speaks a shape this
50
+ * reader does not.
51
+ */
52
+ function getSsrSnapshotBuilder() {
53
+ const candidate = globalThis[SSR_SNAPSHOT_BUILDER_KEY];
54
+ if (candidate === undefined || candidate === null)
55
+ return null;
56
+ if (candidate.protocol !== "wcs-ssr-snapshot")
57
+ return null;
58
+ if (typeof candidate.version !== "number" || candidate.version < 1)
59
+ return null;
60
+ if (typeof candidate.build !== "function")
61
+ return null;
62
+ return candidate;
63
+ }
64
+
3
65
  /**
4
66
  * globalThis を差し替える renderToString の並列実行を防止する Mutex。
5
67
  * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも
@@ -24,6 +86,14 @@ const GLOBALS_KEYS = [
24
86
  'document', 'customElements', 'HTMLElement',
25
87
  'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',
26
88
  'MutationObserver', 'ShadowRoot', 'Element', 'HTMLTemplateElement',
89
+ // URL を持つコンポーネント(@wcstack/router 等)が window.location /
90
+ // history を読めるようにする(docs/ssr-router-design.md §3.1)。
91
+ 'window', 'location', 'history',
92
+ // コンポーネントが発火するイベントをレンダリングウィンドウの realm に揃える。
93
+ // Node ネイティブの CustomEvent は happy-dom の EventTarget に拒否される
94
+ // ("parameter 1 is not of type 'Event'")。vitest の happy-dom 環境では
95
+ // グローバルが happy-dom 側なので隠れ、素の Node サーバーでだけ顕在化する
96
+ 'Event', 'CustomEvent',
27
97
  ];
28
98
  function installGlobals(window) {
29
99
  const saved = {};
@@ -152,12 +222,20 @@ async function loadDefaultBootstraps() {
152
222
  * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
153
223
  * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)
154
224
  *
225
+ * ### router SSR
226
+ * - `<wcs-router enable-ssr>` + `url` オプションで初期ルートをサーバー描画。
227
+ * クライアント側 router は描画済み DOM を採用(adopt)する。
228
+ * 詳細は README「Router SSR」/ docs/ssr-router-design.md
229
+ *
155
230
  * ## SSR でできないこと
156
231
  * - `<head>` 内の `<script src="...">` や `<link>` の自動実行
157
232
  * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)
158
233
  * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)
159
234
  * - イベントハンドラの登録(クライアント側のハイドレーションで復元)
160
235
  * - `<wcs-autoloader>` による動的コンポーネント読み込み
236
+ * - guard 付きルートのサーバー描画(設計上・クライアントで guard 実行)、
237
+ * `<wcs-layout>` ルートの採用(クライアント描画へフォールバック)、
238
+ * `<wcs-head>` のサーバー反映(body のみの出力に head は載らない)
161
239
  *
162
240
  * ## HTML の分割パターン
163
241
  * ```
@@ -177,21 +255,37 @@ async function loadDefaultBootstraps() {
177
255
  async function renderToString(html, options) {
178
256
  // globalThis を差し替えるため、同時に1つしか実行できない
179
257
  const releaseMutex = await renderMutex.acquire();
180
- const window = new Window();
258
+ const window = options?.url ? new Window({ url: options.url }) : new Window();
181
259
  const restoreGlobals = installGlobals(window);
182
260
  const document = window.document;
183
- // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール
184
- const restoreBaseUrl = options?.baseUrl
185
- ? installBaseUrl(options.baseUrl)
186
- : null;
187
- // bootstrap の解決
188
- const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();
189
- for (const bootstrap of bootstraps) {
190
- bootstrap();
191
- }
261
+ let restoreBaseUrl = null;
192
262
  try {
193
- // SSR モードを html 要素に設定
194
- document.documentElement.setAttribute('data-wcs-server', '');
263
+ // url 指定時は <base href> を注入する(既定 "/")。ブラウザで <base> を置く
264
+ // SPA と同じ条件を再現し、深い URL での basename 誤認を防ぐ
265
+ // (docs/ssr-router-design.md §3.1)。
266
+ if (options?.url !== undefined || options?.baseHref !== undefined) {
267
+ const base = document.createElement('base');
268
+ base.setAttribute('href', options.baseHref ?? '/');
269
+ document.head.appendChild(base);
270
+ }
271
+ // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール。
272
+ // baseUrl 省略時は url の origin を既定にする。
273
+ const effectiveBaseUrl = options?.baseUrl ?? (options?.url ? new URL(options.url).origin : undefined);
274
+ restoreBaseUrl = effectiveBaseUrl
275
+ ? installBaseUrl(effectiveBaseUrl)
276
+ : null;
277
+ // bootstrap の解決。非同期ローダー(BootstrapFunction 参照)を許容するため
278
+ // await する。try 内で行うのは、throw 時にもグローバル復元を保証するため。
279
+ const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();
280
+ for (const bootstrap of bootstraps) {
281
+ await bootstrap();
282
+ }
283
+ // SSR モードを html 要素に設定。snapshot builder(bootstraps の実行が
284
+ // 登録し得る — ssr-snapshot プロトコル)が居れば orchestrated を宣言し、
285
+ // <wcs-ssr> 生成をサーバー主導の最終パスへ回す(docs/ssr-router-design.md §5)。
286
+ // 値の宣言はパースより前 — 各要素は connectedCallback で値を読むため
287
+ const snapshotBuilder = getSsrSnapshotBuilder();
288
+ document.documentElement.setAttribute('data-wcs-server', snapshotBuilder !== null ? SSR_ORCHESTRATED_VALUE : '');
195
289
  // HTML をパース
196
290
  // connectedCallback が自動発火 → state ロード → $connectedCallback 実行
197
291
  document.body.innerHTML = html;
@@ -201,7 +295,6 @@ async function renderToString(html, options) {
201
295
  const MAX_ITERATIONS = 10;
202
296
  const awaitedElements = new WeakSet();
203
297
  const readyCtors = new Set();
204
- const readyPromises = [];
205
298
  for (let i = 0; i < MAX_ITERATIONS; i++) {
206
299
  const connectedPromises = [];
207
300
  for (const el of document.querySelectorAll('*-*')) {
@@ -212,20 +305,53 @@ async function renderToString(html, options) {
212
305
  awaitedElements.add(el);
213
306
  connectedPromises.push(el.connectedCallbackPromise);
214
307
  }
215
- if (!readyCtors.has(ctor) && typeof ctor.getBindingsReady === 'function') {
308
+ if (typeof ctor.getBindingsReady === 'function') {
216
309
  readyCtors.add(ctor);
217
- readyPromises.push(ctor.getBindingsReady(document));
218
310
  }
219
311
  }
220
312
  if (connectedPromises.length === 0)
221
313
  break;
222
314
  await Promise.all(connectedPromises);
223
315
  }
224
- // 非同期初期化の完了を待機
225
- await Promise.all(readyPromises);
316
+ // 非同期初期化(バインディング構築)の完了を待機。Promise の**取得**は
317
+ // 安定化ループの後に行う — getBindingsReady の実体(rootNode ごとの ready)は
318
+ // 各要素の connectedCallback 内・最初の await より後に登録されるため、ループ
319
+ // 初回の収集では「まだ登録前」の即時解決 Promise を掴むことがある。inline
320
+ // スナップショット時代は state の connectedCallback 自身が bindings を待って
321
+ // いたため隠れていた(orchestrated 化で顕在化)。取り逃すと構築の続きが
322
+ // グローバル復元後に走り、document 消失でクラッシュする
323
+ await Promise.all(Array.from(readyCtors, (ctor) => ctor.getBindingsReady(document)));
324
+ // スナップショット最終パス(orchestrated): 全要素の完了とバインディング構築の
325
+ // 後に <wcs-ssr> を生成する。inline 生成(connectedCallback 内)が取り逃がす
326
+ // 「後から挿入されたルート内容の構造テンプレート」も、この時点なら確定している
327
+ snapshotBuilder?.build(document);
226
328
  return document.body.innerHTML;
227
329
  }
228
330
  finally {
331
+ // エラー経路でも進行中のバインディング構築を待ってから globals を戻す。
332
+ // 構築は要素の connectedCallback とは独立した microtask 連鎖で走るため、
333
+ // 待たずに戻すと続きが document 消失で unhandled になりプロセスを落とす。
334
+ // 後始末はベストエフォート(rejected も含めて待つだけ待つ)
335
+ try {
336
+ const readyPending = [];
337
+ for (const el of document.querySelectorAll('*-*')) {
338
+ const ctor = el.constructor;
339
+ if (typeof ctor.getBindingsReady === 'function') {
340
+ readyPending.push(ctor.getBindingsReady(document));
341
+ }
342
+ }
343
+ await Promise.allSettled(readyPending);
344
+ }
345
+ catch { /* best effort */ }
346
+ // binder プロトコルの保留キュー(Symbol.for なので installGlobals の restore
347
+ // 対象外=プロセス寿命)を空にする。state を読み込まないページで挿入側
348
+ // (router 等)が差し出したノードは引き取り手が現れないまま蓄積するため、
349
+ // レンダリングごとに後始末する(docs/ssr-router-design.md §3.1)。
350
+ // これはプロトコルの公開シンボル面であり、パッケージ内部への依存ではない。
351
+ const pendingBinds = globalThis[Symbol.for('wcstack.binder.pending')];
352
+ if (Array.isArray(pendingBinds)) {
353
+ pendingBinds.length = 0;
354
+ }
229
355
  restoreBaseUrl?.();
230
356
  restoreGlobals();
231
357
  await window.close();
@@ -233,7 +359,7 @@ async function renderToString(html, options) {
233
359
  }
234
360
  }
235
361
 
236
- var version = "1.31.0";
362
+ var version = "1.32.0";
237
363
  var pkg = {
238
364
  version: version};
239
365
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../../src/render.ts","../../src/version.ts","../../src/RenderCore.ts"],"sourcesContent":["import { Window } from 'happy-dom';\n\n/**\n * globalThis を差し替える renderToString の並列実行を防止する Mutex。\n * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも\n * シリアライズされ、グローバル状態の衝突を防ぐ。\n */\nclass Mutex {\n private _queue: (() => void)[] = [];\n private _locked = false;\n\n async acquire(): Promise<() => void> {\n if (this._locked) {\n await new Promise<void>(resolve => this._queue.push(resolve));\n }\n this._locked = true;\n return () => {\n this._locked = false;\n this._queue.shift()?.();\n };\n }\n}\n\nconst renderMutex = new Mutex();\n\nexport const GLOBALS_KEYS = [\n 'document', 'customElements', 'HTMLElement',\n 'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',\n 'MutationObserver', 'ShadowRoot', 'Element', 'HTMLTemplateElement',\n];\n\nexport function installGlobals(window: Window): () => void {\n const saved: Record<string, any> = {};\n for (const key of GLOBALS_KEYS) {\n saved[key] = (globalThis as any)[key];\n (globalThis as any)[key] = (window as any)[key];\n }\n // URL.createObjectURL を無効化して、\n // loadFromInnerScript が base64 data: URL フォールバックを使うようにする\n const origCreateObjectURL = URL.createObjectURL;\n (URL as any).createObjectURL = undefined;\n return () => {\n URL.createObjectURL = origCreateObjectURL;\n for (const key of GLOBALS_KEYS) {\n (globalThis as any)[key] = saved[key];\n }\n };\n}\n\nexport function installBaseUrl(baseUrl: string): () => void {\n const OrigURL = globalThis.URL;\n const base = baseUrl;\n globalThis.URL = class extends OrigURL {\n constructor(input: string | URL, inputBase?: string | URL) {\n if (typeof input === 'string' && input.startsWith('/') && inputBase === undefined) {\n super(input, base);\n } else {\n super(input as string, inputBase);\n }\n }\n } as typeof URL;\n // 静的メソッドを引き継ぐ\n globalThis.URL.createObjectURL = OrigURL.createObjectURL;\n globalThis.URL.revokeObjectURL = OrigURL.revokeObjectURL;\n return () => { globalThis.URL = OrigURL; };\n}\n\n/** @deprecated Use Ssr.extractStateData() from @wcstack/state instead */\nexport function extractStateData(stateEl: any): Record<string, any> {\n const raw = (stateEl as any).__state;\n if (!raw || typeof raw !== 'object') return {};\n const data: Record<string, any> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!key.startsWith('$') && typeof value !== 'function') {\n data[key] = value;\n }\n }\n return data;\n}\n\nexport type BootstrapFunction = () => void;\n\nexport interface RenderOptions {\n /** 相対 URL を解決するベース URL (例: \"http://localhost:3001\") */\n baseUrl?: string;\n /** bootstrap 関数の配列。省略時は @wcstack/state を自動ロード */\n bootstraps?: BootstrapFunction[];\n}\n\nasync function loadDefaultBootstraps(): Promise<BootstrapFunction[]> {\n const { bootstrapState } = await import('@wcstack/state');\n return [bootstrapState];\n}\n\n\n/**\n * HTML 文字列を SSR レンダリングして返す。\n *\n * ## 入力 HTML のルール\n * - `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)\n * - `<script>` / `<link>` による外部リソース読み込みは実行されない\n * → 必要なパッケージは `options.bootstraps` で明示的に渡す\n *\n * ## SSR でできること\n *\n * ### 状態の初期化とデータ取得\n * - `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type=\"module\">`)\n * - `$connectedCallback` でのサーバーサイド fetch(API 呼び出し、DB 問い合わせ等)\n *\n * ```html\n * <!-- JSON 直接指定 -->\n * <wcs-state enable-ssr json='{\"title\":\"Hello\"}'></wcs-state>\n *\n * <!-- $connectedCallback で API からデータ取得 -->\n * <!-- $connectedCallback は状態オブジェクトのメソッドとして定義し、this が state proxy -->\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const res = await fetch('/api/users');\n * this.users = await res.json();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n *\n * ### wcs-fetch を使ったサーバー通信\n * - `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される\n * - `manual` + `$connectedCallback` で明示的に制御する場合:\n *\n * ```html\n * <wcs-fetch id=\"api\" url=\"/api/users\" manual></wcs-fetch>\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const el = document.getElementById('api');\n * this.users = await el.fetch();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n * ※ `bootstraps` に `bootstrapFetch` を含める必要あり\n *\n * ### バインディングと構造レンダリング\n * - `data-wcs` バインディングの適用(text, attribute, class, style, property)\n * - `<template data-wcs=\"for:\">` / `if:` / `elseif:` / `else:` の構造レンダリング\n *\n * ```html\n * <ul>\n * <template data-wcs=\"for: users\">\n * <li data-wcs=\"textContent: .name\"></li>\n * </template>\n * </ul>\n * <template data-wcs=\"if: isAdmin\">\n * <div class=\"admin-panel\">...</div>\n * </template>\n * ```\n *\n * ### ハイドレーション\n * - `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成\n * - クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)\n * - `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)\n *\n * ### カスタム要素の待機\n * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機\n * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)\n *\n * ## SSR でできないこと\n * - `<head>` 内の `<script src=\"...\">` や `<link>` の自動実行\n * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)\n * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)\n * - イベントハンドラの登録(クライアント側のハイドレーションで復元)\n * - `<wcs-autoloader>` による動的コンポーネント読み込み\n *\n * ## HTML の分割パターン\n * ```\n * // server.js\n * const ssrBody = await renderToString(template, { ... });\n * const page = `<!DOCTYPE html>\n * <html lang=\"ja\">\n * <head>\n * <script type=\"module\" src=\"/packages/state/dist/auto.min.js\"></script>\n * </head>\n * <body>${ssrBody}</body>\n * </html>`;\n * ```\n * `renderToString` には `<body>` の中身だけを渡し、\n * `<head>` や `<script>` タグは外側のテンプレートで囲む。\n */\nexport async function renderToString(html: string, options?: RenderOptions): Promise<string> {\n // globalThis を差し替えるため、同時に1つしか実行できない\n const releaseMutex = await renderMutex.acquire();\n\n const window = new Window();\n const restoreGlobals = installGlobals(window);\n const document = window.document;\n\n // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール\n const restoreBaseUrl = options?.baseUrl\n ? installBaseUrl(options.baseUrl)\n : null;\n\n // bootstrap の解決\n const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();\n\n for (const bootstrap of bootstraps) {\n bootstrap();\n }\n\n try {\n\n // SSR モードを html 要素に設定\n document.documentElement.setAttribute('data-wcs-server', '');\n\n // HTML をパース\n // connectedCallback が自動発火 → state ロード → $connectedCallback 実行\n document.body.innerHTML = html;\n\n // connectedCallbackPromise / getBindingsReady プロトコルを自動検出\n // $connectedCallback が動的にカスタム要素を追加する場合があるため、\n // 新しい要素が見つからなくなるまで走査を繰り返す(安定化ループ)\n const MAX_ITERATIONS = 10;\n const awaitedElements = new WeakSet();\n const readyCtors = new Set<any>();\n const readyPromises: Promise<void>[] = [];\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const connectedPromises: Promise<void>[] = [];\n\n for (const el of document.querySelectorAll('*-*')) {\n if (awaitedElements.has(el)) continue;\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\n awaitedElements.add(el);\n connectedPromises.push((el as any).connectedCallbackPromise);\n }\n if (!readyCtors.has(ctor) && typeof ctor.getBindingsReady === 'function') {\n readyCtors.add(ctor);\n readyPromises.push(ctor.getBindingsReady(document));\n }\n }\n\n if (connectedPromises.length === 0) break;\n await Promise.all(connectedPromises);\n }\n\n // 非同期初期化の完了を待機\n await Promise.all(readyPromises);\n\n return document.body.innerHTML;\n } finally {\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\n releaseMutex();\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\nexport const VERSION = pkg.version;\n","import { renderToString } from \"./render.js\";\nimport { IWcBindable } from \"./types.js\";\n\nexport class RenderCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"html\", event: \"wcs-render:html-changed\", semantics: \"state\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\", semantics: \"state\" },\n { name: \"error\", event: \"wcs-render:error\", semantics: \"state\" },\n ],\n };\n\n private _html: string | null = null;\n private _loading: boolean = false;\n private _error: Error | null = null;\n\n get html(): string | null {\n return this._html;\n }\n\n get loading(): boolean {\n return this._loading;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n private _setLoading(loading: boolean): void {\n this._loading = loading;\n this.dispatchEvent(new CustomEvent(\"wcs-render:loading-changed\", {\n detail: loading,\n }));\n }\n\n private _setHtml(html: string): void {\n this._html = html;\n this.dispatchEvent(new CustomEvent(\"wcs-render:html-changed\", {\n detail: html,\n }));\n }\n\n private _setError(error: Error | null): void {\n this._error = error;\n this.dispatchEvent(new CustomEvent(\"wcs-render:error\", {\n detail: error,\n }));\n }\n\n async render(html: string): Promise<string | null> {\n this._setLoading(true);\n this._setError(null);\n\n try {\n const result = await renderToString(html);\n this._setHtml(result);\n this._setLoading(false);\n return this._html;\n } catch (e: any) {\n this._setError(e instanceof Error ? e : new Error(String(e)));\n this._setLoading(false);\n return null;\n }\n }\n}\n"],"names":[],"mappings":";;AAEA;;;;AAIG;AACH,MAAM,KAAK,CAAA;IACD,MAAM,GAAmB,EAAE;IAC3B,OAAO,GAAG,KAAK;AAEvB,IAAA,MAAM,OAAO,GAAA;AACX,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,MAAM,IAAI,OAAO,CAAO,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/D;AACA,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;AACnB,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,CAAC,OAAO,GAAG,KAAK;AACpB,YAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI;AACzB,QAAA,CAAC;IACH;AACD;AAED,MAAM,WAAW,GAAG,IAAI,KAAK,EAAE;AAExB,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,gBAAgB,EAAE,aAAa;AAC3C,IAAA,kBAAkB,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM;AAC3D,IAAA,kBAAkB,EAAE,YAAY,EAAE,SAAS,EAAE,qBAAqB;;AAG9D,SAAU,cAAc,CAAC,MAAc,EAAA;IAC3C,MAAM,KAAK,GAAwB,EAAE;AACrC,IAAA,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE;QAC9B,KAAK,CAAC,GAAG,CAAC,GAAI,UAAkB,CAAC,GAAG,CAAC;QACpC,UAAkB,CAAC,GAAG,CAAC,GAAI,MAAc,CAAC,GAAG,CAAC;IACjD;;;AAGA,IAAA,MAAM,mBAAmB,GAAG,GAAG,CAAC,eAAe;AAC9C,IAAA,GAAW,CAAC,eAAe,GAAG,SAAS;AACxC,IAAA,OAAO,MAAK;AACV,QAAA,GAAG,CAAC,eAAe,GAAG,mBAAmB;AACzC,QAAA,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE;YAC7B,UAAkB,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC;QACvC;AACF,IAAA,CAAC;AACH;AAEM,SAAU,cAAc,CAAC,OAAe,EAAA;AAC5C,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG;IAC9B,MAAM,IAAI,GAAG,OAAO;AACpB,IAAA,UAAU,CAAC,GAAG,GAAG,cAAc,OAAO,CAAA;QACpC,WAAA,CAAY,KAAmB,EAAE,SAAwB,EAAA;AACvD,YAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,KAAK,SAAS,EAAE;AACjF,gBAAA,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;YACpB;iBAAO;AACL,gBAAA,KAAK,CAAC,KAAe,EAAE,SAAS,CAAC;YACnC;QACF;KACa;;IAEf,UAAU,CAAC,GAAG,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe;IACxD,UAAU,CAAC,GAAG,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe;IACxD,OAAO,MAAK,EAAG,UAAU,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;AAC5C;AAEA;AACM,SAAU,gBAAgB,CAAC,OAAY,EAAA;AAC3C,IAAA,MAAM,GAAG,GAAI,OAAe,CAAC,OAAO;AACpC,IAAA,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,EAAE;IAC9C,MAAM,IAAI,GAAwB,EAAE;AACpC,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;AAC9C,QAAA,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE;AACvD,YAAA,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK;QACnB;IACF;AACA,IAAA,OAAO,IAAI;AACb;AAWA,eAAe,qBAAqB,GAAA;IAClC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,OAAO,gBAAgB,CAAC;IACzD,OAAO,CAAC,cAAc,CAAC;AACzB;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGG;AACI,eAAe,cAAc,CAAC,IAAY,EAAE,OAAuB,EAAA;;AAExE,IAAA,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE;AAEhD,IAAA,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE;AAC3B,IAAA,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,CAAC;AAC7C,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ;;AAGhC,IAAA,MAAM,cAAc,GAAG,OAAO,EAAE;AAC9B,UAAE,cAAc,CAAC,OAAO,CAAC,OAAO;UAC9B,IAAI;;IAGR,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,MAAM,qBAAqB,EAAE;AAEvE,IAAA,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;AAClC,QAAA,SAAS,EAAE;IACb;AAEA,IAAA,IAAI;;QAGF,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,iBAAiB,EAAE,EAAE,CAAC;;;AAI5D,QAAA,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI;;;;QAK9B,MAAM,cAAc,GAAG,EAAE;AACzB,QAAA,MAAM,eAAe,GAAG,IAAI,OAAO,EAAE;AACrC,QAAA,MAAM,UAAU,GAAG,IAAI,GAAG,EAAO;QACjC,MAAM,aAAa,GAAoB,EAAE;AAEzC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,EAAE;YACvC,MAAM,iBAAiB,GAAoB,EAAE;YAE7C,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE;AACjD,gBAAA,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE;AAC7B,gBAAA,MAAM,IAAI,GAAG,EAAE,CAAC,WAAkB;AAClC,gBAAA,IAAI,IAAI,CAAC,2BAA2B,EAAE;AACpC,oBAAA,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;AACvB,oBAAA,iBAAiB,CAAC,IAAI,CAAE,EAAU,CAAC,wBAAwB,CAAC;gBAC9D;AACA,gBAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,gBAAgB,KAAK,UAAU,EAAE;AACxE,oBAAA,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;oBACpB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;gBACrD;YACF;AAEA,YAAA,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;gBAAE;AACpC,YAAA,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACtC;;AAGA,QAAA,MAAM,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAEhC,QAAA,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS;IAChC;YAAU;QACR,cAAc,IAAI;AAClB,QAAA,cAAc,EAAE;AAChB,QAAA,MAAM,MAAM,CAAC,KAAK,EAAE;AACpB,QAAA,YAAY,EAAE;IAChB;AACF;;;;;;AClQO,MAAM,OAAO,GAAG,GAAG,CAAC;;ACErB,MAAO,UAAW,SAAQ,WAAW,CAAA;IACzC,OAAO,UAAU,GAAgB;AAC/B,QAAA,QAAQ,EAAE,aAAa;AACvB,QAAA,OAAO,EAAE,CAAC;AACV,QAAA,UAAU,EAAE;YACV,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,yBAAyB,EAAE,SAAS,EAAE,OAAO,EAAE;YACtE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,4BAA4B,EAAE,SAAS,EAAE,OAAO,EAAE;YAC5E,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,EAAE;AACjE,SAAA;KACF;IAEO,KAAK,GAAkB,IAAI;IAC3B,QAAQ,GAAY,KAAK;IACzB,MAAM,GAAiB,IAAI;AAEnC,IAAA,IAAI,IAAI,GAAA;QACN,OAAO,IAAI,CAAC,KAAK;IACnB;AAEA,IAAA,IAAI,OAAO,GAAA;QACT,OAAO,IAAI,CAAC,QAAQ;IACtB;AAEA,IAAA,IAAI,KAAK,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;IACpB;AAEQ,IAAA,WAAW,CAAC,OAAgB,EAAA;AAClC,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO;AACvB,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,4BAA4B,EAAE;AAC/D,YAAA,MAAM,EAAE,OAAO;AAChB,SAAA,CAAC,CAAC;IACL;AAEQ,IAAA,QAAQ,CAAC,IAAY,EAAA;AAC3B,QAAA,IAAI,CAAC,KAAK,GAAG,IAAI;AACjB,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,yBAAyB,EAAE;AAC5D,YAAA,MAAM,EAAE,IAAI;AACb,SAAA,CAAC,CAAC;IACL;AAEQ,IAAA,SAAS,CAAC,KAAmB,EAAA;AACnC,QAAA,IAAI,CAAC,MAAM,GAAG,KAAK;AACnB,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,kBAAkB,EAAE;AACrD,YAAA,MAAM,EAAE,KAAK;AACd,SAAA,CAAC,CAAC;IACL;IAEA,MAAM,MAAM,CAAC,IAAY,EAAA;AACvB,QAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;AACtB,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;AAEpB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC;AACzC,YAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;AACrB,YAAA,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;YACvB,OAAO,IAAI,CAAC,KAAK;QACnB;QAAE,OAAO,CAAM,EAAE;YACf,IAAI,CAAC,SAAS,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,YAAA,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AACvB,YAAA,OAAO,IAAI;QACb;IACF;;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":["../../src/protocol/ssrSnapshot.ts","../../src/render.ts","../../src/version.ts","../../src/RenderCore.ts"],"sourcesContent":["// ===========================================================================\n// AUTO-GENERATED FILE - DO NOT EDIT.\n// Generated from /protocol/ssr-snapshot.ts by scripts/sync-protocol-types.mjs.\n// Run `node scripts/sync-protocol-types.mjs` after editing the source.\n// ===========================================================================\n\n// ssr-snapshot protocol — how the SSR renderer asks whoever owns reactive\n// state to build hydration snapshots (<wcs-ssr>) as a final pass, after every\n// DOM inserter (router route content, late custom elements) has settled.\n//\n// Without this, the snapshot is built inside <wcs-state>'s connectedCallback\n// and races DOM inserted by other packages: whether a route's structural\n// templates make it into the snapshot depends on document order and state's\n// load mechanism (docs/ssr-router-design.md §5).\n//\n// The provider (@wcstack/state) installs itself on a well-known global symbol\n// at bootstrap. The renderer (@wcstack/server) looks the builder up after\n// running bootstraps: if present it announces orchestration by setting\n// `data-wcs-server=\"orchestrated\"` on the document element BEFORE parsing, and\n// calls build() right before serialization. The provider keeps its inline\n// per-element fallback whenever the attribute value is anything else, so:\n// - old renderer + new provider -> inline build, yesterday's behavior\n// - new renderer + old provider -> no builder found, attribute stays \"\",\n// the old provider builds inline as before\n// - new renderer + new provider -> orchestrated: snapshots are built last\n// and therefore always see settled DOM\n//\n// The symbol (rather than a package import) also pins the builder to the state\n// copy that actually runs on the page — its module-scoped fragment registries\n// are the ones the snapshot must read.\n//\n// SINGLE SOURCE OF TRUTH: edit only this file (/protocol/ssr-snapshot.ts), then\n// run `node scripts/sync-protocol-types.mjs` to regenerate the per-package\n// copies (packages/<pkg>/src/protocol/ssrSnapshot.ts). Those copies are\n// generated — do not edit them.\n\n/**\n * Global key the snapshot builder installs itself under. `Symbol.for` so\n * independently loaded copies of this file (state's and server's) still agree.\n */\nexport const SSR_SNAPSHOT_BUILDER_KEY = Symbol.for(\"wcstack.ssr.snapshotBuilder\");\n\n/**\n * `data-wcs-server` attribute value announcing that the renderer will call the\n * builder as a final pass. Providers must skip their inline per-element build\n * when they see this value, and keep it for any other value (including \"\").\n */\nexport const SSR_ORCHESTRATED_VALUE = \"orchestrated\";\n\nexport interface IWcsSsrSnapshotBuilder {\n readonly protocol: \"wcs-ssr-snapshot\";\n /** Integer protocol version. All versions >= 1 are participant-compatible. */\n readonly version: number;\n /**\n * Build a hydration snapshot for every enable-ssr state element in `root`\n * that does not already have one. Called once per render, after all\n * waiting-protocol elements have settled and bindings are ready. Must be\n * idempotent with respect to already-snapshotted elements.\n */\n build(root: Document): void;\n}\n\n/**\n * The installed builder, or null when there is none or it speaks a shape this\n * reader does not.\n */\nexport function getSsrSnapshotBuilder(): IWcsSsrSnapshotBuilder | null {\n const candidate = (globalThis as Record<symbol, unknown>)[\n SSR_SNAPSHOT_BUILDER_KEY\n ] as IWcsSsrSnapshotBuilder | undefined;\n if (candidate === undefined || candidate === null) return null;\n if (candidate.protocol !== \"wcs-ssr-snapshot\") return null;\n if (typeof candidate.version !== \"number\" || candidate.version < 1) return null;\n if (typeof candidate.build !== \"function\") return null;\n return candidate;\n}\n","import { Window } from 'happy-dom';\nimport { getSsrSnapshotBuilder, SSR_ORCHESTRATED_VALUE } from './protocol/ssrSnapshot';\n\n/**\n * globalThis を差し替える renderToString の並列実行を防止する Mutex。\n * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも\n * シリアライズされ、グローバル状態の衝突を防ぐ。\n */\nclass Mutex {\n private _queue: (() => void)[] = [];\n private _locked = false;\n\n async acquire(): Promise<() => void> {\n if (this._locked) {\n await new Promise<void>(resolve => this._queue.push(resolve));\n }\n this._locked = true;\n return () => {\n this._locked = false;\n this._queue.shift()?.();\n };\n }\n}\n\nconst renderMutex = new Mutex();\n\nexport const GLOBALS_KEYS = [\n 'document', 'customElements', 'HTMLElement',\n 'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',\n 'MutationObserver', 'ShadowRoot', 'Element', 'HTMLTemplateElement',\n // URL を持つコンポーネント(@wcstack/router 等)が window.location /\n // history を読めるようにする(docs/ssr-router-design.md §3.1)。\n 'window', 'location', 'history',\n // コンポーネントが発火するイベントをレンダリングウィンドウの realm に揃える。\n // Node ネイティブの CustomEvent は happy-dom の EventTarget に拒否される\n // (\"parameter 1 is not of type 'Event'\")。vitest の happy-dom 環境では\n // グローバルが happy-dom 側なので隠れ、素の Node サーバーでだけ顕在化する\n 'Event', 'CustomEvent',\n];\n\nexport function installGlobals(window: Window): () => void {\n const saved: Record<string, any> = {};\n for (const key of GLOBALS_KEYS) {\n saved[key] = (globalThis as any)[key];\n (globalThis as any)[key] = (window as any)[key];\n }\n // URL.createObjectURL を無効化して、\n // loadFromInnerScript が base64 data: URL フォールバックを使うようにする\n const origCreateObjectURL = URL.createObjectURL;\n (URL as any).createObjectURL = undefined;\n return () => {\n URL.createObjectURL = origCreateObjectURL;\n for (const key of GLOBALS_KEYS) {\n (globalThis as any)[key] = saved[key];\n }\n };\n}\n\nexport function installBaseUrl(baseUrl: string): () => void {\n const OrigURL = globalThis.URL;\n const base = baseUrl;\n globalThis.URL = class extends OrigURL {\n constructor(input: string | URL, inputBase?: string | URL) {\n if (typeof input === 'string' && input.startsWith('/') && inputBase === undefined) {\n super(input, base);\n } else {\n super(input as string, inputBase);\n }\n }\n } as typeof URL;\n // 静的メソッドを引き継ぐ\n globalThis.URL.createObjectURL = OrigURL.createObjectURL;\n globalThis.URL.revokeObjectURL = OrigURL.revokeObjectURL;\n return () => { globalThis.URL = OrigURL; };\n}\n\n/** @deprecated Use Ssr.extractStateData() from @wcstack/state instead */\nexport function extractStateData(stateEl: any): Record<string, any> {\n const raw = (stateEl as any).__state;\n if (!raw || typeof raw !== 'object') return {};\n const data: Record<string, any> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!key.startsWith('$') && typeof value !== 'function') {\n data[key] = value;\n }\n }\n return data;\n}\n\n/**\n * 同期の bootstrap 関数、または非同期ローダー。\n * `HTMLElement` を継承するクラスはモジュール評価時にグローバルの `HTMLElement` を\n * 参照するため、純 Node 環境ではトップレベル import できないパッケージがある。\n * その場合は `async () => (await import('@wcstack/router')).bootstrapRouter()` の\n * ように非同期ローダーを渡す — 呼び出しは installGlobals の後なので、モジュール\n * 評価時にはグローバルが揃っている(docs/ssr-router-design.md §3.1)。\n */\nexport type BootstrapFunction = () => void | Promise<void>;\n\nexport interface RenderOptions {\n /** 相対 URL を解決するベース URL (例: \"http://localhost:3001\")。省略時は `url` の origin */\n baseUrl?: string;\n /** bootstrap 関数の配列。省略時は @wcstack/state を自動ロード */\n bootstraps?: BootstrapFunction[];\n /**\n * このリクエストの完全 URL (例: \"http://localhost:3000/products/1\")。\n * `window.location` / `document.baseURI` に反映される。ルーティングする\n * コンポーネント(@wcstack/router 等)のサーバーレンダリングに必要\n * (docs/ssr-router-design.md §3.1)。\n */\n url?: string;\n /**\n * `<head>` へ注入する `<base href>` の値。`url` 指定時の既定は \"/\"。\n * ブラウザで `<base>` を置く SPA と同じ条件をサーバー内に再現する\n * (深い URL での basename 誤認を防ぐ)。サブパス配備では明示する。\n */\n baseHref?: string;\n}\n\nasync function loadDefaultBootstraps(): Promise<BootstrapFunction[]> {\n const { bootstrapState } = await import('@wcstack/state');\n return [bootstrapState];\n}\n\n\n/**\n * HTML 文字列を SSR レンダリングして返す。\n *\n * ## 入力 HTML のルール\n * - `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)\n * - `<script>` / `<link>` による外部リソース読み込みは実行されない\n * → 必要なパッケージは `options.bootstraps` で明示的に渡す\n *\n * ## SSR でできること\n *\n * ### 状態の初期化とデータ取得\n * - `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type=\"module\">`)\n * - `$connectedCallback` でのサーバーサイド fetch(API 呼び出し、DB 問い合わせ等)\n *\n * ```html\n * <!-- JSON 直接指定 -->\n * <wcs-state enable-ssr json='{\"title\":\"Hello\"}'></wcs-state>\n *\n * <!-- $connectedCallback で API からデータ取得 -->\n * <!-- $connectedCallback は状態オブジェクトのメソッドとして定義し、this が state proxy -->\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const res = await fetch('/api/users');\n * this.users = await res.json();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n *\n * ### wcs-fetch を使ったサーバー通信\n * - `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される\n * - `manual` + `$connectedCallback` で明示的に制御する場合:\n *\n * ```html\n * <wcs-fetch id=\"api\" url=\"/api/users\" manual></wcs-fetch>\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const el = document.getElementById('api');\n * this.users = await el.fetch();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n * ※ `bootstraps` に `bootstrapFetch` を含める必要あり\n *\n * ### バインディングと構造レンダリング\n * - `data-wcs` バインディングの適用(text, attribute, class, style, property)\n * - `<template data-wcs=\"for:\">` / `if:` / `elseif:` / `else:` の構造レンダリング\n *\n * ```html\n * <ul>\n * <template data-wcs=\"for: users\">\n * <li data-wcs=\"textContent: .name\"></li>\n * </template>\n * </ul>\n * <template data-wcs=\"if: isAdmin\">\n * <div class=\"admin-panel\">...</div>\n * </template>\n * ```\n *\n * ### ハイドレーション\n * - `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成\n * - クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)\n * - `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)\n *\n * ### カスタム要素の待機\n * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機\n * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)\n *\n * ### router SSR\n * - `<wcs-router enable-ssr>` + `url` オプションで初期ルートをサーバー描画。\n * クライアント側 router は描画済み DOM を採用(adopt)する。\n * 詳細は README「Router SSR」/ docs/ssr-router-design.md\n *\n * ## SSR でできないこと\n * - `<head>` 内の `<script src=\"...\">` や `<link>` の自動実行\n * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)\n * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)\n * - イベントハンドラの登録(クライアント側のハイドレーションで復元)\n * - `<wcs-autoloader>` による動的コンポーネント読み込み\n * - guard 付きルートのサーバー描画(設計上・クライアントで guard 実行)、\n * `<wcs-layout>` ルートの採用(クライアント描画へフォールバック)、\n * `<wcs-head>` のサーバー反映(body のみの出力に head は載らない)\n *\n * ## HTML の分割パターン\n * ```\n * // server.js\n * const ssrBody = await renderToString(template, { ... });\n * const page = `<!DOCTYPE html>\n * <html lang=\"ja\">\n * <head>\n * <script type=\"module\" src=\"/packages/state/dist/auto.min.js\"></script>\n * </head>\n * <body>${ssrBody}</body>\n * </html>`;\n * ```\n * `renderToString` には `<body>` の中身だけを渡し、\n * `<head>` や `<script>` タグは外側のテンプレートで囲む。\n */\nexport async function renderToString(html: string, options?: RenderOptions): Promise<string> {\n // globalThis を差し替えるため、同時に1つしか実行できない\n const releaseMutex = await renderMutex.acquire();\n\n const window = options?.url ? new Window({ url: options.url }) : new Window();\n const restoreGlobals = installGlobals(window);\n const document = window.document;\n\n let restoreBaseUrl: (() => void) | null = null;\n\n try {\n // url 指定時は <base href> を注入する(既定 \"/\")。ブラウザで <base> を置く\n // SPA と同じ条件を再現し、深い URL での basename 誤認を防ぐ\n // (docs/ssr-router-design.md §3.1)。\n if (options?.url !== undefined || options?.baseHref !== undefined) {\n const base = document.createElement('base');\n base.setAttribute('href', options.baseHref ?? '/');\n document.head.appendChild(base);\n }\n\n // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール。\n // baseUrl 省略時は url の origin を既定にする。\n const effectiveBaseUrl =\n options?.baseUrl ?? (options?.url ? new URL(options.url).origin : undefined);\n restoreBaseUrl = effectiveBaseUrl\n ? installBaseUrl(effectiveBaseUrl)\n : null;\n\n // bootstrap の解決。非同期ローダー(BootstrapFunction 参照)を許容するため\n // await する。try 内で行うのは、throw 時にもグローバル復元を保証するため。\n const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();\n\n for (const bootstrap of bootstraps) {\n await bootstrap();\n }\n\n // SSR モードを html 要素に設定。snapshot builder(bootstraps の実行が\n // 登録し得る — ssr-snapshot プロトコル)が居れば orchestrated を宣言し、\n // <wcs-ssr> 生成をサーバー主導の最終パスへ回す(docs/ssr-router-design.md §5)。\n // 値の宣言はパースより前 — 各要素は connectedCallback で値を読むため\n const snapshotBuilder = getSsrSnapshotBuilder();\n document.documentElement.setAttribute(\n 'data-wcs-server',\n snapshotBuilder !== null ? SSR_ORCHESTRATED_VALUE : ''\n );\n\n // HTML をパース\n // connectedCallback が自動発火 → state ロード → $connectedCallback 実行\n document.body.innerHTML = html;\n\n // connectedCallbackPromise / getBindingsReady プロトコルを自動検出\n // $connectedCallback が動的にカスタム要素を追加する場合があるため、\n // 新しい要素が見つからなくなるまで走査を繰り返す(安定化ループ)\n const MAX_ITERATIONS = 10;\n const awaitedElements = new WeakSet();\n const readyCtors = new Set<any>();\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const connectedPromises: Promise<void>[] = [];\n\n for (const el of document.querySelectorAll('*-*')) {\n if (awaitedElements.has(el)) continue;\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\n awaitedElements.add(el);\n connectedPromises.push((el as any).connectedCallbackPromise);\n }\n if (typeof ctor.getBindingsReady === 'function') {\n readyCtors.add(ctor);\n }\n }\n\n if (connectedPromises.length === 0) break;\n await Promise.all(connectedPromises);\n }\n\n // 非同期初期化(バインディング構築)の完了を待機。Promise の**取得**は\n // 安定化ループの後に行う — getBindingsReady の実体(rootNode ごとの ready)は\n // 各要素の connectedCallback 内・最初の await より後に登録されるため、ループ\n // 初回の収集では「まだ登録前」の即時解決 Promise を掴むことがある。inline\n // スナップショット時代は state の connectedCallback 自身が bindings を待って\n // いたため隠れていた(orchestrated 化で顕在化)。取り逃すと構築の続きが\n // グローバル復元後に走り、document 消失でクラッシュする\n await Promise.all(\n Array.from(readyCtors, (ctor) => (ctor as { getBindingsReady(root: Node): Promise<void> }).getBindingsReady(document as unknown as Node))\n );\n\n // スナップショット最終パス(orchestrated): 全要素の完了とバインディング構築の\n // 後に <wcs-ssr> を生成する。inline 生成(connectedCallback 内)が取り逃がす\n // 「後から挿入されたルート内容の構造テンプレート」も、この時点なら確定している\n snapshotBuilder?.build(document as unknown as Document);\n\n return document.body.innerHTML;\n } finally {\n // エラー経路でも進行中のバインディング構築を待ってから globals を戻す。\n // 構築は要素の connectedCallback とは独立した microtask 連鎖で走るため、\n // 待たずに戻すと続きが document 消失で unhandled になりプロセスを落とす。\n // 後始末はベストエフォート(rejected も含めて待つだけ待つ)\n try {\n const readyPending: Promise<void>[] = [];\n for (const el of document.querySelectorAll('*-*')) {\n const ctor = el.constructor as { getBindingsReady?(root: Node): Promise<void> };\n if (typeof ctor.getBindingsReady === 'function') {\n readyPending.push(ctor.getBindingsReady(document as unknown as Node));\n }\n }\n await Promise.allSettled(readyPending);\n } catch { /* best effort */ }\n // binder プロトコルの保留キュー(Symbol.for なので installGlobals の restore\n // 対象外=プロセス寿命)を空にする。state を読み込まないページで挿入側\n // (router 等)が差し出したノードは引き取り手が現れないまま蓄積するため、\n // レンダリングごとに後始末する(docs/ssr-router-design.md §3.1)。\n // これはプロトコルの公開シンボル面であり、パッケージ内部への依存ではない。\n const pendingBinds = (globalThis as Record<symbol, unknown>)[\n Symbol.for('wcstack.binder.pending')\n ];\n if (Array.isArray(pendingBinds)) {\n pendingBinds.length = 0;\n }\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\n releaseMutex();\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\nexport const VERSION = pkg.version;\n","import { renderToString } from \"./render.js\";\nimport { IWcBindable } from \"./types.js\";\n\nexport class RenderCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"html\", event: \"wcs-render:html-changed\", semantics: \"state\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\", semantics: \"state\" },\n { name: \"error\", event: \"wcs-render:error\", semantics: \"state\" },\n ],\n };\n\n private _html: string | null = null;\n private _loading: boolean = false;\n private _error: Error | null = null;\n\n get html(): string | null {\n return this._html;\n }\n\n get loading(): boolean {\n return this._loading;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n private _setLoading(loading: boolean): void {\n this._loading = loading;\n this.dispatchEvent(new CustomEvent(\"wcs-render:loading-changed\", {\n detail: loading,\n }));\n }\n\n private _setHtml(html: string): void {\n this._html = html;\n this.dispatchEvent(new CustomEvent(\"wcs-render:html-changed\", {\n detail: html,\n }));\n }\n\n private _setError(error: Error | null): void {\n this._error = error;\n this.dispatchEvent(new CustomEvent(\"wcs-render:error\", {\n detail: error,\n }));\n }\n\n async render(html: string): Promise<string | null> {\n this._setLoading(true);\n this._setError(null);\n\n try {\n const result = await renderToString(html);\n this._setHtml(result);\n this._setLoading(false);\n return this._html;\n } catch (e: any) {\n this._setError(e instanceof Error ? e : new Error(String(e)));\n this._setLoading(false);\n return null;\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{Window as t}from"happy-dom";const e=new class{_queue=[];_locked=!1;async acquire(){return this._locked&&await new Promise(t=>this._queue.push(t)),this._locked=!0,()=>{this._locked=!1,this._queue.shift()?.()}}},n=["document","customElements","HTMLElement","DocumentFragment","Node","NodeFilter","Comment","Text","MutationObserver","ShadowRoot","Element","HTMLTemplateElement"];function r(t){const e={};for(const r of n)e[r]=globalThis[r],globalThis[r]=t[r];const r=URL.createObjectURL;return URL.createObjectURL=void 0,()=>{URL.createObjectURL=r;for(const t of n)globalThis[t]=e[t]}}function s(t){const e=globalThis.URL,n=t;return globalThis.URL=class extends e{constructor(t,e){"string"==typeof t&&t.startsWith("/")&&void 0===e?super(t,n):super(t,e)}},globalThis.URL.createObjectURL=e.createObjectURL,globalThis.URL.revokeObjectURL=e.revokeObjectURL,()=>{globalThis.URL=e}}function o(t){const e=t.__state;if(!e||"object"!=typeof e)return{};const n={};for(const[t,r]of Object.entries(e))t.startsWith("$")||"function"==typeof r||(n[t]=r);return n}async function a(n,o){const a=await e.acquire(),i=new t,c=r(i),l=i.document,d=o?.baseUrl?s(o.baseUrl):null,h=o?.bootstraps??await async function(){const{bootstrapState:t}=await import("@wcstack/state");return[t]}();for(const t of h)t();try{l.documentElement.setAttribute("data-wcs-server",""),l.body.innerHTML=n;const t=10,e=new WeakSet,r=new Set,s=[];for(let n=0;n<t;n++){const t=[];for(const n of l.querySelectorAll("*-*")){if(e.has(n))continue;const o=n.constructor;o.hasConnectedCallbackPromise&&(e.add(n),t.push(n.connectedCallbackPromise)),r.has(o)||"function"!=typeof o.getBindingsReady||(r.add(o),s.push(o.getBindingsReady(l)))}if(0===t.length)break;await Promise.all(t)}return await Promise.all(s),l.body.innerHTML}finally{d?.(),c(),await i.close(),a()}}const i="1.31.0";class c extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"html",event:"wcs-render:html-changed",semantics:"state"},{name:"loading",event:"wcs-render:loading-changed",semantics:"state"},{name:"error",event:"wcs-render:error",semantics:"state"}]};_html=null;_loading=!1;_error=null;get html(){return this._html}get loading(){return this._loading}get error(){return this._error}_setLoading(t){this._loading=t,this.dispatchEvent(new CustomEvent("wcs-render:loading-changed",{detail:t}))}_setHtml(t){this._html=t,this.dispatchEvent(new CustomEvent("wcs-render:html-changed",{detail:t}))}_setError(t){this._error=t,this.dispatchEvent(new CustomEvent("wcs-render:error",{detail:t}))}async render(t){this._setLoading(!0),this._setError(null);try{const e=await a(t);return this._setHtml(e),this._setLoading(!1),this._html}catch(t){return this._setError(t instanceof Error?t:new Error(String(t))),this._setLoading(!1),null}}}export{n as GLOBALS_KEYS,c as RenderCore,i as VERSION,o as extractStateData,s as installBaseUrl,r as installGlobals,a as renderToString};
1
+ import{Window as t}from"happy-dom";const e=Symbol.for("wcstack.ssr.snapshotBuilder");const n=new class{_queue=[];_locked=!1;async acquire(){return this._locked&&await new Promise(t=>this._queue.push(t)),this._locked=!0,()=>{this._locked=!1,this._queue.shift()?.()}}},o=["document","customElements","HTMLElement","DocumentFragment","Node","NodeFilter","Comment","Text","MutationObserver","ShadowRoot","Element","HTMLTemplateElement","window","location","history","Event","CustomEvent"];function r(t){const e={};for(const n of o)e[n]=globalThis[n],globalThis[n]=t[n];const n=URL.createObjectURL;return URL.createObjectURL=void 0,()=>{URL.createObjectURL=n;for(const t of o)globalThis[t]=e[t]}}function s(t){const e=globalThis.URL,n=t;return globalThis.URL=class extends e{constructor(t,e){"string"==typeof t&&t.startsWith("/")&&void 0===e?super(t,n):super(t,e)}},globalThis.URL.createObjectURL=e.createObjectURL,globalThis.URL.revokeObjectURL=e.revokeObjectURL,()=>{globalThis.URL=e}}function i(t){const e=t.__state;if(!e||"object"!=typeof e)return{};const n={};for(const[t,o]of Object.entries(e))t.startsWith("$")||"function"==typeof o||(n[t]=o);return n}async function a(o,i){const a=await n.acquire(),c=i?.url?new t({url:i.url}):new t,l=r(c),d=c.document;let u=null;try{if(void 0!==i?.url||void 0!==i?.baseHref){const t=d.createElement("base");t.setAttribute("href",i.baseHref??"/"),d.head.appendChild(t)}const t=i?.baseUrl??(i?.url?new URL(i.url).origin:void 0);u=t?s(t):null;const n=i?.bootstraps??await async function(){const{bootstrapState:t}=await import("@wcstack/state");return[t]}();for(const t of n)await t();const r=function(){const t=globalThis[e];return null==t||"wcs-ssr-snapshot"!==t.protocol||"number"!=typeof t.version||t.version<1||"function"!=typeof t.build?null:t}();d.documentElement.setAttribute("data-wcs-server",null!==r?"orchestrated":""),d.body.innerHTML=o;const a=10,c=new WeakSet,l=new Set;for(let t=0;t<a;t++){const t=[];for(const e of d.querySelectorAll("*-*")){if(c.has(e))continue;const n=e.constructor;n.hasConnectedCallbackPromise&&(c.add(e),t.push(e.connectedCallbackPromise)),"function"==typeof n.getBindingsReady&&l.add(n)}if(0===t.length)break;await Promise.all(t)}return await Promise.all(Array.from(l,t=>t.getBindingsReady(d))),r?.build(d),d.body.innerHTML}finally{try{const t=[];for(const e of d.querySelectorAll("*-*")){const n=e.constructor;"function"==typeof n.getBindingsReady&&t.push(n.getBindingsReady(d))}await Promise.allSettled(t)}catch{}const t=globalThis[Symbol.for("wcstack.binder.pending")];Array.isArray(t)&&(t.length=0),u?.(),l(),await c.close(),a()}}const c="1.32.0";class l extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"html",event:"wcs-render:html-changed",semantics:"state"},{name:"loading",event:"wcs-render:loading-changed",semantics:"state"},{name:"error",event:"wcs-render:error",semantics:"state"}]};_html=null;_loading=!1;_error=null;get html(){return this._html}get loading(){return this._loading}get error(){return this._error}_setLoading(t){this._loading=t,this.dispatchEvent(new CustomEvent("wcs-render:loading-changed",{detail:t}))}_setHtml(t){this._html=t,this.dispatchEvent(new CustomEvent("wcs-render:html-changed",{detail:t}))}_setError(t){this._error=t,this.dispatchEvent(new CustomEvent("wcs-render:error",{detail:t}))}async render(t){this._setLoading(!0),this._setError(null);try{const e=await a(t);return this._setHtml(e),this._setLoading(!1),this._html}catch(t){return this._setError(t instanceof Error?t:new Error(String(t))),this._setLoading(!1),null}}}export{o as GLOBALS_KEYS,l as RenderCore,c as VERSION,i as extractStateData,s as installBaseUrl,r as installGlobals,a as renderToString};
2
2
  //# sourceMappingURL=index.esm.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.min.js","sources":["../../src/render.ts","../../src/version.ts","../../src/RenderCore.ts"],"sourcesContent":["import { Window } from 'happy-dom';\n\n/**\n * globalThis を差し替える renderToString の並列実行を防止する Mutex。\n * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも\n * シリアライズされ、グローバル状態の衝突を防ぐ。\n */\nclass Mutex {\n private _queue: (() => void)[] = [];\n private _locked = false;\n\n async acquire(): Promise<() => void> {\n if (this._locked) {\n await new Promise<void>(resolve => this._queue.push(resolve));\n }\n this._locked = true;\n return () => {\n this._locked = false;\n this._queue.shift()?.();\n };\n }\n}\n\nconst renderMutex = new Mutex();\n\nexport const GLOBALS_KEYS = [\n 'document', 'customElements', 'HTMLElement',\n 'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',\n 'MutationObserver', 'ShadowRoot', 'Element', 'HTMLTemplateElement',\n];\n\nexport function installGlobals(window: Window): () => void {\n const saved: Record<string, any> = {};\n for (const key of GLOBALS_KEYS) {\n saved[key] = (globalThis as any)[key];\n (globalThis as any)[key] = (window as any)[key];\n }\n // URL.createObjectURL を無効化して、\n // loadFromInnerScript が base64 data: URL フォールバックを使うようにする\n const origCreateObjectURL = URL.createObjectURL;\n (URL as any).createObjectURL = undefined;\n return () => {\n URL.createObjectURL = origCreateObjectURL;\n for (const key of GLOBALS_KEYS) {\n (globalThis as any)[key] = saved[key];\n }\n };\n}\n\nexport function installBaseUrl(baseUrl: string): () => void {\n const OrigURL = globalThis.URL;\n const base = baseUrl;\n globalThis.URL = class extends OrigURL {\n constructor(input: string | URL, inputBase?: string | URL) {\n if (typeof input === 'string' && input.startsWith('/') && inputBase === undefined) {\n super(input, base);\n } else {\n super(input as string, inputBase);\n }\n }\n } as typeof URL;\n // 静的メソッドを引き継ぐ\n globalThis.URL.createObjectURL = OrigURL.createObjectURL;\n globalThis.URL.revokeObjectURL = OrigURL.revokeObjectURL;\n return () => { globalThis.URL = OrigURL; };\n}\n\n/** @deprecated Use Ssr.extractStateData() from @wcstack/state instead */\nexport function extractStateData(stateEl: any): Record<string, any> {\n const raw = (stateEl as any).__state;\n if (!raw || typeof raw !== 'object') return {};\n const data: Record<string, any> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!key.startsWith('$') && typeof value !== 'function') {\n data[key] = value;\n }\n }\n return data;\n}\n\nexport type BootstrapFunction = () => void;\n\nexport interface RenderOptions {\n /** 相対 URL を解決するベース URL (例: \"http://localhost:3001\") */\n baseUrl?: string;\n /** bootstrap 関数の配列。省略時は @wcstack/state を自動ロード */\n bootstraps?: BootstrapFunction[];\n}\n\nasync function loadDefaultBootstraps(): Promise<BootstrapFunction[]> {\n const { bootstrapState } = await import('@wcstack/state');\n return [bootstrapState];\n}\n\n\n/**\n * HTML 文字列を SSR レンダリングして返す。\n *\n * ## 入力 HTML のルール\n * - `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)\n * - `<script>` / `<link>` による外部リソース読み込みは実行されない\n * → 必要なパッケージは `options.bootstraps` で明示的に渡す\n *\n * ## SSR でできること\n *\n * ### 状態の初期化とデータ取得\n * - `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type=\"module\">`)\n * - `$connectedCallback` でのサーバーサイド fetch(API 呼び出し、DB 問い合わせ等)\n *\n * ```html\n * <!-- JSON 直接指定 -->\n * <wcs-state enable-ssr json='{\"title\":\"Hello\"}'></wcs-state>\n *\n * <!-- $connectedCallback で API からデータ取得 -->\n * <!-- $connectedCallback は状態オブジェクトのメソッドとして定義し、this が state proxy -->\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const res = await fetch('/api/users');\n * this.users = await res.json();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n *\n * ### wcs-fetch を使ったサーバー通信\n * - `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される\n * - `manual` + `$connectedCallback` で明示的に制御する場合:\n *\n * ```html\n * <wcs-fetch id=\"api\" url=\"/api/users\" manual></wcs-fetch>\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const el = document.getElementById('api');\n * this.users = await el.fetch();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n * ※ `bootstraps` に `bootstrapFetch` を含める必要あり\n *\n * ### バインディングと構造レンダリング\n * - `data-wcs` バインディングの適用(text, attribute, class, style, property)\n * - `<template data-wcs=\"for:\">` / `if:` / `elseif:` / `else:` の構造レンダリング\n *\n * ```html\n * <ul>\n * <template data-wcs=\"for: users\">\n * <li data-wcs=\"textContent: .name\"></li>\n * </template>\n * </ul>\n * <template data-wcs=\"if: isAdmin\">\n * <div class=\"admin-panel\">...</div>\n * </template>\n * ```\n *\n * ### ハイドレーション\n * - `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成\n * - クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)\n * - `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)\n *\n * ### カスタム要素の待機\n * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機\n * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)\n *\n * ## SSR でできないこと\n * - `<head>` 内の `<script src=\"...\">` や `<link>` の自動実行\n * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)\n * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)\n * - イベントハンドラの登録(クライアント側のハイドレーションで復元)\n * - `<wcs-autoloader>` による動的コンポーネント読み込み\n *\n * ## HTML の分割パターン\n * ```\n * // server.js\n * const ssrBody = await renderToString(template, { ... });\n * const page = `<!DOCTYPE html>\n * <html lang=\"ja\">\n * <head>\n * <script type=\"module\" src=\"/packages/state/dist/auto.min.js\"></script>\n * </head>\n * <body>${ssrBody}</body>\n * </html>`;\n * ```\n * `renderToString` には `<body>` の中身だけを渡し、\n * `<head>` や `<script>` タグは外側のテンプレートで囲む。\n */\nexport async function renderToString(html: string, options?: RenderOptions): Promise<string> {\n // globalThis を差し替えるため、同時に1つしか実行できない\n const releaseMutex = await renderMutex.acquire();\n\n const window = new Window();\n const restoreGlobals = installGlobals(window);\n const document = window.document;\n\n // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール\n const restoreBaseUrl = options?.baseUrl\n ? installBaseUrl(options.baseUrl)\n : null;\n\n // bootstrap の解決\n const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();\n\n for (const bootstrap of bootstraps) {\n bootstrap();\n }\n\n try {\n\n // SSR モードを html 要素に設定\n document.documentElement.setAttribute('data-wcs-server', '');\n\n // HTML をパース\n // connectedCallback が自動発火 → state ロード → $connectedCallback 実行\n document.body.innerHTML = html;\n\n // connectedCallbackPromise / getBindingsReady プロトコルを自動検出\n // $connectedCallback が動的にカスタム要素を追加する場合があるため、\n // 新しい要素が見つからなくなるまで走査を繰り返す(安定化ループ)\n const MAX_ITERATIONS = 10;\n const awaitedElements = new WeakSet();\n const readyCtors = new Set<any>();\n const readyPromises: Promise<void>[] = [];\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const connectedPromises: Promise<void>[] = [];\n\n for (const el of document.querySelectorAll('*-*')) {\n if (awaitedElements.has(el)) continue;\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\n awaitedElements.add(el);\n connectedPromises.push((el as any).connectedCallbackPromise);\n }\n if (!readyCtors.has(ctor) && typeof ctor.getBindingsReady === 'function') {\n readyCtors.add(ctor);\n readyPromises.push(ctor.getBindingsReady(document));\n }\n }\n\n if (connectedPromises.length === 0) break;\n await Promise.all(connectedPromises);\n }\n\n // 非同期初期化の完了を待機\n await Promise.all(readyPromises);\n\n return document.body.innerHTML;\n } finally {\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\n releaseMutex();\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\nexport const VERSION = pkg.version;\n","import { renderToString } from \"./render.js\";\nimport { IWcBindable } from \"./types.js\";\n\nexport class RenderCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"html\", event: \"wcs-render:html-changed\", semantics: \"state\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\", semantics: \"state\" },\n { name: \"error\", event: \"wcs-render:error\", semantics: \"state\" },\n ],\n };\n\n private _html: string | null = null;\n private _loading: boolean = false;\n private _error: Error | null = null;\n\n get html(): string | null {\n return this._html;\n }\n\n get loading(): boolean {\n return this._loading;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n private _setLoading(loading: boolean): void {\n this._loading = loading;\n this.dispatchEvent(new CustomEvent(\"wcs-render:loading-changed\", {\n detail: loading,\n }));\n }\n\n private _setHtml(html: string): void {\n this._html = html;\n this.dispatchEvent(new CustomEvent(\"wcs-render:html-changed\", {\n detail: html,\n }));\n }\n\n private _setError(error: Error | null): void {\n this._error = error;\n this.dispatchEvent(new CustomEvent(\"wcs-render:error\", {\n detail: error,\n }));\n }\n\n async render(html: string): Promise<string | null> {\n this._setLoading(true);\n this._setError(null);\n\n try {\n const result = await renderToString(html);\n this._setHtml(result);\n this._setLoading(false);\n return this._html;\n } catch (e: any) {\n this._setError(e instanceof Error ? e : new Error(String(e)));\n this._setLoading(false);\n return null;\n }\n }\n}\n"],"names":["renderMutex","_queue","_locked","acquire","this","Promise","resolve","push","shift","GLOBALS_KEYS","installGlobals","window","saved","key","globalThis","origCreateObjectURL","URL","createObjectURL","undefined","installBaseUrl","baseUrl","OrigURL","base","constructor","input","inputBase","startsWith","super","revokeObjectURL","extractStateData","stateEl","raw","__state","data","value","Object","entries","async","renderToString","html","options","releaseMutex","Window","restoreGlobals","document","restoreBaseUrl","bootstraps","bootstrapState","import","loadDefaultBootstraps","bootstrap","documentElement","setAttribute","body","innerHTML","MAX_ITERATIONS","awaitedElements","WeakSet","readyCtors","Set","readyPromises","i","connectedPromises","el","querySelectorAll","has","ctor","hasConnectedCallbackPromise","add","connectedCallbackPromise","getBindingsReady","length","all","close","VERSION","RenderCore","EventTarget","static","protocol","version","properties","name","event","semantics","_html","_loading","_error","loading","error","_setLoading","dispatchEvent","CustomEvent","detail","_setHtml","_setError","render","result","e","Error","String"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.min.js","sources":["../../src/protocol/ssrSnapshot.ts","../../src/render.ts","../../src/version.ts","../../src/RenderCore.ts"],"sourcesContent":["// ===========================================================================\n// AUTO-GENERATED FILE - DO NOT EDIT.\n// Generated from /protocol/ssr-snapshot.ts by scripts/sync-protocol-types.mjs.\n// Run `node scripts/sync-protocol-types.mjs` after editing the source.\n// ===========================================================================\n\n// ssr-snapshot protocol — how the SSR renderer asks whoever owns reactive\n// state to build hydration snapshots (<wcs-ssr>) as a final pass, after every\n// DOM inserter (router route content, late custom elements) has settled.\n//\n// Without this, the snapshot is built inside <wcs-state>'s connectedCallback\n// and races DOM inserted by other packages: whether a route's structural\n// templates make it into the snapshot depends on document order and state's\n// load mechanism (docs/ssr-router-design.md §5).\n//\n// The provider (@wcstack/state) installs itself on a well-known global symbol\n// at bootstrap. The renderer (@wcstack/server) looks the builder up after\n// running bootstraps: if present it announces orchestration by setting\n// `data-wcs-server=\"orchestrated\"` on the document element BEFORE parsing, and\n// calls build() right before serialization. The provider keeps its inline\n// per-element fallback whenever the attribute value is anything else, so:\n// - old renderer + new provider -> inline build, yesterday's behavior\n// - new renderer + old provider -> no builder found, attribute stays \"\",\n// the old provider builds inline as before\n// - new renderer + new provider -> orchestrated: snapshots are built last\n// and therefore always see settled DOM\n//\n// The symbol (rather than a package import) also pins the builder to the state\n// copy that actually runs on the page — its module-scoped fragment registries\n// are the ones the snapshot must read.\n//\n// SINGLE SOURCE OF TRUTH: edit only this file (/protocol/ssr-snapshot.ts), then\n// run `node scripts/sync-protocol-types.mjs` to regenerate the per-package\n// copies (packages/<pkg>/src/protocol/ssrSnapshot.ts). Those copies are\n// generated — do not edit them.\n\n/**\n * Global key the snapshot builder installs itself under. `Symbol.for` so\n * independently loaded copies of this file (state's and server's) still agree.\n */\nexport const SSR_SNAPSHOT_BUILDER_KEY = Symbol.for(\"wcstack.ssr.snapshotBuilder\");\n\n/**\n * `data-wcs-server` attribute value announcing that the renderer will call the\n * builder as a final pass. Providers must skip their inline per-element build\n * when they see this value, and keep it for any other value (including \"\").\n */\nexport const SSR_ORCHESTRATED_VALUE = \"orchestrated\";\n\nexport interface IWcsSsrSnapshotBuilder {\n readonly protocol: \"wcs-ssr-snapshot\";\n /** Integer protocol version. All versions >= 1 are participant-compatible. */\n readonly version: number;\n /**\n * Build a hydration snapshot for every enable-ssr state element in `root`\n * that does not already have one. Called once per render, after all\n * waiting-protocol elements have settled and bindings are ready. Must be\n * idempotent with respect to already-snapshotted elements.\n */\n build(root: Document): void;\n}\n\n/**\n * The installed builder, or null when there is none or it speaks a shape this\n * reader does not.\n */\nexport function getSsrSnapshotBuilder(): IWcsSsrSnapshotBuilder | null {\n const candidate = (globalThis as Record<symbol, unknown>)[\n SSR_SNAPSHOT_BUILDER_KEY\n ] as IWcsSsrSnapshotBuilder | undefined;\n if (candidate === undefined || candidate === null) return null;\n if (candidate.protocol !== \"wcs-ssr-snapshot\") return null;\n if (typeof candidate.version !== \"number\" || candidate.version < 1) return null;\n if (typeof candidate.build !== \"function\") return null;\n return candidate;\n}\n","import { Window } from 'happy-dom';\nimport { getSsrSnapshotBuilder, SSR_ORCHESTRATED_VALUE } from './protocol/ssrSnapshot';\n\n/**\n * globalThis を差し替える renderToString の並列実行を防止する Mutex。\n * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも\n * シリアライズされ、グローバル状態の衝突を防ぐ。\n */\nclass Mutex {\n private _queue: (() => void)[] = [];\n private _locked = false;\n\n async acquire(): Promise<() => void> {\n if (this._locked) {\n await new Promise<void>(resolve => this._queue.push(resolve));\n }\n this._locked = true;\n return () => {\n this._locked = false;\n this._queue.shift()?.();\n };\n }\n}\n\nconst renderMutex = new Mutex();\n\nexport const GLOBALS_KEYS = [\n 'document', 'customElements', 'HTMLElement',\n 'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',\n 'MutationObserver', 'ShadowRoot', 'Element', 'HTMLTemplateElement',\n // URL を持つコンポーネント(@wcstack/router 等)が window.location /\n // history を読めるようにする(docs/ssr-router-design.md §3.1)。\n 'window', 'location', 'history',\n // コンポーネントが発火するイベントをレンダリングウィンドウの realm に揃える。\n // Node ネイティブの CustomEvent は happy-dom の EventTarget に拒否される\n // (\"parameter 1 is not of type 'Event'\")。vitest の happy-dom 環境では\n // グローバルが happy-dom 側なので隠れ、素の Node サーバーでだけ顕在化する\n 'Event', 'CustomEvent',\n];\n\nexport function installGlobals(window: Window): () => void {\n const saved: Record<string, any> = {};\n for (const key of GLOBALS_KEYS) {\n saved[key] = (globalThis as any)[key];\n (globalThis as any)[key] = (window as any)[key];\n }\n // URL.createObjectURL を無効化して、\n // loadFromInnerScript が base64 data: URL フォールバックを使うようにする\n const origCreateObjectURL = URL.createObjectURL;\n (URL as any).createObjectURL = undefined;\n return () => {\n URL.createObjectURL = origCreateObjectURL;\n for (const key of GLOBALS_KEYS) {\n (globalThis as any)[key] = saved[key];\n }\n };\n}\n\nexport function installBaseUrl(baseUrl: string): () => void {\n const OrigURL = globalThis.URL;\n const base = baseUrl;\n globalThis.URL = class extends OrigURL {\n constructor(input: string | URL, inputBase?: string | URL) {\n if (typeof input === 'string' && input.startsWith('/') && inputBase === undefined) {\n super(input, base);\n } else {\n super(input as string, inputBase);\n }\n }\n } as typeof URL;\n // 静的メソッドを引き継ぐ\n globalThis.URL.createObjectURL = OrigURL.createObjectURL;\n globalThis.URL.revokeObjectURL = OrigURL.revokeObjectURL;\n return () => { globalThis.URL = OrigURL; };\n}\n\n/** @deprecated Use Ssr.extractStateData() from @wcstack/state instead */\nexport function extractStateData(stateEl: any): Record<string, any> {\n const raw = (stateEl as any).__state;\n if (!raw || typeof raw !== 'object') return {};\n const data: Record<string, any> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!key.startsWith('$') && typeof value !== 'function') {\n data[key] = value;\n }\n }\n return data;\n}\n\n/**\n * 同期の bootstrap 関数、または非同期ローダー。\n * `HTMLElement` を継承するクラスはモジュール評価時にグローバルの `HTMLElement` を\n * 参照するため、純 Node 環境ではトップレベル import できないパッケージがある。\n * その場合は `async () => (await import('@wcstack/router')).bootstrapRouter()` の\n * ように非同期ローダーを渡す — 呼び出しは installGlobals の後なので、モジュール\n * 評価時にはグローバルが揃っている(docs/ssr-router-design.md §3.1)。\n */\nexport type BootstrapFunction = () => void | Promise<void>;\n\nexport interface RenderOptions {\n /** 相対 URL を解決するベース URL (例: \"http://localhost:3001\")。省略時は `url` の origin */\n baseUrl?: string;\n /** bootstrap 関数の配列。省略時は @wcstack/state を自動ロード */\n bootstraps?: BootstrapFunction[];\n /**\n * このリクエストの完全 URL (例: \"http://localhost:3000/products/1\")。\n * `window.location` / `document.baseURI` に反映される。ルーティングする\n * コンポーネント(@wcstack/router 等)のサーバーレンダリングに必要\n * (docs/ssr-router-design.md §3.1)。\n */\n url?: string;\n /**\n * `<head>` へ注入する `<base href>` の値。`url` 指定時の既定は \"/\"。\n * ブラウザで `<base>` を置く SPA と同じ条件をサーバー内に再現する\n * (深い URL での basename 誤認を防ぐ)。サブパス配備では明示する。\n */\n baseHref?: string;\n}\n\nasync function loadDefaultBootstraps(): Promise<BootstrapFunction[]> {\n const { bootstrapState } = await import('@wcstack/state');\n return [bootstrapState];\n}\n\n\n/**\n * HTML 文字列を SSR レンダリングして返す。\n *\n * ## 入力 HTML のルール\n * - `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)\n * - `<script>` / `<link>` による外部リソース読み込みは実行されない\n * → 必要なパッケージは `options.bootstraps` で明示的に渡す\n *\n * ## SSR でできること\n *\n * ### 状態の初期化とデータ取得\n * - `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type=\"module\">`)\n * - `$connectedCallback` でのサーバーサイド fetch(API 呼び出し、DB 問い合わせ等)\n *\n * ```html\n * <!-- JSON 直接指定 -->\n * <wcs-state enable-ssr json='{\"title\":\"Hello\"}'></wcs-state>\n *\n * <!-- $connectedCallback で API からデータ取得 -->\n * <!-- $connectedCallback は状態オブジェクトのメソッドとして定義し、this が state proxy -->\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const res = await fetch('/api/users');\n * this.users = await res.json();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n *\n * ### wcs-fetch を使ったサーバー通信\n * - `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される\n * - `manual` + `$connectedCallback` で明示的に制御する場合:\n *\n * ```html\n * <wcs-fetch id=\"api\" url=\"/api/users\" manual></wcs-fetch>\n * <wcs-state enable-ssr>\n * <script type=\"module\">\n * export default {\n * async $connectedCallback() {\n * const el = document.getElementById('api');\n * this.users = await el.fetch();\n * }\n * };\n * </script>\n * </wcs-state>\n * ```\n * ※ `bootstraps` に `bootstrapFetch` を含める必要あり\n *\n * ### バインディングと構造レンダリング\n * - `data-wcs` バインディングの適用(text, attribute, class, style, property)\n * - `<template data-wcs=\"for:\">` / `if:` / `elseif:` / `else:` の構造レンダリング\n *\n * ```html\n * <ul>\n * <template data-wcs=\"for: users\">\n * <li data-wcs=\"textContent: .name\"></li>\n * </template>\n * </ul>\n * <template data-wcs=\"if: isAdmin\">\n * <div class=\"admin-panel\">...</div>\n * </template>\n * ```\n *\n * ### ハイドレーション\n * - `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成\n * - クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)\n * - `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)\n *\n * ### カスタム要素の待機\n * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機\n * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)\n *\n * ### router SSR\n * - `<wcs-router enable-ssr>` + `url` オプションで初期ルートをサーバー描画。\n * クライアント側 router は描画済み DOM を採用(adopt)する。\n * 詳細は README「Router SSR」/ docs/ssr-router-design.md\n *\n * ## SSR でできないこと\n * - `<head>` 内の `<script src=\"...\">` や `<link>` の自動実行\n * - ブラウザ固有 API(localStorage, sessionStorage, navigator 等)\n * - Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)\n * - イベントハンドラの登録(クライアント側のハイドレーションで復元)\n * - `<wcs-autoloader>` による動的コンポーネント読み込み\n * - guard 付きルートのサーバー描画(設計上・クライアントで guard 実行)、\n * `<wcs-layout>` ルートの採用(クライアント描画へフォールバック)、\n * `<wcs-head>` のサーバー反映(body のみの出力に head は載らない)\n *\n * ## HTML の分割パターン\n * ```\n * // server.js\n * const ssrBody = await renderToString(template, { ... });\n * const page = `<!DOCTYPE html>\n * <html lang=\"ja\">\n * <head>\n * <script type=\"module\" src=\"/packages/state/dist/auto.min.js\"></script>\n * </head>\n * <body>${ssrBody}</body>\n * </html>`;\n * ```\n * `renderToString` には `<body>` の中身だけを渡し、\n * `<head>` や `<script>` タグは外側のテンプレートで囲む。\n */\nexport async function renderToString(html: string, options?: RenderOptions): Promise<string> {\n // globalThis を差し替えるため、同時に1つしか実行できない\n const releaseMutex = await renderMutex.acquire();\n\n const window = options?.url ? new Window({ url: options.url }) : new Window();\n const restoreGlobals = installGlobals(window);\n const document = window.document;\n\n let restoreBaseUrl: (() => void) | null = null;\n\n try {\n // url 指定時は <base href> を注入する(既定 \"/\")。ブラウザで <base> を置く\n // SPA と同じ条件を再現し、深い URL での basename 誤認を防ぐ\n // (docs/ssr-router-design.md §3.1)。\n if (options?.url !== undefined || options?.baseHref !== undefined) {\n const base = document.createElement('base');\n base.setAttribute('href', options.baseHref ?? '/');\n document.head.appendChild(base);\n }\n\n // 相対 URL を baseUrl で解決する URL コンストラクタパッチをインストール。\n // baseUrl 省略時は url の origin を既定にする。\n const effectiveBaseUrl =\n options?.baseUrl ?? (options?.url ? new URL(options.url).origin : undefined);\n restoreBaseUrl = effectiveBaseUrl\n ? installBaseUrl(effectiveBaseUrl)\n : null;\n\n // bootstrap の解決。非同期ローダー(BootstrapFunction 参照)を許容するため\n // await する。try 内で行うのは、throw 時にもグローバル復元を保証するため。\n const bootstraps = options?.bootstraps ?? await loadDefaultBootstraps();\n\n for (const bootstrap of bootstraps) {\n await bootstrap();\n }\n\n // SSR モードを html 要素に設定。snapshot builder(bootstraps の実行が\n // 登録し得る — ssr-snapshot プロトコル)が居れば orchestrated を宣言し、\n // <wcs-ssr> 生成をサーバー主導の最終パスへ回す(docs/ssr-router-design.md §5)。\n // 値の宣言はパースより前 — 各要素は connectedCallback で値を読むため\n const snapshotBuilder = getSsrSnapshotBuilder();\n document.documentElement.setAttribute(\n 'data-wcs-server',\n snapshotBuilder !== null ? SSR_ORCHESTRATED_VALUE : ''\n );\n\n // HTML をパース\n // connectedCallback が自動発火 → state ロード → $connectedCallback 実行\n document.body.innerHTML = html;\n\n // connectedCallbackPromise / getBindingsReady プロトコルを自動検出\n // $connectedCallback が動的にカスタム要素を追加する場合があるため、\n // 新しい要素が見つからなくなるまで走査を繰り返す(安定化ループ)\n const MAX_ITERATIONS = 10;\n const awaitedElements = new WeakSet();\n const readyCtors = new Set<any>();\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const connectedPromises: Promise<void>[] = [];\n\n for (const el of document.querySelectorAll('*-*')) {\n if (awaitedElements.has(el)) continue;\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\n awaitedElements.add(el);\n connectedPromises.push((el as any).connectedCallbackPromise);\n }\n if (typeof ctor.getBindingsReady === 'function') {\n readyCtors.add(ctor);\n }\n }\n\n if (connectedPromises.length === 0) break;\n await Promise.all(connectedPromises);\n }\n\n // 非同期初期化(バインディング構築)の完了を待機。Promise の**取得**は\n // 安定化ループの後に行う — getBindingsReady の実体(rootNode ごとの ready)は\n // 各要素の connectedCallback 内・最初の await より後に登録されるため、ループ\n // 初回の収集では「まだ登録前」の即時解決 Promise を掴むことがある。inline\n // スナップショット時代は state の connectedCallback 自身が bindings を待って\n // いたため隠れていた(orchestrated 化で顕在化)。取り逃すと構築の続きが\n // グローバル復元後に走り、document 消失でクラッシュする\n await Promise.all(\n Array.from(readyCtors, (ctor) => (ctor as { getBindingsReady(root: Node): Promise<void> }).getBindingsReady(document as unknown as Node))\n );\n\n // スナップショット最終パス(orchestrated): 全要素の完了とバインディング構築の\n // 後に <wcs-ssr> を生成する。inline 生成(connectedCallback 内)が取り逃がす\n // 「後から挿入されたルート内容の構造テンプレート」も、この時点なら確定している\n snapshotBuilder?.build(document as unknown as Document);\n\n return document.body.innerHTML;\n } finally {\n // エラー経路でも進行中のバインディング構築を待ってから globals を戻す。\n // 構築は要素の connectedCallback とは独立した microtask 連鎖で走るため、\n // 待たずに戻すと続きが document 消失で unhandled になりプロセスを落とす。\n // 後始末はベストエフォート(rejected も含めて待つだけ待つ)\n try {\n const readyPending: Promise<void>[] = [];\n for (const el of document.querySelectorAll('*-*')) {\n const ctor = el.constructor as { getBindingsReady?(root: Node): Promise<void> };\n if (typeof ctor.getBindingsReady === 'function') {\n readyPending.push(ctor.getBindingsReady(document as unknown as Node));\n }\n }\n await Promise.allSettled(readyPending);\n } catch { /* best effort */ }\n // binder プロトコルの保留キュー(Symbol.for なので installGlobals の restore\n // 対象外=プロセス寿命)を空にする。state を読み込まないページで挿入側\n // (router 等)が差し出したノードは引き取り手が現れないまま蓄積するため、\n // レンダリングごとに後始末する(docs/ssr-router-design.md §3.1)。\n // これはプロトコルの公開シンボル面であり、パッケージ内部への依存ではない。\n const pendingBinds = (globalThis as Record<symbol, unknown>)[\n Symbol.for('wcstack.binder.pending')\n ];\n if (Array.isArray(pendingBinds)) {\n pendingBinds.length = 0;\n }\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\n releaseMutex();\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\nexport const VERSION = pkg.version;\n","import { renderToString } from \"./render.js\";\nimport { IWcBindable } from \"./types.js\";\n\nexport class RenderCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"html\", event: \"wcs-render:html-changed\", semantics: \"state\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\", semantics: \"state\" },\n { name: \"error\", event: \"wcs-render:error\", semantics: \"state\" },\n ],\n };\n\n private _html: string | null = null;\n private _loading: boolean = false;\n private _error: Error | null = null;\n\n get html(): string | null {\n return this._html;\n }\n\n get loading(): boolean {\n return this._loading;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n private _setLoading(loading: boolean): void {\n this._loading = loading;\n this.dispatchEvent(new CustomEvent(\"wcs-render:loading-changed\", {\n detail: loading,\n }));\n }\n\n private _setHtml(html: string): void {\n this._html = html;\n this.dispatchEvent(new CustomEvent(\"wcs-render:html-changed\", {\n detail: html,\n }));\n }\n\n private _setError(error: Error | null): void {\n this._error = error;\n this.dispatchEvent(new CustomEvent(\"wcs-render:error\", {\n detail: error,\n }));\n }\n\n async render(html: string): Promise<string | null> {\n this._setLoading(true);\n this._setError(null);\n\n try {\n const result = await renderToString(html);\n this._setHtml(result);\n this._setLoading(false);\n return this._html;\n } catch (e: any) {\n this._setError(e instanceof Error ? e : new Error(String(e)));\n this._setLoading(false);\n return null;\n }\n }\n}\n"],"names":["SSR_SNAPSHOT_BUILDER_KEY","Symbol","for","renderMutex","_queue","_locked","acquire","this","Promise","resolve","push","shift","GLOBALS_KEYS","installGlobals","window","saved","key","globalThis","origCreateObjectURL","URL","createObjectURL","undefined","installBaseUrl","baseUrl","OrigURL","base","constructor","input","inputBase","startsWith","super","revokeObjectURL","extractStateData","stateEl","raw","__state","data","value","Object","entries","async","renderToString","html","options","releaseMutex","url","Window","restoreGlobals","document","restoreBaseUrl","baseHref","createElement","setAttribute","head","appendChild","effectiveBaseUrl","origin","bootstraps","bootstrapState","import","loadDefaultBootstraps","bootstrap","snapshotBuilder","candidate","protocol","version","build","getSsrSnapshotBuilder","documentElement","body","innerHTML","MAX_ITERATIONS","awaitedElements","WeakSet","readyCtors","Set","i","connectedPromises","el","querySelectorAll","has","ctor","hasConnectedCallbackPromise","add","connectedCallbackPromise","getBindingsReady","length","all","Array","from","readyPending","allSettled","pendingBinds","isArray","close","VERSION","RenderCore","EventTarget","static","properties","name","event","semantics","_html","_loading","_error","loading","error","_setLoading","dispatchEvent","CustomEvent","detail","_setHtml","_setError","render","result","e","Error","String"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/server",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Server-side rendering for wcstack Web Components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",