@vertz/ui-server 0.2.0 → 0.2.4
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.md +298 -310
- package/dist/bun-dev-server.d.ts +122 -0
- package/dist/bun-dev-server.js +2247 -0
- package/dist/bun-plugin/fast-refresh-dom-state.d.ts +51 -0
- package/dist/bun-plugin/fast-refresh-dom-state.js +10 -0
- package/dist/bun-plugin/fast-refresh-runtime.d.ts +43 -0
- package/dist/bun-plugin/fast-refresh-runtime.js +150 -0
- package/dist/bun-plugin/index.d.ts +120 -0
- package/dist/bun-plugin/index.js +216 -0
- package/dist/dom-shim/index.d.ts +37 -6
- package/dist/dom-shim/index.js +12 -324
- package/dist/index.d.ts +331 -64
- package/dist/index.js +285 -292
- package/dist/jsx-runtime/index.js +15 -2
- package/dist/shared/chunk-2qsqp9xj.js +150 -0
- package/dist/shared/chunk-32688jav.js +564 -0
- package/dist/shared/chunk-4t0ekdyv.js +513 -0
- package/dist/shared/chunk-eb80r8e8.js +4 -0
- package/dist/ssr/index.d.ts +86 -0
- package/dist/ssr/index.js +11 -0
- package/package.json +35 -18
package/dist/index.js
CHANGED
|
@@ -1,58 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
function serializeToHtml(node) {
|
|
36
|
-
if (typeof node === "string") {
|
|
37
|
-
return escapeHtml(node);
|
|
38
|
-
}
|
|
39
|
-
if (isRawHtml(node)) {
|
|
40
|
-
return node.html;
|
|
41
|
-
}
|
|
42
|
-
const { tag, attrs, children } = node;
|
|
43
|
-
const attrStr = serializeAttrs(attrs);
|
|
44
|
-
if (VOID_ELEMENTS.has(tag)) {
|
|
45
|
-
return `<${tag}${attrStr}>`;
|
|
46
|
-
}
|
|
47
|
-
const isRawText = RAW_TEXT_ELEMENTS.has(tag);
|
|
48
|
-
const childrenHtml = children.map((child) => {
|
|
49
|
-
if (typeof child === "string" && isRawText) {
|
|
50
|
-
return child;
|
|
51
|
-
}
|
|
52
|
-
return serializeToHtml(child);
|
|
53
|
-
}).join("");
|
|
54
|
-
return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
|
|
55
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
clearGlobalSSRTimeout,
|
|
3
|
+
collectStreamChunks,
|
|
4
|
+
createSSRDataChunk,
|
|
5
|
+
createSSRHandler,
|
|
6
|
+
createSlotPlaceholder,
|
|
7
|
+
createTemplateChunk,
|
|
8
|
+
encodeChunk,
|
|
9
|
+
escapeAttr,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
getGlobalSSRTimeout,
|
|
12
|
+
getSSRQueries,
|
|
13
|
+
getSSRUrl,
|
|
14
|
+
getStreamingRuntimeScript,
|
|
15
|
+
isInSSR,
|
|
16
|
+
registerSSRQuery,
|
|
17
|
+
renderToStream,
|
|
18
|
+
resetSlotCounter,
|
|
19
|
+
safeSerialize,
|
|
20
|
+
serializeToHtml,
|
|
21
|
+
setGlobalSSRTimeout,
|
|
22
|
+
ssrDiscoverQueries,
|
|
23
|
+
ssrRenderToString,
|
|
24
|
+
ssrStorage,
|
|
25
|
+
streamToString
|
|
26
|
+
} from "./shared/chunk-32688jav.js";
|
|
27
|
+
import {
|
|
28
|
+
SSRElement,
|
|
29
|
+
createSSRAdapter,
|
|
30
|
+
installDomShim,
|
|
31
|
+
rawHtml,
|
|
32
|
+
removeDomShim
|
|
33
|
+
} from "./shared/chunk-4t0ekdyv.js";
|
|
56
34
|
|
|
57
35
|
// src/asset-pipeline.ts
|
|
58
36
|
function renderAssetTags(assets) {
|
|
@@ -79,136 +57,6 @@ function inlineCriticalCss(css) {
|
|
|
79
57
|
const safeCss = css.replace(/<\/style>/gi, "<\\/style>");
|
|
80
58
|
return `<style>${safeCss}</style>`;
|
|
81
59
|
}
|
|
82
|
-
// src/dev-server.ts
|
|
83
|
-
import { createServer as createHttpServer } from "node:http";
|
|
84
|
-
import { InternalServerErrorException } from "@vertz/server";
|
|
85
|
-
import { createServer as createViteServer } from "vite";
|
|
86
|
-
function createDevServer(options) {
|
|
87
|
-
const {
|
|
88
|
-
entry,
|
|
89
|
-
port = 5173,
|
|
90
|
-
host = "0.0.0.0",
|
|
91
|
-
viteConfig = {},
|
|
92
|
-
middleware,
|
|
93
|
-
skipModuleInvalidation = false,
|
|
94
|
-
logRequests = true
|
|
95
|
-
} = options;
|
|
96
|
-
let vite;
|
|
97
|
-
let httpServer;
|
|
98
|
-
const listen = async () => {
|
|
99
|
-
if (logRequests) {
|
|
100
|
-
console.log("[Server] Starting Vite SSR dev server...");
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
vite = await createViteServer({
|
|
104
|
-
...viteConfig,
|
|
105
|
-
server: {
|
|
106
|
-
...viteConfig.server,
|
|
107
|
-
middlewareMode: true
|
|
108
|
-
},
|
|
109
|
-
appType: "custom"
|
|
110
|
-
});
|
|
111
|
-
if (logRequests) {
|
|
112
|
-
console.log("[Server] Vite dev server created");
|
|
113
|
-
}
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.error("[Server] Failed to create Vite server:", err);
|
|
116
|
-
throw err;
|
|
117
|
-
}
|
|
118
|
-
if (middleware) {
|
|
119
|
-
vite.middlewares.use(middleware);
|
|
120
|
-
}
|
|
121
|
-
vite.middlewares.use(async (req, res, next) => {
|
|
122
|
-
const url = req.url || "/";
|
|
123
|
-
try {
|
|
124
|
-
if (url.startsWith("/@") || url.startsWith("/node_modules")) {
|
|
125
|
-
return next();
|
|
126
|
-
}
|
|
127
|
-
if (url === entry || url.startsWith("/src/")) {
|
|
128
|
-
return next();
|
|
129
|
-
}
|
|
130
|
-
if (logRequests) {
|
|
131
|
-
console.log(`[Server] Rendering: ${url}`);
|
|
132
|
-
}
|
|
133
|
-
if (!skipModuleInvalidation) {
|
|
134
|
-
for (const mod of vite.moduleGraph.idToModuleMap.values()) {
|
|
135
|
-
if (mod.ssrModule) {
|
|
136
|
-
vite.moduleGraph.invalidateModule(mod);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const entryModule = await vite.ssrLoadModule(entry);
|
|
141
|
-
if (!entryModule.renderToString) {
|
|
142
|
-
throw new InternalServerErrorException(`Entry module "${entry}" does not export a renderToString function`);
|
|
143
|
-
}
|
|
144
|
-
const html = await entryModule.renderToString(url);
|
|
145
|
-
const transformedHtml = await vite.transformIndexHtml(url, html);
|
|
146
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
147
|
-
res.end(transformedHtml);
|
|
148
|
-
} catch (err) {
|
|
149
|
-
console.error("[Server] SSR error:", err);
|
|
150
|
-
if (err instanceof Error) {
|
|
151
|
-
vite.ssrFixStacktrace(err);
|
|
152
|
-
}
|
|
153
|
-
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
154
|
-
res.end(err.stack || String(err));
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
httpServer = createHttpServer(vite.middlewares);
|
|
158
|
-
httpServer.on("error", (err) => {
|
|
159
|
-
if (err.code === "EADDRINUSE") {
|
|
160
|
-
console.error(`[Server] Port ${port} is already in use`);
|
|
161
|
-
} else {
|
|
162
|
-
console.error(`[Server] Server error:`, err);
|
|
163
|
-
}
|
|
164
|
-
process.exit(1);
|
|
165
|
-
});
|
|
166
|
-
await new Promise((resolve) => {
|
|
167
|
-
httpServer.listen(port, host, () => {
|
|
168
|
-
if (logRequests) {
|
|
169
|
-
console.log(`[Server] Running at http://${host}:${port}`);
|
|
170
|
-
console.log(`[Server] Local: http://localhost:${port}`);
|
|
171
|
-
}
|
|
172
|
-
resolve();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
const shutdown = () => {
|
|
176
|
-
if (logRequests) {
|
|
177
|
-
console.log("[Server] Shutting down...");
|
|
178
|
-
}
|
|
179
|
-
httpServer.close();
|
|
180
|
-
vite.close();
|
|
181
|
-
process.exit(0);
|
|
182
|
-
};
|
|
183
|
-
process.on("SIGTERM", shutdown);
|
|
184
|
-
process.on("SIGINT", shutdown);
|
|
185
|
-
};
|
|
186
|
-
const close = async () => {
|
|
187
|
-
if (httpServer) {
|
|
188
|
-
await new Promise((resolve, reject) => {
|
|
189
|
-
httpServer.close((err) => {
|
|
190
|
-
if (err)
|
|
191
|
-
reject(err);
|
|
192
|
-
else
|
|
193
|
-
resolve();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (vite) {
|
|
198
|
-
await vite.close();
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
return {
|
|
202
|
-
listen,
|
|
203
|
-
close,
|
|
204
|
-
get vite() {
|
|
205
|
-
return vite;
|
|
206
|
-
},
|
|
207
|
-
get httpServer() {
|
|
208
|
-
return httpServer;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
60
|
// src/head.ts
|
|
213
61
|
class HeadCollector {
|
|
214
62
|
entries = [];
|
|
@@ -263,141 +111,286 @@ function wrapWithHydrationMarkers(node, options) {
|
|
|
263
111
|
children: newChildren
|
|
264
112
|
};
|
|
265
113
|
}
|
|
266
|
-
// src/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
children: typeof fallback === "string" ? [fallback] : [fallback],
|
|
277
|
-
_slotId: id
|
|
278
|
-
};
|
|
279
|
-
return placeholder;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// src/streaming.ts
|
|
283
|
-
var encoder = new TextEncoder;
|
|
284
|
-
var decoder = new TextDecoder;
|
|
285
|
-
function encodeChunk(html) {
|
|
286
|
-
return encoder.encode(html);
|
|
287
|
-
}
|
|
288
|
-
async function streamToString(stream) {
|
|
289
|
-
const reader = stream.getReader();
|
|
290
|
-
const parts = [];
|
|
291
|
-
for (;; ) {
|
|
292
|
-
const { done, value } = await reader.read();
|
|
293
|
-
if (done)
|
|
294
|
-
break;
|
|
295
|
-
parts.push(decoder.decode(value, { stream: true }));
|
|
114
|
+
// src/render-page.ts
|
|
115
|
+
function buildHeadHtml(options, _componentHeadEntries = []) {
|
|
116
|
+
const entries = [];
|
|
117
|
+
entries.push({ tag: "meta", attrs: { charset: "utf-8" } });
|
|
118
|
+
entries.push({
|
|
119
|
+
tag: "meta",
|
|
120
|
+
attrs: { name: "viewport", content: "width=device-width, initial-scale=1" }
|
|
121
|
+
});
|
|
122
|
+
if (options.title) {
|
|
123
|
+
entries.push({ tag: "title", textContent: options.title });
|
|
296
124
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
async function collectStreamChunks(stream) {
|
|
301
|
-
const reader = stream.getReader();
|
|
302
|
-
const chunks = [];
|
|
303
|
-
for (;; ) {
|
|
304
|
-
const { done, value } = await reader.read();
|
|
305
|
-
if (done)
|
|
306
|
-
break;
|
|
307
|
-
chunks.push(decoder.decode(value, { stream: true }));
|
|
125
|
+
if (options.description) {
|
|
126
|
+
entries.push({ tag: "meta", attrs: { name: "description", content: options.description } });
|
|
308
127
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const nonceAttr = nonce != null ? ` nonce="${escapeNonce(nonce)}"` : "";
|
|
317
|
-
return `<template id="${tmplId}">${resolvedHtml}</template>` + `<script${nonceAttr}>` + `(function(){` + `var s=document.getElementById("${slotRef}");` + `var t=document.getElementById("${tmplId}");` + `if(s&&t){s.replaceWith(t.content.cloneNode(true));t.remove()}` + `})()` + "</script>";
|
|
318
|
-
}
|
|
319
|
-
function escapeNonce(value) {
|
|
320
|
-
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// src/render-to-stream.ts
|
|
324
|
-
function isSuspenseNode(node) {
|
|
325
|
-
return typeof node === "object" && "tag" in node && node.tag === "__suspense" && "_resolve" in node;
|
|
326
|
-
}
|
|
327
|
-
function renderToStream(tree, options) {
|
|
328
|
-
const pendingBoundaries = [];
|
|
329
|
-
function walkAndSerialize(node) {
|
|
330
|
-
if (typeof node === "string") {
|
|
331
|
-
return escapeHtml(node);
|
|
128
|
+
const hasOgConfig = options.og != null;
|
|
129
|
+
const hasTitleOrDesc = options.title != null || options.description != null;
|
|
130
|
+
if (hasOgConfig || hasTitleOrDesc) {
|
|
131
|
+
const ogTitle = options.og?.title ?? options.title ?? "";
|
|
132
|
+
const ogDesc = options.og?.description ?? options.description ?? "";
|
|
133
|
+
if (ogTitle) {
|
|
134
|
+
entries.push({ tag: "meta", attrs: { property: "og:title", content: ogTitle } });
|
|
332
135
|
}
|
|
333
|
-
if (
|
|
334
|
-
|
|
136
|
+
if (ogDesc) {
|
|
137
|
+
entries.push({ tag: "meta", attrs: { property: "og:description", content: ogDesc } });
|
|
335
138
|
}
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
pendingBoundaries.push({
|
|
339
|
-
slotId: placeholder._slotId,
|
|
340
|
-
resolve: node._resolve
|
|
341
|
-
});
|
|
342
|
-
return serializeToHtml(placeholder);
|
|
139
|
+
if (options.og?.image) {
|
|
140
|
+
entries.push({ tag: "meta", attrs: { property: "og:image", content: options.og.image } });
|
|
343
141
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${escapeAttr(v)}"`).join("");
|
|
347
|
-
if (VOID_ELEMENTS.has(tag)) {
|
|
348
|
-
return `<${tag}${attrStr}>`;
|
|
142
|
+
if (options.og?.url) {
|
|
143
|
+
entries.push({ tag: "meta", attrs: { property: "og:url", content: options.og.url } });
|
|
349
144
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return walkAndSerialize(child);
|
|
355
|
-
}).join("");
|
|
356
|
-
return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
|
|
145
|
+
entries.push({
|
|
146
|
+
tag: "meta",
|
|
147
|
+
attrs: { property: "og:type", content: options.og?.type ?? "website" }
|
|
148
|
+
});
|
|
357
149
|
}
|
|
358
|
-
|
|
150
|
+
if (options.twitter) {
|
|
151
|
+
if (options.twitter.card) {
|
|
152
|
+
entries.push({ tag: "meta", attrs: { name: "twitter:card", content: options.twitter.card } });
|
|
153
|
+
}
|
|
154
|
+
if (options.twitter.site) {
|
|
155
|
+
entries.push({ tag: "meta", attrs: { name: "twitter:site", content: options.twitter.site } });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (options.favicon) {
|
|
159
|
+
entries.push({ tag: "link", attrs: { rel: "icon", href: options.favicon } });
|
|
160
|
+
}
|
|
161
|
+
if (options.styles) {
|
|
162
|
+
for (const style of options.styles) {
|
|
163
|
+
entries.push({ tag: "link", attrs: { rel: "stylesheet", href: style } });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return renderHeadToHtml(entries);
|
|
167
|
+
}
|
|
168
|
+
function renderPage(vnode, options) {
|
|
169
|
+
const status = options?.status ?? 200;
|
|
170
|
+
const lang = options?.lang ?? "en";
|
|
171
|
+
const headHtml = buildHeadHtml(options ?? {});
|
|
172
|
+
const fullHeadHtml = options?.head ? `${headHtml}
|
|
173
|
+
${options.head}` : headHtml;
|
|
174
|
+
const scriptsHtml = options?.scripts ? `
|
|
175
|
+
` + options.scripts.map((src) => ` <script type="module" src="${escapeAttr(src)}"></script>`).join(`
|
|
176
|
+
`) : "";
|
|
177
|
+
const stream = new ReadableStream({
|
|
359
178
|
async start(controller) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
179
|
+
controller.enqueue(encodeChunk(`<!DOCTYPE html>
|
|
180
|
+
`));
|
|
181
|
+
controller.enqueue(encodeChunk(`<html lang="${escapeAttr(lang)}">
|
|
182
|
+
`));
|
|
183
|
+
controller.enqueue(encodeChunk(`<head>
|
|
184
|
+
`));
|
|
185
|
+
controller.enqueue(encodeChunk(fullHeadHtml));
|
|
186
|
+
controller.enqueue(encodeChunk(`
|
|
187
|
+
</head>
|
|
188
|
+
`));
|
|
189
|
+
controller.enqueue(encodeChunk(`<body>
|
|
190
|
+
`));
|
|
191
|
+
const componentStream = renderToStream(vnode);
|
|
192
|
+
const reader = componentStream.getReader();
|
|
193
|
+
try {
|
|
194
|
+
while (true) {
|
|
195
|
+
const { done, value } = await reader.read();
|
|
196
|
+
if (done)
|
|
197
|
+
break;
|
|
198
|
+
controller.enqueue(value);
|
|
377
199
|
}
|
|
200
|
+
} finally {
|
|
201
|
+
reader.releaseLock();
|
|
378
202
|
}
|
|
203
|
+
controller.enqueue(encodeChunk(scriptsHtml));
|
|
204
|
+
controller.enqueue(encodeChunk(`
|
|
205
|
+
</body>
|
|
206
|
+
`));
|
|
207
|
+
controller.enqueue(encodeChunk("</html>"));
|
|
379
208
|
controller.close();
|
|
380
209
|
}
|
|
381
210
|
});
|
|
211
|
+
return new Response(stream, {
|
|
212
|
+
status,
|
|
213
|
+
headers: {
|
|
214
|
+
"content-type": "text/html; charset=utf-8"
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// src/render-to-html.ts
|
|
219
|
+
import { compileTheme } from "@vertz/ui";
|
|
220
|
+
import { setAdapter } from "@vertz/ui/internals";
|
|
221
|
+
function installSSR() {
|
|
222
|
+
setAdapter(createSSRAdapter());
|
|
223
|
+
installDomShim();
|
|
224
|
+
}
|
|
225
|
+
function removeSSR() {
|
|
226
|
+
setAdapter(null);
|
|
227
|
+
removeDomShim();
|
|
228
|
+
}
|
|
229
|
+
async function twoPassRender(options) {
|
|
230
|
+
options.app();
|
|
231
|
+
const queries = getSSRQueries();
|
|
232
|
+
if (queries.length > 0) {
|
|
233
|
+
await Promise.allSettled(queries.map((entry) => Promise.race([
|
|
234
|
+
entry.promise.then((data) => {
|
|
235
|
+
entry.resolve(data);
|
|
236
|
+
entry.resolved = true;
|
|
237
|
+
}),
|
|
238
|
+
new Promise((r) => setTimeout(r, entry.timeout))
|
|
239
|
+
])));
|
|
240
|
+
const store = ssrStorage.getStore();
|
|
241
|
+
if (store)
|
|
242
|
+
store.queries = [];
|
|
243
|
+
}
|
|
244
|
+
const pendingQueries = queries.filter((q) => !q.resolved);
|
|
245
|
+
const vnode = options.app();
|
|
246
|
+
const fakeDoc = globalThis.document;
|
|
247
|
+
const collectedCSS = [];
|
|
248
|
+
if (fakeDoc?.head?.children) {
|
|
249
|
+
for (const child of fakeDoc.head.children) {
|
|
250
|
+
if (child instanceof SSRElement && child.tag === "style") {
|
|
251
|
+
const cssText = child.children?.join("") ?? "";
|
|
252
|
+
if (cssText)
|
|
253
|
+
collectedCSS.push(cssText);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const themeCss = options.theme ? compileTheme(options.theme).css : "";
|
|
258
|
+
const allStyles = [themeCss, ...options.styles ?? [], ...collectedCSS].filter(Boolean);
|
|
259
|
+
const styleTags = allStyles.map((css) => `<style>${css}</style>`).join(`
|
|
260
|
+
`);
|
|
261
|
+
const metaHtml = options.head?.meta?.map((m) => `<meta ${m.name ? `name="${m.name}"` : `property="${m.property}"`} content="${m.content}">`).join(`
|
|
262
|
+
`) ?? "";
|
|
263
|
+
const linkHtml = options.head?.links?.map((link) => `<link rel="${link.rel}" href="${link.href}">`).join(`
|
|
264
|
+
`) ?? "";
|
|
265
|
+
const runtimeScript = pendingQueries.length > 0 ? getStreamingRuntimeScript(options.nonce) : "";
|
|
266
|
+
const headContent = [metaHtml, linkHtml, styleTags, runtimeScript].filter(Boolean).join(`
|
|
267
|
+
`);
|
|
268
|
+
const response = renderPage(vnode, {
|
|
269
|
+
title: options.head?.title,
|
|
270
|
+
head: headContent
|
|
271
|
+
});
|
|
272
|
+
const html = await response.text();
|
|
273
|
+
return { html, pendingQueries };
|
|
274
|
+
}
|
|
275
|
+
async function renderToHTMLStream(options) {
|
|
276
|
+
installSSR();
|
|
277
|
+
const streamTimeout = options.streamTimeout ?? 30000;
|
|
278
|
+
return ssrStorage.run({ url: options.url, errors: [], queries: [] }, async () => {
|
|
279
|
+
try {
|
|
280
|
+
if (options.ssrTimeout !== undefined) {
|
|
281
|
+
setGlobalSSRTimeout(options.ssrTimeout);
|
|
282
|
+
}
|
|
283
|
+
const { html, pendingQueries } = await twoPassRender(options);
|
|
284
|
+
if (pendingQueries.length === 0) {
|
|
285
|
+
clearGlobalSSRTimeout();
|
|
286
|
+
removeSSR();
|
|
287
|
+
return new Response(html, {
|
|
288
|
+
status: 200,
|
|
289
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
clearGlobalSSRTimeout();
|
|
293
|
+
removeSSR();
|
|
294
|
+
const TIMEOUT_SENTINEL = Symbol("stream-timeout");
|
|
295
|
+
let closed = false;
|
|
296
|
+
let hardTimeoutId;
|
|
297
|
+
const stream = new ReadableStream({
|
|
298
|
+
async start(controller) {
|
|
299
|
+
controller.enqueue(encodeChunk(html));
|
|
300
|
+
const hardTimeout = new Promise((r) => {
|
|
301
|
+
hardTimeoutId = setTimeout(() => r(TIMEOUT_SENTINEL), streamTimeout);
|
|
302
|
+
});
|
|
303
|
+
const streamPromises = pendingQueries.map(async (entry) => {
|
|
304
|
+
try {
|
|
305
|
+
const result = await Promise.race([entry.promise, hardTimeout]);
|
|
306
|
+
if (result === TIMEOUT_SENTINEL || closed)
|
|
307
|
+
return;
|
|
308
|
+
const chunk = createSSRDataChunk(entry.key, result, options.nonce);
|
|
309
|
+
controller.enqueue(encodeChunk(chunk));
|
|
310
|
+
} catch {}
|
|
311
|
+
});
|
|
312
|
+
await Promise.race([Promise.allSettled(streamPromises), hardTimeout]);
|
|
313
|
+
if (hardTimeoutId !== undefined)
|
|
314
|
+
clearTimeout(hardTimeoutId);
|
|
315
|
+
closed = true;
|
|
316
|
+
controller.close();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
return new Response(stream, {
|
|
320
|
+
status: 200,
|
|
321
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
322
|
+
});
|
|
323
|
+
} catch (err) {
|
|
324
|
+
clearGlobalSSRTimeout();
|
|
325
|
+
removeSSR();
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async function renderToHTML(appOrOptions, maybeOptions) {
|
|
331
|
+
const options = typeof appOrOptions === "function" ? { ...maybeOptions, app: appOrOptions } : appOrOptions;
|
|
332
|
+
installSSR();
|
|
333
|
+
return ssrStorage.run({ url: options.url, errors: [], queries: [] }, async () => {
|
|
334
|
+
try {
|
|
335
|
+
const { html } = await twoPassRender(options);
|
|
336
|
+
return html;
|
|
337
|
+
} finally {
|
|
338
|
+
clearGlobalSSRTimeout();
|
|
339
|
+
removeSSR();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
382
342
|
}
|
|
383
|
-
// src/
|
|
384
|
-
function
|
|
385
|
-
|
|
343
|
+
// src/ssr-html.ts
|
|
344
|
+
function generateSSRHtml(options) {
|
|
345
|
+
const { appHtml, css, ssrData, clientEntry, title = "Vertz App" } = options;
|
|
346
|
+
const ssrDataScript = ssrData.length > 0 ? `<script>window.__VERTZ_SSR_DATA__ = ${JSON.stringify(ssrData)};</script>` : "";
|
|
347
|
+
return `<!doctype html>
|
|
348
|
+
<html lang="en">
|
|
349
|
+
<head>
|
|
350
|
+
<meta charset="UTF-8" />
|
|
351
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
352
|
+
<title>${escapeHtml(title)}</title>
|
|
353
|
+
${css}
|
|
354
|
+
</head>
|
|
355
|
+
<body>
|
|
356
|
+
<div id="app">${appHtml}</div>
|
|
357
|
+
${ssrDataScript}
|
|
358
|
+
<script type="module" src="${escapeAttr(clientEntry)}"></script>
|
|
359
|
+
</body>
|
|
360
|
+
</html>`;
|
|
386
361
|
}
|
|
387
362
|
export {
|
|
388
363
|
wrapWithHydrationMarkers,
|
|
389
364
|
streamToString,
|
|
365
|
+
ssrStorage,
|
|
366
|
+
ssrRenderToString,
|
|
367
|
+
ssrDiscoverQueries,
|
|
368
|
+
setGlobalSSRTimeout,
|
|
390
369
|
serializeToHtml,
|
|
370
|
+
safeSerialize,
|
|
391
371
|
resetSlotCounter,
|
|
392
372
|
renderToStream,
|
|
373
|
+
renderToHTMLStream,
|
|
374
|
+
renderToHTML,
|
|
375
|
+
renderPage,
|
|
393
376
|
renderHeadToHtml,
|
|
394
377
|
renderAssetTags,
|
|
378
|
+
registerSSRQuery,
|
|
395
379
|
rawHtml,
|
|
380
|
+
isInSSR,
|
|
396
381
|
inlineCriticalCss,
|
|
382
|
+
getStreamingRuntimeScript,
|
|
383
|
+
getSSRUrl,
|
|
384
|
+
getSSRQueries,
|
|
385
|
+
getGlobalSSRTimeout,
|
|
386
|
+
generateSSRHtml,
|
|
397
387
|
encodeChunk,
|
|
398
388
|
createTemplateChunk,
|
|
399
389
|
createSlotPlaceholder,
|
|
400
|
-
|
|
390
|
+
createSSRHandler,
|
|
391
|
+
createSSRDataChunk,
|
|
392
|
+
createSSRAdapter,
|
|
401
393
|
collectStreamChunks,
|
|
394
|
+
clearGlobalSSRTimeout,
|
|
402
395
|
HeadCollector
|
|
403
396
|
};
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// src/jsx-runtime/index.ts
|
|
2
|
+
function unwrapSignal(value) {
|
|
3
|
+
if (value != null && typeof value === "object" && "peek" in value && typeof value.peek === "function") {
|
|
4
|
+
return value.peek();
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
2
8
|
function normalizeChildren(children) {
|
|
3
9
|
if (children == null || children === false || children === true) {
|
|
4
10
|
return [];
|
|
@@ -9,6 +15,12 @@ function normalizeChildren(children) {
|
|
|
9
15
|
if (typeof children === "object" && (("tag" in children) || ("__raw" in children))) {
|
|
10
16
|
return [children];
|
|
11
17
|
}
|
|
18
|
+
if (typeof children === "object") {
|
|
19
|
+
const unwrapped = unwrapSignal(children);
|
|
20
|
+
if (unwrapped !== children) {
|
|
21
|
+
return normalizeChildren(unwrapped);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
return [String(children)];
|
|
13
25
|
}
|
|
14
26
|
function jsx(tag, props) {
|
|
@@ -17,10 +29,11 @@ function jsx(tag, props) {
|
|
|
17
29
|
}
|
|
18
30
|
const { children, ...attrs } = props || {};
|
|
19
31
|
const serializableAttrs = {};
|
|
20
|
-
for (const [key,
|
|
21
|
-
if (key.startsWith("on") && typeof
|
|
32
|
+
for (const [key, rawValue] of Object.entries(attrs)) {
|
|
33
|
+
if (key.startsWith("on") && typeof rawValue === "function") {
|
|
22
34
|
continue;
|
|
23
35
|
}
|
|
36
|
+
const value = unwrapSignal(rawValue);
|
|
24
37
|
if (key === "class" && value != null) {
|
|
25
38
|
serializableAttrs.class = String(value);
|
|
26
39
|
continue;
|