@wcstack/server 0.3.1 → 1.8.5
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 +354 -354
- package/README.md +354 -354
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +60 -60
package/README.md
CHANGED
|
@@ -1,354 +1,354 @@
|
|
|
1
|
-
# @wcstack/server
|
|
2
|
-
|
|
3
|
-
**What if Web Components rendered on the server?**
|
|
4
|
-
|
|
5
|
-
Imagine a future where your `<wcs-state>` templates are fully rendered before they reach the browser — data is fetched, bindings are resolved, lists are expanded, conditionals are evaluated. The user sees content instantly, and the client picks up exactly where the server left off.
|
|
6
|
-
|
|
7
|
-
That's what `@wcstack/server` explores. It runs your existing `@wcstack/state` templates through happy-dom, produces fully-rendered HTML with embedded hydration data, and lets the client resume reactivity with zero flicker. No special template syntax, no server-specific markup — just the same HTML you already write.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
### Basic Features
|
|
12
|
-
- **Full Template Rendering**: Runs `@wcstack/state` bindings server-side — text, attributes, `for` loops, `if`/`elseif`/`else` conditionals, filters, and mustache `{{ }}` syntax.
|
|
13
|
-
- **Automatic Hydration Data**: Generates `<wcs-ssr>` elements containing state snapshots, template fragments, and property maps for seamless client-side hydration.
|
|
14
|
-
- **Async Data Fetching**: Supports `$connectedCallback` with `fetch()` — server waits for all async operations before rendering.
|
|
15
|
-
- **RenderCore**: A headless, event-driven rendering class that follows the `wc-bindable` protocol for observable `html` / `loading` / `error` state.
|
|
16
|
-
- **Zero Browser Dependencies**: Runs in Node.js with happy-dom as the only runtime dependency.
|
|
17
|
-
|
|
18
|
-
### Unique Features
|
|
19
|
-
- **Drop-in SSR**: No changes to your client-side templates. Add `enable-ssr` to `<wcs-state>` and render with `renderToString()`.
|
|
20
|
-
- **Template Fragment Preservation**: `for`/`if` template sources are captured with UUID references so the client can re-execute structural directives.
|
|
21
|
-
- **Property Hydration**: DOM properties that can't be expressed as attributes (e.g., `innerHTML`) are serialized separately and restored during hydration.
|
|
22
|
-
- **wc-bindable Protocol**: `RenderCore` exposes rendering state via the standard protocol, enabling the same `bind()` pattern on both server and client.
|
|
23
|
-
|
|
24
|
-
## Installation
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install @wcstack/server
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Quick Start
|
|
31
|
-
|
|
32
|
-
### `renderToString()` — One-shot rendering
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
import { renderToString } from "@wcstack/server";
|
|
36
|
-
|
|
37
|
-
const html = await renderToString(`
|
|
38
|
-
<wcs-state json='{"items":["Apple","Banana","Cherry"]}' enable-ssr>
|
|
39
|
-
</wcs-state>
|
|
40
|
-
<ul>
|
|
41
|
-
<template data-wcs="for: items">
|
|
42
|
-
<li data-wcs="textContent: items.*"></li>
|
|
43
|
-
</template>
|
|
44
|
-
</ul>
|
|
45
|
-
`);
|
|
46
|
-
|
|
47
|
-
console.log(html);
|
|
48
|
-
// Fully rendered HTML with <wcs-ssr> hydration data
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### `RenderCore` — Observable rendering with caching
|
|
52
|
-
|
|
53
|
-
```javascript
|
|
54
|
-
import { RenderCore } from "@wcstack/server";
|
|
55
|
-
|
|
56
|
-
const renderer = new RenderCore();
|
|
57
|
-
|
|
58
|
-
// Listen to state changes via wc-bindable protocol
|
|
59
|
-
renderer.addEventListener("wcs-render:loading-changed", (e) => {
|
|
60
|
-
console.log("loading:", e.detail);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
renderer.addEventListener("wcs-render:html-changed", (e) => {
|
|
64
|
-
console.log("rendered:", e.detail.length, "bytes");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Render and cache
|
|
68
|
-
await renderer.render(templateHtml);
|
|
69
|
-
|
|
70
|
-
// Subsequent reads use the cached result
|
|
71
|
-
console.log(renderer.html);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## API Reference
|
|
75
|
-
|
|
76
|
-
### `renderToString(html: string): Promise<string>`
|
|
77
|
-
|
|
78
|
-
Renders an HTML string containing `@wcstack/state` templates. Returns fully-rendered HTML with hydration data for any `<wcs-state enable-ssr>` elements.
|
|
79
|
-
|
|
80
|
-
**Rendering pipeline:**
|
|
81
|
-
1. Creates a happy-dom window and installs browser globals
|
|
82
|
-
2. Parses HTML and triggers `connectedCallback` on all `<wcs-state>` elements
|
|
83
|
-
3. Awaits all `$connectedCallback` promises (including `fetch()` calls)
|
|
84
|
-
4. Waits for `buildBindings` to complete
|
|
85
|
-
5. Generates `<wcs-ssr>` elements for states with `enable-ssr`
|
|
86
|
-
6. Restores globals and returns the rendered HTML
|
|
87
|
-
|
|
88
|
-
### `RenderCore`
|
|
89
|
-
|
|
90
|
-
Headless rendering class extending `EventTarget`. Implements the `wc-bindable` protocol.
|
|
91
|
-
|
|
92
|
-
| Property | Type | Description |
|
|
93
|
-
|----------|------|-------------|
|
|
94
|
-
| `html` | `string \| null` | Rendered HTML (cached after `render()`) |
|
|
95
|
-
| `loading` | `boolean` | `true` while rendering is in progress |
|
|
96
|
-
| `error` | `Error \| null` | Error from the last `render()` call, if any |
|
|
97
|
-
|
|
98
|
-
| Method | Returns | Description |
|
|
99
|
-
|--------|---------|-------------|
|
|
100
|
-
| `render(html)` | `Promise<string \| null>` | Renders the template and caches the result. Returns `null` on error. |
|
|
101
|
-
|
|
102
|
-
| Event | Detail | Description |
|
|
103
|
-
|-------|--------|-------------|
|
|
104
|
-
| `wcs-render:html-changed` | `string` | Fired when rendering completes successfully |
|
|
105
|
-
| `wcs-render:loading-changed` | `boolean` | Fired when loading state changes |
|
|
106
|
-
| `wcs-render:error` | `Error` | Fired when rendering fails |
|
|
107
|
-
|
|
108
|
-
**wc-bindable declaration:**
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
static wcBindable = {
|
|
112
|
-
protocol: "wc-bindable",
|
|
113
|
-
version: 1,
|
|
114
|
-
properties: [
|
|
115
|
-
{ name: "html", event: "wcs-render:html-changed" },
|
|
116
|
-
{ name: "loading", event: "wcs-render:loading-changed" },
|
|
117
|
-
{ name: "error", event: "wcs-render:error" },
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Helper Functions
|
|
123
|
-
|
|
124
|
-
| Function | Description |
|
|
125
|
-
|----------|-------------|
|
|
126
|
-
| `installGlobals(window)` | Installs happy-dom globals on `globalThis`. Returns a restore function. |
|
|
127
|
-
| `extractStateData(stateEl)` | Extracts data properties from a `<wcs-state>` element (excludes `$`-prefixed keys and functions). |
|
|
128
|
-
|
|
129
|
-
### Constants
|
|
130
|
-
|
|
131
|
-
| Name | Description |
|
|
132
|
-
|------|-------------|
|
|
133
|
-
| `GLOBALS_KEYS` | Array of browser global keys installed during SSR (`document`, `HTMLElement`, `Node`, etc.) |
|
|
134
|
-
| `VERSION` | Package version string from `package.json` |
|
|
135
|
-
|
|
136
|
-
## SSR Output Structure
|
|
137
|
-
|
|
138
|
-
When a `<wcs-state>` has the `enable-ssr` attribute, `renderToString()` inserts a `<wcs-ssr>` element immediately before it containing all hydration data:
|
|
139
|
-
|
|
140
|
-
```html
|
|
141
|
-
<!-- Generated by renderToString() -->
|
|
142
|
-
<wcs-ssr name="default" version="0.1.0">
|
|
143
|
-
|
|
144
|
-
<!-- State snapshot -->
|
|
145
|
-
<script type="application/json">{"items":["Apple","Banana","Cherry"]}</script>
|
|
146
|
-
|
|
147
|
-
<!-- Template fragments (for client-side re-execution) -->
|
|
148
|
-
<template id="uuid-1234" data-wcs="for: items">
|
|
149
|
-
<li data-wcs="textContent: items.*"></li>
|
|
150
|
-
</template>
|
|
151
|
-
|
|
152
|
-
<!-- Non-attribute properties (optional) -->
|
|
153
|
-
<script type="application/json" data-wcs-ssr-props>
|
|
154
|
-
{"wcs-ssr-0": {"innerHTML": "<b>rich</b>"}}
|
|
155
|
-
</script>
|
|
156
|
-
|
|
157
|
-
</wcs-ssr>
|
|
158
|
-
|
|
159
|
-
<wcs-state json='...' enable-ssr></wcs-state>
|
|
160
|
-
|
|
161
|
-
<!-- Rendered output (visible immediately) -->
|
|
162
|
-
<ul>
|
|
163
|
-
<li>Apple</li>
|
|
164
|
-
<li>Banana</li>
|
|
165
|
-
<li>Cherry</li>
|
|
166
|
-
</ul>
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
The client-side `@wcstack/state` reads the `<wcs-ssr>` element during hydration, restores state and templates, and resumes reactivity without re-rendering.
|
|
170
|
-
|
|
171
|
-
## Server Integration Example
|
|
172
|
-
|
|
173
|
-
```javascript
|
|
174
|
-
import { createServer } from "node:http";
|
|
175
|
-
import { RenderCore } from "@wcstack/server";
|
|
176
|
-
|
|
177
|
-
const renderer = new RenderCore();
|
|
178
|
-
|
|
179
|
-
const template = `
|
|
180
|
-
<wcs-state enable-ssr>
|
|
181
|
-
<script type="module">
|
|
182
|
-
export default {
|
|
183
|
-
async $connectedCallback() {
|
|
184
|
-
const res = await fetch("http://localhost:3000/api/data");
|
|
185
|
-
this.items = await res.json();
|
|
186
|
-
},
|
|
187
|
-
items: []
|
|
188
|
-
};
|
|
189
|
-
</script>
|
|
190
|
-
</wcs-state>
|
|
191
|
-
<ul>
|
|
192
|
-
<template data-wcs="for: items">
|
|
193
|
-
<li data-wcs="textContent: items.*"></li>
|
|
194
|
-
</template>
|
|
195
|
-
</ul>
|
|
196
|
-
`;
|
|
197
|
-
|
|
198
|
-
createServer(async (req, res) => {
|
|
199
|
-
if (!renderer.html) {
|
|
200
|
-
await renderer.render(template);
|
|
201
|
-
}
|
|
202
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
203
|
-
res.end(renderer.html);
|
|
204
|
-
}).listen(3000);
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Input HTML Rules
|
|
208
|
-
|
|
209
|
-
- Pass only the contents of `<body>` — do not include `<html>`, `<head>`, or `<body>` tags.
|
|
210
|
-
- `<script>` / `<link>` external resource loading is not executed.
|
|
211
|
-
→ Provide required packages via `options.bootstraps`.
|
|
212
|
-
|
|
213
|
-
## What SSR Can Do
|
|
214
|
-
|
|
215
|
-
### State Initialization & Data Fetching
|
|
216
|
-
|
|
217
|
-
- Load `<wcs-state>` from `json` attribute, `src` attribute, or inline `<script type="module">`
|
|
218
|
-
- Execute `$connectedCallback` for server-side fetch (API calls, etc.)
|
|
219
|
-
|
|
220
|
-
```html
|
|
221
|
-
<!-- Direct JSON -->
|
|
222
|
-
<wcs-state enable-ssr json='{"title":"Hello"}'></wcs-state>
|
|
223
|
-
|
|
224
|
-
<!-- Fetch data from API in $connectedCallback -->
|
|
225
|
-
<!-- $connectedCallback is defined as a method on the state object; `this` is the state proxy -->
|
|
226
|
-
<wcs-state enable-ssr>
|
|
227
|
-
<script type="module">
|
|
228
|
-
export default {
|
|
229
|
-
async $connectedCallback() {
|
|
230
|
-
const res = await fetch('/api/users');
|
|
231
|
-
this.users = await res.json();
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
</script>
|
|
235
|
-
</wcs-state>
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Server Communication with wcs-fetch
|
|
239
|
-
|
|
240
|
-
- `<wcs-fetch>` auto-fetch (without `manual`) also executes on the server
|
|
241
|
-
- Use `manual` + `$connectedCallback` for explicit control:
|
|
242
|
-
|
|
243
|
-
```html
|
|
244
|
-
<wcs-fetch id="api" url="/api/users" manual></wcs-fetch>
|
|
245
|
-
<wcs-state enable-ssr>
|
|
246
|
-
<script type="module">
|
|
247
|
-
export default {
|
|
248
|
-
async $connectedCallback() {
|
|
249
|
-
const el = document.getElementById('api');
|
|
250
|
-
this.users = await el.fetch();
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
</script>
|
|
254
|
-
</wcs-state>
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
> Note: `bootstrapFetch` must be included in the `bootstraps` option.
|
|
258
|
-
|
|
259
|
-
### Bindings & Structural Rendering
|
|
260
|
-
|
|
261
|
-
- `data-wcs` binding application (text, attribute, class, style, property)
|
|
262
|
-
- `<template data-wcs="for:">` / `if:` / `elseif:` / `else:` structural rendering
|
|
263
|
-
|
|
264
|
-
```html
|
|
265
|
-
<ul>
|
|
266
|
-
<template data-wcs="for: users">
|
|
267
|
-
<li data-wcs="textContent: .name"></li>
|
|
268
|
-
</template>
|
|
269
|
-
</ul>
|
|
270
|
-
<template data-wcs="if: isAdmin">
|
|
271
|
-
<div class="admin-panel">...</div>
|
|
272
|
-
</template>
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Hydration
|
|
276
|
-
|
|
277
|
-
- Automatic `<wcs-ssr>` metadata generation for `<wcs-state enable-ssr>`
|
|
278
|
-
- Client-side hydration restores bindings without re-rendering
|
|
279
|
-
- `<wcs-state>` without `enable-ssr` runs client-only (partial CSR)
|
|
280
|
-
|
|
281
|
-
### Custom Element Waiting
|
|
282
|
-
|
|
283
|
-
- Automatically awaits all custom elements with `static hasConnectedCallbackPromise = true`
|
|
284
|
-
|
|
285
|
-
## What SSR Cannot Do
|
|
286
|
-
|
|
287
|
-
- Execute `<script src="...">` or `<link>` in `<head>`
|
|
288
|
-
- Access browser-specific APIs (localStorage, sessionStorage, navigator, etc.)
|
|
289
|
-
- Render Shadow DOM (Declarative Shadow DOM not supported)
|
|
290
|
-
- Register event handlers (restored via client-side hydration)
|
|
291
|
-
- Load components dynamically via `<wcs-autoloader>`
|
|
292
|
-
|
|
293
|
-
## HTML Splitting Pattern
|
|
294
|
-
|
|
295
|
-
`renderToString` receives only the `<body>` contents. Wrap the result with `<head>` and `<script>` tags on the outside:
|
|
296
|
-
|
|
297
|
-
```javascript
|
|
298
|
-
// server.js
|
|
299
|
-
const ssrBody = await renderToString(template, {
|
|
300
|
-
baseUrl: 'http://localhost:3001',
|
|
301
|
-
});
|
|
302
|
-
const page = `<!DOCTYPE html>
|
|
303
|
-
<html lang="en">
|
|
304
|
-
<head>
|
|
305
|
-
<script type="module" src="/packages/state/dist/auto.js"></script>
|
|
306
|
-
</head>
|
|
307
|
-
<body>${ssrBody}</body>
|
|
308
|
-
</html>`;
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### Using Multiple Packages
|
|
312
|
-
|
|
313
|
-
```javascript
|
|
314
|
-
import { bootstrapState, getBindingsReady } from '@wcstack/state';
|
|
315
|
-
import { bootstrapFetch } from '@wcstack/fetch';
|
|
316
|
-
|
|
317
|
-
const ssrBody = await renderToString(template, {
|
|
318
|
-
baseUrl: 'http://localhost:3001',
|
|
319
|
-
bootstraps: [bootstrapState, bootstrapFetch],
|
|
320
|
-
ready: [(doc) => getBindingsReady(doc)],
|
|
321
|
-
});
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
## How It Works
|
|
325
|
-
|
|
326
|
-
### Rendering Pipeline
|
|
327
|
-
|
|
328
|
-
1. **Global Setup**: Creates a happy-dom `Window` and temporarily installs browser globals (`document`, `HTMLElement`, `MutationObserver`, etc.) on `globalThis`. Disables `URL.createObjectURL` to force the base64 data URL fallback for inline scripts.
|
|
329
|
-
|
|
330
|
-
2. **SSR Mode**: Sets `data-wcs-server` attribute on the `<html>` element. `@wcstack/state` detects this attribute to enable SSR behavior.
|
|
331
|
-
|
|
332
|
-
3. **Bootstrap**: Calls user-provided bootstrap functions (defaults to `bootstrapState()` if omitted).
|
|
333
|
-
|
|
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
|
-
|
|
336
|
-
5. **Ready**: Awaits user-provided ready functions (defaults to `getBindingsReady()`) — text interpolation, attribute mapping, list expansion, conditional evaluation.
|
|
337
|
-
|
|
338
|
-
6. **SSR Metadata**: Each `<wcs-state enable-ssr>` automatically generates a `<wcs-ssr>` element in its `connectedCallback`.
|
|
339
|
-
|
|
340
|
-
7. **Cleanup**: Restores original globals and closes the happy-dom window.
|
|
341
|
-
|
|
342
|
-
### Client-Side Hydration
|
|
343
|
-
|
|
344
|
-
The client-side `@wcstack/state` detects `<wcs-ssr>` elements and:
|
|
345
|
-
1. Restores state from the JSON snapshot (skipping network requests)
|
|
346
|
-
2. Re-attaches template fragments using UUID references
|
|
347
|
-
3. Applies non-attribute properties from the props script
|
|
348
|
-
4. Resumes normal reactive binding
|
|
349
|
-
|
|
350
|
-
The rendered DOM is visible immediately — hydration only wires up interactivity.
|
|
351
|
-
|
|
352
|
-
## License
|
|
353
|
-
|
|
354
|
-
MIT
|
|
1
|
+
# @wcstack/server
|
|
2
|
+
|
|
3
|
+
**What if Web Components rendered on the server?**
|
|
4
|
+
|
|
5
|
+
Imagine a future where your `<wcs-state>` templates are fully rendered before they reach the browser — data is fetched, bindings are resolved, lists are expanded, conditionals are evaluated. The user sees content instantly, and the client picks up exactly where the server left off.
|
|
6
|
+
|
|
7
|
+
That's what `@wcstack/server` explores. It runs your existing `@wcstack/state` templates through happy-dom, produces fully-rendered HTML with embedded hydration data, and lets the client resume reactivity with zero flicker. No special template syntax, no server-specific markup — just the same HTML you already write.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
### Basic Features
|
|
12
|
+
- **Full Template Rendering**: Runs `@wcstack/state` bindings server-side — text, attributes, `for` loops, `if`/`elseif`/`else` conditionals, filters, and mustache `{{ }}` syntax.
|
|
13
|
+
- **Automatic Hydration Data**: Generates `<wcs-ssr>` elements containing state snapshots, template fragments, and property maps for seamless client-side hydration.
|
|
14
|
+
- **Async Data Fetching**: Supports `$connectedCallback` with `fetch()` — server waits for all async operations before rendering.
|
|
15
|
+
- **RenderCore**: A headless, event-driven rendering class that follows the `wc-bindable` protocol for observable `html` / `loading` / `error` state.
|
|
16
|
+
- **Zero Browser Dependencies**: Runs in Node.js with happy-dom as the only runtime dependency.
|
|
17
|
+
|
|
18
|
+
### Unique Features
|
|
19
|
+
- **Drop-in SSR**: No changes to your client-side templates. Add `enable-ssr` to `<wcs-state>` and render with `renderToString()`.
|
|
20
|
+
- **Template Fragment Preservation**: `for`/`if` template sources are captured with UUID references so the client can re-execute structural directives.
|
|
21
|
+
- **Property Hydration**: DOM properties that can't be expressed as attributes (e.g., `innerHTML`) are serialized separately and restored during hydration.
|
|
22
|
+
- **wc-bindable Protocol**: `RenderCore` exposes rendering state via the standard protocol, enabling the same `bind()` pattern on both server and client.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @wcstack/server
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### `renderToString()` — One-shot rendering
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { renderToString } from "@wcstack/server";
|
|
36
|
+
|
|
37
|
+
const html = await renderToString(`
|
|
38
|
+
<wcs-state json='{"items":["Apple","Banana","Cherry"]}' enable-ssr>
|
|
39
|
+
</wcs-state>
|
|
40
|
+
<ul>
|
|
41
|
+
<template data-wcs="for: items">
|
|
42
|
+
<li data-wcs="textContent: items.*"></li>
|
|
43
|
+
</template>
|
|
44
|
+
</ul>
|
|
45
|
+
`);
|
|
46
|
+
|
|
47
|
+
console.log(html);
|
|
48
|
+
// Fully rendered HTML with <wcs-ssr> hydration data
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `RenderCore` — Observable rendering with caching
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import { RenderCore } from "@wcstack/server";
|
|
55
|
+
|
|
56
|
+
const renderer = new RenderCore();
|
|
57
|
+
|
|
58
|
+
// Listen to state changes via wc-bindable protocol
|
|
59
|
+
renderer.addEventListener("wcs-render:loading-changed", (e) => {
|
|
60
|
+
console.log("loading:", e.detail);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
renderer.addEventListener("wcs-render:html-changed", (e) => {
|
|
64
|
+
console.log("rendered:", e.detail.length, "bytes");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Render and cache
|
|
68
|
+
await renderer.render(templateHtml);
|
|
69
|
+
|
|
70
|
+
// Subsequent reads use the cached result
|
|
71
|
+
console.log(renderer.html);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### `renderToString(html: string): Promise<string>`
|
|
77
|
+
|
|
78
|
+
Renders an HTML string containing `@wcstack/state` templates. Returns fully-rendered HTML with hydration data for any `<wcs-state enable-ssr>` elements.
|
|
79
|
+
|
|
80
|
+
**Rendering pipeline:**
|
|
81
|
+
1. Creates a happy-dom window and installs browser globals
|
|
82
|
+
2. Parses HTML and triggers `connectedCallback` on all `<wcs-state>` elements
|
|
83
|
+
3. Awaits all `$connectedCallback` promises (including `fetch()` calls)
|
|
84
|
+
4. Waits for `buildBindings` to complete
|
|
85
|
+
5. Generates `<wcs-ssr>` elements for states with `enable-ssr`
|
|
86
|
+
6. Restores globals and returns the rendered HTML
|
|
87
|
+
|
|
88
|
+
### `RenderCore`
|
|
89
|
+
|
|
90
|
+
Headless rendering class extending `EventTarget`. Implements the `wc-bindable` protocol.
|
|
91
|
+
|
|
92
|
+
| Property | Type | Description |
|
|
93
|
+
|----------|------|-------------|
|
|
94
|
+
| `html` | `string \| null` | Rendered HTML (cached after `render()`) |
|
|
95
|
+
| `loading` | `boolean` | `true` while rendering is in progress |
|
|
96
|
+
| `error` | `Error \| null` | Error from the last `render()` call, if any |
|
|
97
|
+
|
|
98
|
+
| Method | Returns | Description |
|
|
99
|
+
|--------|---------|-------------|
|
|
100
|
+
| `render(html)` | `Promise<string \| null>` | Renders the template and caches the result. Returns `null` on error. |
|
|
101
|
+
|
|
102
|
+
| Event | Detail | Description |
|
|
103
|
+
|-------|--------|-------------|
|
|
104
|
+
| `wcs-render:html-changed` | `string` | Fired when rendering completes successfully |
|
|
105
|
+
| `wcs-render:loading-changed` | `boolean` | Fired when loading state changes |
|
|
106
|
+
| `wcs-render:error` | `Error` | Fired when rendering fails |
|
|
107
|
+
|
|
108
|
+
**wc-bindable declaration:**
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
static wcBindable = {
|
|
112
|
+
protocol: "wc-bindable",
|
|
113
|
+
version: 1,
|
|
114
|
+
properties: [
|
|
115
|
+
{ name: "html", event: "wcs-render:html-changed" },
|
|
116
|
+
{ name: "loading", event: "wcs-render:loading-changed" },
|
|
117
|
+
{ name: "error", event: "wcs-render:error" },
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Helper Functions
|
|
123
|
+
|
|
124
|
+
| Function | Description |
|
|
125
|
+
|----------|-------------|
|
|
126
|
+
| `installGlobals(window)` | Installs happy-dom globals on `globalThis`. Returns a restore function. |
|
|
127
|
+
| `extractStateData(stateEl)` | Extracts data properties from a `<wcs-state>` element (excludes `$`-prefixed keys and functions). |
|
|
128
|
+
|
|
129
|
+
### Constants
|
|
130
|
+
|
|
131
|
+
| Name | Description |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `GLOBALS_KEYS` | Array of browser global keys installed during SSR (`document`, `HTMLElement`, `Node`, etc.) |
|
|
134
|
+
| `VERSION` | Package version string from `package.json` |
|
|
135
|
+
|
|
136
|
+
## SSR Output Structure
|
|
137
|
+
|
|
138
|
+
When a `<wcs-state>` has the `enable-ssr` attribute, `renderToString()` inserts a `<wcs-ssr>` element immediately before it containing all hydration data:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<!-- Generated by renderToString() -->
|
|
142
|
+
<wcs-ssr name="default" version="0.1.0">
|
|
143
|
+
|
|
144
|
+
<!-- State snapshot -->
|
|
145
|
+
<script type="application/json">{"items":["Apple","Banana","Cherry"]}</script>
|
|
146
|
+
|
|
147
|
+
<!-- Template fragments (for client-side re-execution) -->
|
|
148
|
+
<template id="uuid-1234" data-wcs="for: items">
|
|
149
|
+
<li data-wcs="textContent: items.*"></li>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<!-- Non-attribute properties (optional) -->
|
|
153
|
+
<script type="application/json" data-wcs-ssr-props>
|
|
154
|
+
{"wcs-ssr-0": {"innerHTML": "<b>rich</b>"}}
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
</wcs-ssr>
|
|
158
|
+
|
|
159
|
+
<wcs-state json='...' enable-ssr></wcs-state>
|
|
160
|
+
|
|
161
|
+
<!-- Rendered output (visible immediately) -->
|
|
162
|
+
<ul>
|
|
163
|
+
<li>Apple</li>
|
|
164
|
+
<li>Banana</li>
|
|
165
|
+
<li>Cherry</li>
|
|
166
|
+
</ul>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The client-side `@wcstack/state` reads the `<wcs-ssr>` element during hydration, restores state and templates, and resumes reactivity without re-rendering.
|
|
170
|
+
|
|
171
|
+
## Server Integration Example
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
import { createServer } from "node:http";
|
|
175
|
+
import { RenderCore } from "@wcstack/server";
|
|
176
|
+
|
|
177
|
+
const renderer = new RenderCore();
|
|
178
|
+
|
|
179
|
+
const template = `
|
|
180
|
+
<wcs-state enable-ssr>
|
|
181
|
+
<script type="module">
|
|
182
|
+
export default {
|
|
183
|
+
async $connectedCallback() {
|
|
184
|
+
const res = await fetch("http://localhost:3000/api/data");
|
|
185
|
+
this.items = await res.json();
|
|
186
|
+
},
|
|
187
|
+
items: []
|
|
188
|
+
};
|
|
189
|
+
</script>
|
|
190
|
+
</wcs-state>
|
|
191
|
+
<ul>
|
|
192
|
+
<template data-wcs="for: items">
|
|
193
|
+
<li data-wcs="textContent: items.*"></li>
|
|
194
|
+
</template>
|
|
195
|
+
</ul>
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
createServer(async (req, res) => {
|
|
199
|
+
if (!renderer.html) {
|
|
200
|
+
await renderer.render(template);
|
|
201
|
+
}
|
|
202
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
203
|
+
res.end(renderer.html);
|
|
204
|
+
}).listen(3000);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Input HTML Rules
|
|
208
|
+
|
|
209
|
+
- Pass only the contents of `<body>` — do not include `<html>`, `<head>`, or `<body>` tags.
|
|
210
|
+
- `<script>` / `<link>` external resource loading is not executed.
|
|
211
|
+
→ Provide required packages via `options.bootstraps`.
|
|
212
|
+
|
|
213
|
+
## What SSR Can Do
|
|
214
|
+
|
|
215
|
+
### State Initialization & Data Fetching
|
|
216
|
+
|
|
217
|
+
- Load `<wcs-state>` from `json` attribute, `src` attribute, or inline `<script type="module">`
|
|
218
|
+
- Execute `$connectedCallback` for server-side fetch (API calls, etc.)
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<!-- Direct JSON -->
|
|
222
|
+
<wcs-state enable-ssr json='{"title":"Hello"}'></wcs-state>
|
|
223
|
+
|
|
224
|
+
<!-- Fetch data from API in $connectedCallback -->
|
|
225
|
+
<!-- $connectedCallback is defined as a method on the state object; `this` is the state proxy -->
|
|
226
|
+
<wcs-state enable-ssr>
|
|
227
|
+
<script type="module">
|
|
228
|
+
export default {
|
|
229
|
+
async $connectedCallback() {
|
|
230
|
+
const res = await fetch('/api/users');
|
|
231
|
+
this.users = await res.json();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
</script>
|
|
235
|
+
</wcs-state>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Server Communication with wcs-fetch
|
|
239
|
+
|
|
240
|
+
- `<wcs-fetch>` auto-fetch (without `manual`) also executes on the server
|
|
241
|
+
- Use `manual` + `$connectedCallback` for explicit control:
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<wcs-fetch id="api" url="/api/users" manual></wcs-fetch>
|
|
245
|
+
<wcs-state enable-ssr>
|
|
246
|
+
<script type="module">
|
|
247
|
+
export default {
|
|
248
|
+
async $connectedCallback() {
|
|
249
|
+
const el = document.getElementById('api');
|
|
250
|
+
this.users = await el.fetch();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
</script>
|
|
254
|
+
</wcs-state>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
> Note: `bootstrapFetch` must be included in the `bootstraps` option.
|
|
258
|
+
|
|
259
|
+
### Bindings & Structural Rendering
|
|
260
|
+
|
|
261
|
+
- `data-wcs` binding application (text, attribute, class, style, property)
|
|
262
|
+
- `<template data-wcs="for:">` / `if:` / `elseif:` / `else:` structural rendering
|
|
263
|
+
|
|
264
|
+
```html
|
|
265
|
+
<ul>
|
|
266
|
+
<template data-wcs="for: users">
|
|
267
|
+
<li data-wcs="textContent: .name"></li>
|
|
268
|
+
</template>
|
|
269
|
+
</ul>
|
|
270
|
+
<template data-wcs="if: isAdmin">
|
|
271
|
+
<div class="admin-panel">...</div>
|
|
272
|
+
</template>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Hydration
|
|
276
|
+
|
|
277
|
+
- Automatic `<wcs-ssr>` metadata generation for `<wcs-state enable-ssr>`
|
|
278
|
+
- Client-side hydration restores bindings without re-rendering
|
|
279
|
+
- `<wcs-state>` without `enable-ssr` runs client-only (partial CSR)
|
|
280
|
+
|
|
281
|
+
### Custom Element Waiting
|
|
282
|
+
|
|
283
|
+
- Automatically awaits all custom elements with `static hasConnectedCallbackPromise = true`
|
|
284
|
+
|
|
285
|
+
## What SSR Cannot Do
|
|
286
|
+
|
|
287
|
+
- Execute `<script src="...">` or `<link>` in `<head>`
|
|
288
|
+
- Access browser-specific APIs (localStorage, sessionStorage, navigator, etc.)
|
|
289
|
+
- Render Shadow DOM (Declarative Shadow DOM not supported)
|
|
290
|
+
- Register event handlers (restored via client-side hydration)
|
|
291
|
+
- Load components dynamically via `<wcs-autoloader>`
|
|
292
|
+
|
|
293
|
+
## HTML Splitting Pattern
|
|
294
|
+
|
|
295
|
+
`renderToString` receives only the `<body>` contents. Wrap the result with `<head>` and `<script>` tags on the outside:
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
// server.js
|
|
299
|
+
const ssrBody = await renderToString(template, {
|
|
300
|
+
baseUrl: 'http://localhost:3001',
|
|
301
|
+
});
|
|
302
|
+
const page = `<!DOCTYPE html>
|
|
303
|
+
<html lang="en">
|
|
304
|
+
<head>
|
|
305
|
+
<script type="module" src="/packages/state/dist/auto.js"></script>
|
|
306
|
+
</head>
|
|
307
|
+
<body>${ssrBody}</body>
|
|
308
|
+
</html>`;
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Using Multiple Packages
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
import { bootstrapState, getBindingsReady } from '@wcstack/state';
|
|
315
|
+
import { bootstrapFetch } from '@wcstack/fetch';
|
|
316
|
+
|
|
317
|
+
const ssrBody = await renderToString(template, {
|
|
318
|
+
baseUrl: 'http://localhost:3001',
|
|
319
|
+
bootstraps: [bootstrapState, bootstrapFetch],
|
|
320
|
+
ready: [(doc) => getBindingsReady(doc)],
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## How It Works
|
|
325
|
+
|
|
326
|
+
### Rendering Pipeline
|
|
327
|
+
|
|
328
|
+
1. **Global Setup**: Creates a happy-dom `Window` and temporarily installs browser globals (`document`, `HTMLElement`, `MutationObserver`, etc.) on `globalThis`. Disables `URL.createObjectURL` to force the base64 data URL fallback for inline scripts.
|
|
329
|
+
|
|
330
|
+
2. **SSR Mode**: Sets `data-wcs-server` attribute on the `<html>` element. `@wcstack/state` detects this attribute to enable SSR behavior.
|
|
331
|
+
|
|
332
|
+
3. **Bootstrap**: Calls user-provided bootstrap functions (defaults to `bootstrapState()` if omitted).
|
|
333
|
+
|
|
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
|
+
|
|
336
|
+
5. **Ready**: Awaits user-provided ready functions (defaults to `getBindingsReady()`) — text interpolation, attribute mapping, list expansion, conditional evaluation.
|
|
337
|
+
|
|
338
|
+
6. **SSR Metadata**: Each `<wcs-state enable-ssr>` automatically generates a `<wcs-ssr>` element in its `connectedCallback`.
|
|
339
|
+
|
|
340
|
+
7. **Cleanup**: Restores original globals and closes the happy-dom window.
|
|
341
|
+
|
|
342
|
+
### Client-Side Hydration
|
|
343
|
+
|
|
344
|
+
The client-side `@wcstack/state` detects `<wcs-ssr>` elements and:
|
|
345
|
+
1. Restores state from the JSON snapshot (skipping network requests)
|
|
346
|
+
2. Re-attaches template fragments using UUID references
|
|
347
|
+
3. Applies non-attribute properties from the props script
|
|
348
|
+
4. Resumes normal reactive binding
|
|
349
|
+
|
|
350
|
+
The rendered DOM is visible immediately — hydration only wires up interactivity.
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
MIT
|