@wcstack/server 1.8.5 → 1.8.6

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
@@ -281,6 +281,7 @@ createServer(async (req, res) => {
281
281
  ### カスタム要素の待機
282
282
 
283
283
  - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
284
+ - 安定化ループ: await 後に DOM を再走査し、`$connectedCallback` 中に動的追加されたカスタム要素も待機(最大 10 回)
284
285
 
285
286
  ## SSR でできないこと
286
287
 
@@ -331,7 +332,7 @@ const ssrBody = await renderToString(template, {
331
332
 
332
333
  3. **ブートストラップ**: ユーザー提供の bootstrap 関数を呼び出す(省略時は `bootstrapState()` をデフォルト使用)。
333
334
 
334
- 4. **HTML パースとコールバック**: `document.body.innerHTML` に HTML をセットすることで、happy-dom の要素ライフサイクルが発火。各 `<wcs-state>` がデータソースをロードし `$connectedCallback` を実行。`hasConnectedCallbackPromise` を持つ全カスタム要素を待機。
335
+ 4. **HTML パースとコールバック**: `document.body.innerHTML` に HTML をセットすることで、happy-dom の要素ライフサイクルが発火。各 `<wcs-state>` がデータソースをロードし `$connectedCallback` を実行。`hasConnectedCallbackPromise` を持つ全カスタム要素を安定化ループで待機 — await 後に DOM を再走査し、動的追加された要素も検出(最大 10 回)。
335
336
 
336
337
  5. **Ready**: ユーザー提供の ready 関数を待機(デフォルトは `getBindingsReady()`) — テキスト補間、属性マッピング、リスト展開、条件評価。
337
338
 
package/README.md CHANGED
@@ -281,6 +281,7 @@ createServer(async (req, res) => {
281
281
  ### Custom Element Waiting
282
282
 
283
283
  - Automatically awaits all custom elements with `static hasConnectedCallbackPromise = true`
284
+ - 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
284
285
 
285
286
  ## What SSR Cannot Do
286
287
 
@@ -331,7 +332,7 @@ const ssrBody = await renderToString(template, {
331
332
 
332
333
  3. **Bootstrap**: Calls user-provided bootstrap functions (defaults to `bootstrapState()` if omitted).
333
334
 
334
- 4. **HTML Parse & Callback**: Sets `document.body.innerHTML`, which triggers happy-dom's element lifecycle. Each `<wcs-state>` loads its data source and runs `$connectedCallback`. Awaits all custom elements with `hasConnectedCallbackPromise`.
335
+ 4. **HTML Parse & Callback**: Sets `document.body.innerHTML`, which triggers happy-dom's element lifecycle. Each `<wcs-state>` loads its data source and runs `$connectedCallback`. Awaits all custom elements with `hasConnectedCallbackPromise` via a stabilization loop — after each await, re-scans the DOM for newly added elements (up to 10 iterations).
335
336
 
336
337
  5. **Ready**: Awaits user-provided ready functions (defaults to `getBindingsReady()`) — text interpolation, attribute mapping, list expansion, conditional evaluation.
337
338
 
package/dist/index.d.ts CHANGED
@@ -85,6 +85,7 @@ interface RenderOptions {
85
85
  *
86
86
  * ### カスタム要素の待機
87
87
  * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
88
+ * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)
88
89
  *
89
90
  * ## SSR でできないこと
90
91
  * - `<head>` 内の `<script src="...">` や `<link>` の自動実行
package/dist/index.esm.js CHANGED
@@ -1,5 +1,25 @@
1
1
  import { Window } from 'happy-dom';
2
2
 
3
+ /**
4
+ * globalThis を差し替える renderToString の並列実行を防止する Mutex。
5
+ * 同一 Node プロセス内で複数リクエストが同時に renderToString を呼んでも
6
+ * シリアライズされ、グローバル状態の衝突を防ぐ。
7
+ */
8
+ class Mutex {
9
+ _queue = [];
10
+ _locked = false;
11
+ async acquire() {
12
+ if (this._locked) {
13
+ await new Promise(resolve => this._queue.push(resolve));
14
+ }
15
+ this._locked = true;
16
+ return () => {
17
+ this._locked = false;
18
+ this._queue.shift()?.();
19
+ };
20
+ }
21
+ }
22
+ const renderMutex = new Mutex();
3
23
  const GLOBALS_KEYS = [
4
24
  'document', 'customElements', 'HTMLElement',
5
25
  'DocumentFragment', 'Node', 'NodeFilter', 'Comment', 'Text',
@@ -130,6 +150,7 @@ async function loadDefaultBootstraps() {
130
150
  *
131
151
  * ### カスタム要素の待機
132
152
  * - `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
153
+ * - `$connectedCallback` 中に動的追加されたカスタム要素も安定化ループで検出・待機(最大 10 回)
133
154
  *
134
155
  * ## SSR でできないこと
135
156
  * - `<head>` 内の `<script src="...">` や `<link>` の自動実行
@@ -154,6 +175,8 @@ async function loadDefaultBootstraps() {
154
175
  * `<head>` や `<script>` タグは外側のテンプレートで囲む。
155
176
  */
156
177
  async function renderToString(html, options) {
178
+ // globalThis を差し替えるため、同時に1つしか実行できない
179
+ const releaseMutex = await renderMutex.acquire();
157
180
  const window = new Window();
158
181
  const restoreGlobals = installGlobals(window);
159
182
  const document = window.document;
@@ -173,20 +196,31 @@ async function renderToString(html, options) {
173
196
  // connectedCallback が自動発火 → state ロード → $connectedCallback 実行
174
197
  document.body.innerHTML = html;
175
198
  // connectedCallbackPromise / getBindingsReady プロトコルを自動検出
176
- const connectedPromises = [];
177
- const readyPromises = [];
199
+ // $connectedCallback が動的にカスタム要素を追加する場合があるため、
200
+ // 新しい要素が見つからなくなるまで走査を繰り返す(安定化ループ)
201
+ const MAX_ITERATIONS = 10;
202
+ const awaitedElements = new WeakSet();
178
203
  const readyCtors = new Set();
179
- for (const el of document.querySelectorAll('*-*')) {
180
- const ctor = el.constructor;
181
- if (ctor.hasConnectedCallbackPromise) {
182
- connectedPromises.push(el.connectedCallbackPromise);
183
- }
184
- if (!readyCtors.has(ctor) && typeof ctor.getBindingsReady === 'function') {
185
- readyCtors.add(ctor);
186
- readyPromises.push(ctor.getBindingsReady(document));
204
+ const readyPromises = [];
205
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
206
+ const connectedPromises = [];
207
+ for (const el of document.querySelectorAll('*-*')) {
208
+ if (awaitedElements.has(el))
209
+ continue;
210
+ const ctor = el.constructor;
211
+ if (ctor.hasConnectedCallbackPromise) {
212
+ awaitedElements.add(el);
213
+ connectedPromises.push(el.connectedCallbackPromise);
214
+ }
215
+ if (!readyCtors.has(ctor) && typeof ctor.getBindingsReady === 'function') {
216
+ readyCtors.add(ctor);
217
+ readyPromises.push(ctor.getBindingsReady(document));
218
+ }
187
219
  }
220
+ if (connectedPromises.length === 0)
221
+ break;
222
+ await Promise.all(connectedPromises);
188
223
  }
189
- await Promise.all(connectedPromises);
190
224
  // 非同期初期化の完了を待機
191
225
  await Promise.all(readyPromises);
192
226
  return document.body.innerHTML;
@@ -195,10 +229,11 @@ async function renderToString(html, options) {
195
229
  restoreBaseUrl?.();
196
230
  restoreGlobals();
197
231
  await window.close();
232
+ releaseMutex();
198
233
  }
199
234
  }
200
235
 
201
- var version = "1.8.4";
236
+ var version = "1.8.5";
202
237
  var pkg = {
203
238
  version: version};
204
239
 
@@ -246,7 +281,7 @@ class RenderCore extends EventTarget {
246
281
  }
247
282
  async render(html) {
248
283
  this._setLoading(true);
249
- this._error = null;
284
+ this._setError(null);
250
285
  try {
251
286
  const result = await renderToString(html);
252
287
  this._setHtml(result);
@@ -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\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 *\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.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 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 const connectedPromises: Promise<void>[] = [];\n const readyPromises: Promise<void>[] = [];\n const readyCtors = new Set<any>();\n for (const el of document.querySelectorAll('*-*')) {\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\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 await Promise.all(connectedPromises);\n\n // 非同期初期化の完了を待機\n await Promise.all(readyPromises);\n\n return document.body.innerHTML;\n } finally {\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\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\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\" },\n { name: \"error\", event: \"wcs-render:error\" },\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): 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._error = 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
+ {"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.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\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\" },\n { name: \"error\", event: \"wcs-render:error\" },\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=["document","customElements","HTMLElement","DocumentFragment","Node","NodeFilter","Comment","Text","MutationObserver","ShadowRoot","Element","HTMLTemplateElement"];function n(t){const n={};for(const r of e)n[r]=globalThis[r],globalThis[r]=t[r];const r=URL.createObjectURL;return URL.createObjectURL=void 0,()=>{URL.createObjectURL=r;for(const t of e)globalThis[t]=n[t]}}function r(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 s(e,o){const s=new t,a=n(s),i=s.document,c=o?.baseUrl?r(o.baseUrl):null,l=o?.bootstraps??await async function(){const{bootstrapState:t}=await import("@wcstack/state");return[t]}();for(const t of l)t();try{i.documentElement.setAttribute("data-wcs-server",""),i.body.innerHTML=e;const t=[],n=[],r=new Set;for(const e of i.querySelectorAll("*-*")){const o=e.constructor;o.hasConnectedCallbackPromise&&t.push(e.connectedCallbackPromise),r.has(o)||"function"!=typeof o.getBindingsReady||(r.add(o),n.push(o.getBindingsReady(i)))}return await Promise.all(t),await Promise.all(n),i.body.innerHTML}finally{c?.(),a(),await s.close()}}const a="1.8.4";class i extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"html",event:"wcs-render:html-changed"},{name:"loading",event:"wcs-render:loading-changed"},{name:"error",event:"wcs-render:error"}]};_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._error=null;try{const e=await s(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{e as GLOBALS_KEYS,i as RenderCore,a as VERSION,o as extractStateData,r as installBaseUrl,n as installGlobals,s as renderToString};
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 o(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 s(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,s){const a=await e.acquire(),i=new t,c=r(i),l=i.document,d=s?.baseUrl?o(s.baseUrl):null,h=s?.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,o=[];for(let n=0;n<t;n++){const t=[];for(const n of l.querySelectorAll("*-*")){if(e.has(n))continue;const s=n.constructor;s.hasConnectedCallbackPromise&&(e.add(n),t.push(n.connectedCallbackPromise)),r.has(s)||"function"!=typeof s.getBindingsReady||(r.add(s),o.push(s.getBindingsReady(l)))}if(0===t.length)break;await Promise.all(t)}return await Promise.all(o),l.body.innerHTML}finally{d?.(),c(),await i.close(),a()}}const i="1.8.5";class c extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"html",event:"wcs-render:html-changed"},{name:"loading",event:"wcs-render:loading-changed"},{name:"error",event:"wcs-render:error"}]};_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,s as extractStateData,o 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\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 *\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.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 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 const connectedPromises: Promise<void>[] = [];\n const readyPromises: Promise<void>[] = [];\n const readyCtors = new Set<any>();\n for (const el of document.querySelectorAll('*-*')) {\n const ctor = el.constructor as any;\n if (ctor.hasConnectedCallbackPromise) {\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 await Promise.all(connectedPromises);\n\n // 非同期初期化の完了を待機\n await Promise.all(readyPromises);\n\n return document.body.innerHTML;\n } finally {\n restoreBaseUrl?.();\n restoreGlobals();\n await window.close();\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\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\" },\n { name: \"error\", event: \"wcs-render:error\" },\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): 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._error = 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":["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","Window","restoreGlobals","document","restoreBaseUrl","bootstraps","bootstrapState","import","loadDefaultBootstraps","bootstrap","documentElement","setAttribute","body","innerHTML","connectedPromises","readyPromises","readyCtors","Set","el","querySelectorAll","ctor","hasConnectedCallbackPromise","push","connectedCallbackPromise","has","getBindingsReady","add","Promise","all","close","VERSION","RenderCore","EventTarget","static","protocol","version","properties","name","event","_html","_loading","_error","this","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/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.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\" },\n { name: \"loading\", event: \"wcs-render:loading-changed\" },\n { name: \"error\", event: \"wcs-render:error\" },\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","_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.8.5",
3
+ "version": "1.8.6",
4
4
  "description": "Server-side rendering for wcstack Web Components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",
@@ -55,6 +55,12 @@
55
55
  "rollup-plugin-dts": "^6.1.1",
56
56
  "tslib": "^2.8.1",
57
57
  "typescript": "^5.9.3",
58
- "vitest": "^4.0.15"
58
+ "vitest": "^4.0.15",
59
+ "@eslint/js": "^9.39.1",
60
+ "@typescript-eslint/eslint-plugin": "^8.33.1",
61
+ "@typescript-eslint/parser": "^8.33.1",
62
+ "eslint": "^9.39.1",
63
+ "globals": "^16.2.0",
64
+ "typescript-eslint": "^8.49.0"
59
65
  }
60
66
  }