@wcstack/server 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +355 -355
- package/README.md +355 -355
- 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 +66 -66
package/README.ja.md
CHANGED
|
@@ -1,355 +1,355 @@
|
|
|
1
|
-
# @wcstack/server
|
|
2
|
-
|
|
3
|
-
**Web Components がサーバーでレンダリングされたら?**
|
|
4
|
-
|
|
5
|
-
`<wcs-state>` テンプレートがブラウザに届く前に完全にレンダリングされる世界を想像してください。データは取得済み、バインディングは解決済み、リストは展開済み、条件分岐は評価済み。ユーザーは即座にコンテンツを目にし、クライアントはサーバーが中断した地点からシームレスに引き継ぎます。
|
|
6
|
-
|
|
7
|
-
`@wcstack/server` はそれを実現します。既存の `@wcstack/state` テンプレートを happy-dom 上で実行し、ハイドレーションデータを埋め込んだレンダリング済み HTML を生成。クライアントはフリッカーなしでリアクティビティを再開します。特別なテンプレート構文もサーバー専用のマークアップも不要 — いつも書いている HTML がそのまま使えます。
|
|
8
|
-
|
|
9
|
-
## 特徴
|
|
10
|
-
|
|
11
|
-
### 基本機能
|
|
12
|
-
- **テンプレートの完全レンダリング**: `@wcstack/state` のバインディングをサーバーサイドで実行 — テキスト、属性、`for` ループ、`if`/`elseif`/`else` 条件分岐、フィルタ、Mustache `{{ }}` 構文に対応
|
|
13
|
-
- **ハイドレーションデータの自動生成**: 状態スナップショット、テンプレートフラグメント、プロパティマップを含む `<wcs-ssr>` 要素を生成し、クライアント側でシームレスにハイドレーション
|
|
14
|
-
- **非同期データ取得**: `$connectedCallback` 内の `fetch()` に対応 — サーバーはすべての非同期処理の完了を待ってからレンダリング
|
|
15
|
-
- **RenderCore**: `wc-bindable` プロトコルに準拠したヘッドレスのイベント駆動レンダリングクラス。`html` / `loading` / `error` の状態を監視可能
|
|
16
|
-
- **ブラウザ依存ゼロ**: Node.js 上で動作し、ランタイム依存は happy-dom のみ
|
|
17
|
-
|
|
18
|
-
### ユニークな機能
|
|
19
|
-
- **ドロップイン SSR**: クライアント側テンプレートの変更不要。`<wcs-state>` に `enable-ssr` を追加して `renderToString()` で呼び出すだけ
|
|
20
|
-
- **テンプレートフラグメントの保存**: `for`/`if` テンプレートのソースを UUID 参照付きでキャプチャし、クライアント側で構造ディレクティブを再実行可能に
|
|
21
|
-
- **プロパティハイドレーション**: 属性では表現できない DOM プロパティ(`innerHTML` など)を個別にシリアライズし、ハイドレーション時に復元
|
|
22
|
-
- **wc-bindable プロトコル**: `RenderCore` は標準プロトコルでレンダリング状態を公開し、サーバーでもクライアントでも同じ `bind()` パターンで利用可能
|
|
23
|
-
|
|
24
|
-
## インストール
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install @wcstack/server
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## クイックスタート
|
|
31
|
-
|
|
32
|
-
### `renderToString()` — ワンショットレンダリング
|
|
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
|
-
// ハイドレーションデータ付きのレンダリング済み HTML
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### `RenderCore` — 監視可能なレンダリング(キャッシュ付き)
|
|
52
|
-
|
|
53
|
-
```javascript
|
|
54
|
-
import { RenderCore } from "@wcstack/server";
|
|
55
|
-
|
|
56
|
-
const renderer = new RenderCore();
|
|
57
|
-
|
|
58
|
-
// wc-bindable プロトコル経由で状態変更をリッスン
|
|
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
|
-
// レンダリングしてキャッシュ
|
|
68
|
-
await renderer.render(templateHtml);
|
|
69
|
-
|
|
70
|
-
// 以降の読み取りはキャッシュを利用
|
|
71
|
-
console.log(renderer.html);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## API リファレンス
|
|
75
|
-
|
|
76
|
-
### `renderToString(html: string): Promise<string>`
|
|
77
|
-
|
|
78
|
-
`@wcstack/state` テンプレートを含む HTML 文字列をレンダリングします。`<wcs-state enable-ssr>` を持つ要素のハイドレーションデータ付きのレンダリング済み HTML を返します。
|
|
79
|
-
|
|
80
|
-
**レンダリングパイプライン:**
|
|
81
|
-
1. happy-dom ウィンドウを作成し、ブラウザグローバルをインストール
|
|
82
|
-
2. HTML をパースし、すべての `<wcs-state>` 要素の `connectedCallback` を発火
|
|
83
|
-
3. すべての `$connectedCallback` プロミス(`fetch()` 呼び出し含む)の完了を待機
|
|
84
|
-
4. `buildBindings` の完了を待機
|
|
85
|
-
5. `enable-ssr` を持つ状態に `<wcs-ssr>` 要素を生成
|
|
86
|
-
6. グローバルを復元し、レンダリング済み HTML を返却
|
|
87
|
-
|
|
88
|
-
### `RenderCore`
|
|
89
|
-
|
|
90
|
-
`EventTarget` を継承したヘッドレスレンダリングクラス。`wc-bindable` プロトコルを実装。
|
|
91
|
-
|
|
92
|
-
| プロパティ | 型 | 説明 |
|
|
93
|
-
|----------|------|-------------|
|
|
94
|
-
| `html` | `string \| null` | レンダリング済み HTML(`render()` 後にキャッシュ) |
|
|
95
|
-
| `loading` | `boolean` | レンダリング中は `true` |
|
|
96
|
-
| `error` | `Error \| null` | 直前の `render()` のエラー(エラーがあれば) |
|
|
97
|
-
|
|
98
|
-
| メソッド | 戻り値 | 説明 |
|
|
99
|
-
|--------|---------|-------------|
|
|
100
|
-
| `render(html)` | `Promise<string \| null>` | テンプレートをレンダリングして結果をキャッシュ。エラー時は `null` を返却 |
|
|
101
|
-
|
|
102
|
-
| イベント | Detail | 説明 |
|
|
103
|
-
|-------|--------|-------------|
|
|
104
|
-
| `wcs-render:html-changed` | `string` | レンダリング成功時に発火 |
|
|
105
|
-
| `wcs-render:loading-changed` | `boolean` | ローディング状態の変更時に発火 |
|
|
106
|
-
| `wcs-render:error` | `Error` | レンダリング失敗時に発火 |
|
|
107
|
-
|
|
108
|
-
**wc-bindable 宣言:**
|
|
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
|
-
### ヘルパー関数
|
|
123
|
-
|
|
124
|
-
| 関数 | 説明 |
|
|
125
|
-
|----------|-------------|
|
|
126
|
-
| `installGlobals(window)` | happy-dom のグローバルを `globalThis` にインストール。復元関数を返す |
|
|
127
|
-
| `extractStateData(stateEl)` | `<wcs-state>` 要素からデータプロパティを抽出(`$` プレフィックスのキーと関数は除外) |
|
|
128
|
-
|
|
129
|
-
### 定数
|
|
130
|
-
|
|
131
|
-
| 名前 | 説明 |
|
|
132
|
-
|------|-------------|
|
|
133
|
-
| `GLOBALS_KEYS` | SSR 中にインストールされるブラウザグローバルキーの配列(`document`、`HTMLElement`、`Node` 等) |
|
|
134
|
-
| `VERSION` | `package.json` から取得したパッケージバージョン文字列 |
|
|
135
|
-
|
|
136
|
-
## SSR 出力構造
|
|
137
|
-
|
|
138
|
-
`<wcs-state>` に `enable-ssr` 属性がある場合、`renderToString()` はその直前にハイドレーションデータを含む `<wcs-ssr>` 要素を挿入します:
|
|
139
|
-
|
|
140
|
-
```html
|
|
141
|
-
<!-- renderToString() が生成 -->
|
|
142
|
-
<wcs-ssr name="default" version="0.1.0">
|
|
143
|
-
|
|
144
|
-
<!-- 状態スナップショット -->
|
|
145
|
-
<script type="application/json">{"items":["Apple","Banana","Cherry"]}</script>
|
|
146
|
-
|
|
147
|
-
<!-- テンプレートフラグメント(クライアント側での再実行用) -->
|
|
148
|
-
<template id="uuid-1234" data-wcs="for: items">
|
|
149
|
-
<li data-wcs="textContent: items.*"></li>
|
|
150
|
-
</template>
|
|
151
|
-
|
|
152
|
-
<!-- 属性で代替不可なプロパティ(オプション) -->
|
|
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
|
-
<!-- レンダリング済み出力(即座に表示) -->
|
|
162
|
-
<ul>
|
|
163
|
-
<li>Apple</li>
|
|
164
|
-
<li>Banana</li>
|
|
165
|
-
<li>Cherry</li>
|
|
166
|
-
</ul>
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
クライアント側の `@wcstack/state` はハイドレーション時に `<wcs-ssr>` 要素を読み取り、状態とテンプレートを復元し、再レンダリングなしでリアクティビティを再開します。
|
|
170
|
-
|
|
171
|
-
## サーバー統合の例
|
|
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
|
-
## 入力 HTML のルール
|
|
208
|
-
|
|
209
|
-
- `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)
|
|
210
|
-
- `<script>` / `<link>` による外部リソース読み込みは実行されない
|
|
211
|
-
→ 必要なパッケージは `options.bootstraps` で明示的に渡す
|
|
212
|
-
|
|
213
|
-
## SSR でできること
|
|
214
|
-
|
|
215
|
-
### 状態の初期化とデータ取得
|
|
216
|
-
|
|
217
|
-
- `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type="module">`)
|
|
218
|
-
- `$connectedCallback` でのサーバーサイド fetch(API 呼び出し等)
|
|
219
|
-
|
|
220
|
-
```html
|
|
221
|
-
<!-- JSON 直接指定 -->
|
|
222
|
-
<wcs-state enable-ssr json='{"title":"Hello"}'></wcs-state>
|
|
223
|
-
|
|
224
|
-
<!-- $connectedCallback で API からデータ取得 -->
|
|
225
|
-
<!-- $connectedCallback は状態オブジェクトのメソッドとして定義、this が 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
|
-
### wcs-fetch を使ったサーバー通信
|
|
239
|
-
|
|
240
|
-
- `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される
|
|
241
|
-
- `manual` + `$connectedCallback` で明示的に制御する場合:
|
|
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
|
-
> ※ `bootstraps` オプションに `bootstrapFetch` を含める必要あり
|
|
258
|
-
|
|
259
|
-
### バインディングと構造レンダリング
|
|
260
|
-
|
|
261
|
-
- `data-wcs` バインディングの適用(text, attribute, class, style, property)
|
|
262
|
-
- `<template data-wcs="for:">` / `if:` / `elseif:` / `else:` の構造レンダリング
|
|
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
|
-
### ハイドレーション
|
|
276
|
-
|
|
277
|
-
- `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成
|
|
278
|
-
- クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)
|
|
279
|
-
- `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)
|
|
280
|
-
|
|
281
|
-
### カスタム要素の待機
|
|
282
|
-
|
|
283
|
-
- `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
|
|
284
|
-
- 安定化ループ: await 後に DOM を再走査し、`$connectedCallback` 中に動的追加されたカスタム要素も待機(最大 10 回)
|
|
285
|
-
|
|
286
|
-
## SSR でできないこと
|
|
287
|
-
|
|
288
|
-
- `<head>` 内の `<script src="...">` や `<link>` の自動実行
|
|
289
|
-
- ブラウザ固有 API(localStorage, sessionStorage, navigator 等)
|
|
290
|
-
- Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)
|
|
291
|
-
- イベントハンドラの登録(クライアント側のハイドレーションで復元)
|
|
292
|
-
- `<wcs-autoloader>` による動的コンポーネント読み込み
|
|
293
|
-
|
|
294
|
-
## HTML の分割パターン
|
|
295
|
-
|
|
296
|
-
`renderToString` には `<body>` の中身だけを渡し、`<head>` や `<script>` タグは外側のテンプレートで囲む:
|
|
297
|
-
|
|
298
|
-
```javascript
|
|
299
|
-
// server.js
|
|
300
|
-
const ssrBody = await renderToString(template, {
|
|
301
|
-
baseUrl: 'http://localhost:3001',
|
|
302
|
-
});
|
|
303
|
-
const page = `<!DOCTYPE html>
|
|
304
|
-
<html lang="ja">
|
|
305
|
-
<head>
|
|
306
|
-
<script type="module" src="/packages/state/dist/auto.js"></script>
|
|
307
|
-
</head>
|
|
308
|
-
<body>${ssrBody}</body>
|
|
309
|
-
</html>`;
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### 複数パッケージを使う場合
|
|
313
|
-
|
|
314
|
-
```javascript
|
|
315
|
-
import { bootstrapState, getBindingsReady } from '@wcstack/state';
|
|
316
|
-
import { bootstrapFetch } from '@wcstack/fetch';
|
|
317
|
-
|
|
318
|
-
const ssrBody = await renderToString(template, {
|
|
319
|
-
baseUrl: 'http://localhost:3001',
|
|
320
|
-
bootstraps: [bootstrapState, bootstrapFetch],
|
|
321
|
-
ready: [(doc) => getBindingsReady(doc)],
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## 仕組み
|
|
326
|
-
|
|
327
|
-
### レンダリングパイプライン
|
|
328
|
-
|
|
329
|
-
1. **グローバルのセットアップ**: happy-dom の `Window` を作成し、ブラウザグローバル(`document`、`HTMLElement`、`MutationObserver` 等)を `globalThis` に一時的にインストール。`URL.createObjectURL` を無効化し、インラインスクリプトの base64 data URL フォールバックを強制。
|
|
330
|
-
|
|
331
|
-
2. **SSR モード**: `<html>` 要素に `data-wcs-server` 属性を設定。`@wcstack/state` はこの属性を検出して SSR 動作を有効化。
|
|
332
|
-
|
|
333
|
-
3. **ブートストラップ**: ユーザー提供の bootstrap 関数を呼び出す(省略時は `bootstrapState()` をデフォルト使用)。
|
|
334
|
-
|
|
335
|
-
4. **HTML パースとコールバック**: `document.body.innerHTML` に HTML をセットすることで、happy-dom の要素ライフサイクルが発火。各 `<wcs-state>` がデータソースをロードし `$connectedCallback` を実行。`hasConnectedCallbackPromise` を持つ全カスタム要素を安定化ループで待機 — await 後に DOM を再走査し、動的追加された要素も検出(最大 10 回)。
|
|
336
|
-
|
|
337
|
-
5. **Ready**: ユーザー提供の ready 関数を待機(デフォルトは `getBindingsReady()`) — テキスト補間、属性マッピング、リスト展開、条件評価。
|
|
338
|
-
|
|
339
|
-
6. **SSR メタデータ**: 各 `<wcs-state enable-ssr>` が `connectedCallback` 内で自動的に `<wcs-ssr>` 要素を生成。
|
|
340
|
-
|
|
341
|
-
7. **クリーンアップ**: 元のグローバルを復元し、happy-dom ウィンドウを閉じる。
|
|
342
|
-
|
|
343
|
-
### クライアント側のハイドレーション
|
|
344
|
-
|
|
345
|
-
クライアント側の `@wcstack/state` は `<wcs-ssr>` 要素を検出し、以下を行います:
|
|
346
|
-
1. JSON スナップショットから状態を復元(ネットワークリクエストをスキップ)
|
|
347
|
-
2. UUID 参照を使ってテンプレートフラグメントを再接続
|
|
348
|
-
3. props スクリプトから属性で代替不可なプロパティを適用
|
|
349
|
-
4. 通常のリアクティブバインディングを再開
|
|
350
|
-
|
|
351
|
-
レンダリング済みの DOM は即座に表示されます — ハイドレーションはインタラクティビティの復元のみを行います。
|
|
352
|
-
|
|
353
|
-
## ライセンス
|
|
354
|
-
|
|
355
|
-
MIT
|
|
1
|
+
# @wcstack/server
|
|
2
|
+
|
|
3
|
+
**Web Components がサーバーでレンダリングされたら?**
|
|
4
|
+
|
|
5
|
+
`<wcs-state>` テンプレートがブラウザに届く前に完全にレンダリングされる世界を想像してください。データは取得済み、バインディングは解決済み、リストは展開済み、条件分岐は評価済み。ユーザーは即座にコンテンツを目にし、クライアントはサーバーが中断した地点からシームレスに引き継ぎます。
|
|
6
|
+
|
|
7
|
+
`@wcstack/server` はそれを実現します。既存の `@wcstack/state` テンプレートを happy-dom 上で実行し、ハイドレーションデータを埋め込んだレンダリング済み HTML を生成。クライアントはフリッカーなしでリアクティビティを再開します。特別なテンプレート構文もサーバー専用のマークアップも不要 — いつも書いている HTML がそのまま使えます。
|
|
8
|
+
|
|
9
|
+
## 特徴
|
|
10
|
+
|
|
11
|
+
### 基本機能
|
|
12
|
+
- **テンプレートの完全レンダリング**: `@wcstack/state` のバインディングをサーバーサイドで実行 — テキスト、属性、`for` ループ、`if`/`elseif`/`else` 条件分岐、フィルタ、Mustache `{{ }}` 構文に対応
|
|
13
|
+
- **ハイドレーションデータの自動生成**: 状態スナップショット、テンプレートフラグメント、プロパティマップを含む `<wcs-ssr>` 要素を生成し、クライアント側でシームレスにハイドレーション
|
|
14
|
+
- **非同期データ取得**: `$connectedCallback` 内の `fetch()` に対応 — サーバーはすべての非同期処理の完了を待ってからレンダリング
|
|
15
|
+
- **RenderCore**: `wc-bindable` プロトコルに準拠したヘッドレスのイベント駆動レンダリングクラス。`html` / `loading` / `error` の状態を監視可能
|
|
16
|
+
- **ブラウザ依存ゼロ**: Node.js 上で動作し、ランタイム依存は happy-dom のみ
|
|
17
|
+
|
|
18
|
+
### ユニークな機能
|
|
19
|
+
- **ドロップイン SSR**: クライアント側テンプレートの変更不要。`<wcs-state>` に `enable-ssr` を追加して `renderToString()` で呼び出すだけ
|
|
20
|
+
- **テンプレートフラグメントの保存**: `for`/`if` テンプレートのソースを UUID 参照付きでキャプチャし、クライアント側で構造ディレクティブを再実行可能に
|
|
21
|
+
- **プロパティハイドレーション**: 属性では表現できない DOM プロパティ(`innerHTML` など)を個別にシリアライズし、ハイドレーション時に復元
|
|
22
|
+
- **wc-bindable プロトコル**: `RenderCore` は標準プロトコルでレンダリング状態を公開し、サーバーでもクライアントでも同じ `bind()` パターンで利用可能
|
|
23
|
+
|
|
24
|
+
## インストール
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @wcstack/server
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## クイックスタート
|
|
31
|
+
|
|
32
|
+
### `renderToString()` — ワンショットレンダリング
|
|
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
|
+
// ハイドレーションデータ付きのレンダリング済み HTML
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `RenderCore` — 監視可能なレンダリング(キャッシュ付き)
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import { RenderCore } from "@wcstack/server";
|
|
55
|
+
|
|
56
|
+
const renderer = new RenderCore();
|
|
57
|
+
|
|
58
|
+
// wc-bindable プロトコル経由で状態変更をリッスン
|
|
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
|
+
// レンダリングしてキャッシュ
|
|
68
|
+
await renderer.render(templateHtml);
|
|
69
|
+
|
|
70
|
+
// 以降の読み取りはキャッシュを利用
|
|
71
|
+
console.log(renderer.html);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API リファレンス
|
|
75
|
+
|
|
76
|
+
### `renderToString(html: string): Promise<string>`
|
|
77
|
+
|
|
78
|
+
`@wcstack/state` テンプレートを含む HTML 文字列をレンダリングします。`<wcs-state enable-ssr>` を持つ要素のハイドレーションデータ付きのレンダリング済み HTML を返します。
|
|
79
|
+
|
|
80
|
+
**レンダリングパイプライン:**
|
|
81
|
+
1. happy-dom ウィンドウを作成し、ブラウザグローバルをインストール
|
|
82
|
+
2. HTML をパースし、すべての `<wcs-state>` 要素の `connectedCallback` を発火
|
|
83
|
+
3. すべての `$connectedCallback` プロミス(`fetch()` 呼び出し含む)の完了を待機
|
|
84
|
+
4. `buildBindings` の完了を待機
|
|
85
|
+
5. `enable-ssr` を持つ状態に `<wcs-ssr>` 要素を生成
|
|
86
|
+
6. グローバルを復元し、レンダリング済み HTML を返却
|
|
87
|
+
|
|
88
|
+
### `RenderCore`
|
|
89
|
+
|
|
90
|
+
`EventTarget` を継承したヘッドレスレンダリングクラス。`wc-bindable` プロトコルを実装。
|
|
91
|
+
|
|
92
|
+
| プロパティ | 型 | 説明 |
|
|
93
|
+
|----------|------|-------------|
|
|
94
|
+
| `html` | `string \| null` | レンダリング済み HTML(`render()` 後にキャッシュ) |
|
|
95
|
+
| `loading` | `boolean` | レンダリング中は `true` |
|
|
96
|
+
| `error` | `Error \| null` | 直前の `render()` のエラー(エラーがあれば) |
|
|
97
|
+
|
|
98
|
+
| メソッド | 戻り値 | 説明 |
|
|
99
|
+
|--------|---------|-------------|
|
|
100
|
+
| `render(html)` | `Promise<string \| null>` | テンプレートをレンダリングして結果をキャッシュ。エラー時は `null` を返却 |
|
|
101
|
+
|
|
102
|
+
| イベント | Detail | 説明 |
|
|
103
|
+
|-------|--------|-------------|
|
|
104
|
+
| `wcs-render:html-changed` | `string` | レンダリング成功時に発火 |
|
|
105
|
+
| `wcs-render:loading-changed` | `boolean` | ローディング状態の変更時に発火 |
|
|
106
|
+
| `wcs-render:error` | `Error` | レンダリング失敗時に発火 |
|
|
107
|
+
|
|
108
|
+
**wc-bindable 宣言:**
|
|
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
|
+
### ヘルパー関数
|
|
123
|
+
|
|
124
|
+
| 関数 | 説明 |
|
|
125
|
+
|----------|-------------|
|
|
126
|
+
| `installGlobals(window)` | happy-dom のグローバルを `globalThis` にインストール。復元関数を返す |
|
|
127
|
+
| `extractStateData(stateEl)` | `<wcs-state>` 要素からデータプロパティを抽出(`$` プレフィックスのキーと関数は除外) |
|
|
128
|
+
|
|
129
|
+
### 定数
|
|
130
|
+
|
|
131
|
+
| 名前 | 説明 |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `GLOBALS_KEYS` | SSR 中にインストールされるブラウザグローバルキーの配列(`document`、`HTMLElement`、`Node` 等) |
|
|
134
|
+
| `VERSION` | `package.json` から取得したパッケージバージョン文字列 |
|
|
135
|
+
|
|
136
|
+
## SSR 出力構造
|
|
137
|
+
|
|
138
|
+
`<wcs-state>` に `enable-ssr` 属性がある場合、`renderToString()` はその直前にハイドレーションデータを含む `<wcs-ssr>` 要素を挿入します:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<!-- renderToString() が生成 -->
|
|
142
|
+
<wcs-ssr name="default" version="0.1.0">
|
|
143
|
+
|
|
144
|
+
<!-- 状態スナップショット -->
|
|
145
|
+
<script type="application/json">{"items":["Apple","Banana","Cherry"]}</script>
|
|
146
|
+
|
|
147
|
+
<!-- テンプレートフラグメント(クライアント側での再実行用) -->
|
|
148
|
+
<template id="uuid-1234" data-wcs="for: items">
|
|
149
|
+
<li data-wcs="textContent: items.*"></li>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<!-- 属性で代替不可なプロパティ(オプション) -->
|
|
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
|
+
<!-- レンダリング済み出力(即座に表示) -->
|
|
162
|
+
<ul>
|
|
163
|
+
<li>Apple</li>
|
|
164
|
+
<li>Banana</li>
|
|
165
|
+
<li>Cherry</li>
|
|
166
|
+
</ul>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
クライアント側の `@wcstack/state` はハイドレーション時に `<wcs-ssr>` 要素を読み取り、状態とテンプレートを復元し、再レンダリングなしでリアクティビティを再開します。
|
|
170
|
+
|
|
171
|
+
## サーバー統合の例
|
|
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
|
+
## 入力 HTML のルール
|
|
208
|
+
|
|
209
|
+
- `<body>` の中身だけを渡す(`<html>`, `<head>`, `<body>` タグは含めない)
|
|
210
|
+
- `<script>` / `<link>` による外部リソース読み込みは実行されない
|
|
211
|
+
→ 必要なパッケージは `options.bootstraps` で明示的に渡す
|
|
212
|
+
|
|
213
|
+
## SSR でできること
|
|
214
|
+
|
|
215
|
+
### 状態の初期化とデータ取得
|
|
216
|
+
|
|
217
|
+
- `<wcs-state>` の状態ロード(json 属性, src 属性, inline `<script type="module">`)
|
|
218
|
+
- `$connectedCallback` でのサーバーサイド fetch(API 呼び出し等)
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<!-- JSON 直接指定 -->
|
|
222
|
+
<wcs-state enable-ssr json='{"title":"Hello"}'></wcs-state>
|
|
223
|
+
|
|
224
|
+
<!-- $connectedCallback で API からデータ取得 -->
|
|
225
|
+
<!-- $connectedCallback は状態オブジェクトのメソッドとして定義、this が 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
|
+
### wcs-fetch を使ったサーバー通信
|
|
239
|
+
|
|
240
|
+
- `<wcs-fetch>` の auto-fetch(`manual` なし)はサーバーでも実行される
|
|
241
|
+
- `manual` + `$connectedCallback` で明示的に制御する場合:
|
|
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
|
+
> ※ `bootstraps` オプションに `bootstrapFetch` を含める必要あり
|
|
258
|
+
|
|
259
|
+
### バインディングと構造レンダリング
|
|
260
|
+
|
|
261
|
+
- `data-wcs` バインディングの適用(text, attribute, class, style, property)
|
|
262
|
+
- `<template data-wcs="for:">` / `if:` / `elseif:` / `else:` の構造レンダリング
|
|
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
|
+
### ハイドレーション
|
|
276
|
+
|
|
277
|
+
- `enable-ssr` 付き `<wcs-state>` の `<wcs-ssr>` メタデータ自動生成
|
|
278
|
+
- クライアント側でのハイドレーション(再レンダリングなしでバインディング復元)
|
|
279
|
+
- `enable-ssr` を外した `<wcs-state>` はクライアントのみで動作(部分 CSR)
|
|
280
|
+
|
|
281
|
+
### カスタム要素の待機
|
|
282
|
+
|
|
283
|
+
- `static hasConnectedCallbackPromise = true` プロトコル準拠の全カスタム要素を自動待機
|
|
284
|
+
- 安定化ループ: await 後に DOM を再走査し、`$connectedCallback` 中に動的追加されたカスタム要素も待機(最大 10 回)
|
|
285
|
+
|
|
286
|
+
## SSR でできないこと
|
|
287
|
+
|
|
288
|
+
- `<head>` 内の `<script src="...">` や `<link>` の自動実行
|
|
289
|
+
- ブラウザ固有 API(localStorage, sessionStorage, navigator 等)
|
|
290
|
+
- Shadow DOM のレンダリング(Declarative Shadow DOM 非対応)
|
|
291
|
+
- イベントハンドラの登録(クライアント側のハイドレーションで復元)
|
|
292
|
+
- `<wcs-autoloader>` による動的コンポーネント読み込み
|
|
293
|
+
|
|
294
|
+
## HTML の分割パターン
|
|
295
|
+
|
|
296
|
+
`renderToString` には `<body>` の中身だけを渡し、`<head>` や `<script>` タグは外側のテンプレートで囲む:
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
// server.js
|
|
300
|
+
const ssrBody = await renderToString(template, {
|
|
301
|
+
baseUrl: 'http://localhost:3001',
|
|
302
|
+
});
|
|
303
|
+
const page = `<!DOCTYPE html>
|
|
304
|
+
<html lang="ja">
|
|
305
|
+
<head>
|
|
306
|
+
<script type="module" src="/packages/state/dist/auto.js"></script>
|
|
307
|
+
</head>
|
|
308
|
+
<body>${ssrBody}</body>
|
|
309
|
+
</html>`;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 複数パッケージを使う場合
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
import { bootstrapState, getBindingsReady } from '@wcstack/state';
|
|
316
|
+
import { bootstrapFetch } from '@wcstack/fetch';
|
|
317
|
+
|
|
318
|
+
const ssrBody = await renderToString(template, {
|
|
319
|
+
baseUrl: 'http://localhost:3001',
|
|
320
|
+
bootstraps: [bootstrapState, bootstrapFetch],
|
|
321
|
+
ready: [(doc) => getBindingsReady(doc)],
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## 仕組み
|
|
326
|
+
|
|
327
|
+
### レンダリングパイプライン
|
|
328
|
+
|
|
329
|
+
1. **グローバルのセットアップ**: happy-dom の `Window` を作成し、ブラウザグローバル(`document`、`HTMLElement`、`MutationObserver` 等)を `globalThis` に一時的にインストール。`URL.createObjectURL` を無効化し、インラインスクリプトの base64 data URL フォールバックを強制。
|
|
330
|
+
|
|
331
|
+
2. **SSR モード**: `<html>` 要素に `data-wcs-server` 属性を設定。`@wcstack/state` はこの属性を検出して SSR 動作を有効化。
|
|
332
|
+
|
|
333
|
+
3. **ブートストラップ**: ユーザー提供の bootstrap 関数を呼び出す(省略時は `bootstrapState()` をデフォルト使用)。
|
|
334
|
+
|
|
335
|
+
4. **HTML パースとコールバック**: `document.body.innerHTML` に HTML をセットすることで、happy-dom の要素ライフサイクルが発火。各 `<wcs-state>` がデータソースをロードし `$connectedCallback` を実行。`hasConnectedCallbackPromise` を持つ全カスタム要素を安定化ループで待機 — await 後に DOM を再走査し、動的追加された要素も検出(最大 10 回)。
|
|
336
|
+
|
|
337
|
+
5. **Ready**: ユーザー提供の ready 関数を待機(デフォルトは `getBindingsReady()`) — テキスト補間、属性マッピング、リスト展開、条件評価。
|
|
338
|
+
|
|
339
|
+
6. **SSR メタデータ**: 各 `<wcs-state enable-ssr>` が `connectedCallback` 内で自動的に `<wcs-ssr>` 要素を生成。
|
|
340
|
+
|
|
341
|
+
7. **クリーンアップ**: 元のグローバルを復元し、happy-dom ウィンドウを閉じる。
|
|
342
|
+
|
|
343
|
+
### クライアント側のハイドレーション
|
|
344
|
+
|
|
345
|
+
クライアント側の `@wcstack/state` は `<wcs-ssr>` 要素を検出し、以下を行います:
|
|
346
|
+
1. JSON スナップショットから状態を復元(ネットワークリクエストをスキップ)
|
|
347
|
+
2. UUID 参照を使ってテンプレートフラグメントを再接続
|
|
348
|
+
3. props スクリプトから属性で代替不可なプロパティを適用
|
|
349
|
+
4. 通常のリアクティブバインディングを再開
|
|
350
|
+
|
|
351
|
+
レンダリング済みの DOM は即座に表示されます — ハイドレーションはインタラクティビティの復元のみを行います。
|
|
352
|
+
|
|
353
|
+
## ライセンス
|
|
354
|
+
|
|
355
|
+
MIT
|