@vertz/ui-server 0.2.47 → 0.2.52
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/CHANGELOG.md +715 -0
- package/dist/bun-dev-server.d.ts +35 -0
- package/dist/bun-dev-server.js +104 -2
- package/dist/bun-plugin/state-inspector.d.ts +59 -0
- package/dist/bun-plugin/state-inspector.js +270 -0
- package/dist/dom-shim/index.d.ts +7 -0
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -8
- package/dist/node-handler.d.ts +22 -0
- package/dist/node-handler.js +3 -3
- package/dist/shared/{chunk-szvdd1qq.js → chunk-bp1ez7v4.js} +46 -2
- package/dist/shared/{chunk-tm5aeq94.js → chunk-c22cgv96.js} +39 -5
- package/dist/shared/{chunk-ck3n699k.js → chunk-nstzxw4e.js} +34 -7
- package/dist/shared/{chunk-kzycr5v0.js → chunk-x8r5mh0q.js} +31 -10
- package/dist/ssr/index.d.ts +22 -0
- package/dist/ssr/index.js +3 -3
- package/package.json +9 -5
package/dist/node-handler.d.ts
CHANGED
|
@@ -261,6 +261,28 @@ interface SSRHandlerOptions {
|
|
|
261
261
|
* (JSON files, third-party APIs, custom DB clients).
|
|
262
262
|
*/
|
|
263
263
|
aotDataResolver?: AotDataResolver;
|
|
264
|
+
/**
|
|
265
|
+
* Derive attributes to set on the `<html>` tag from the incoming request.
|
|
266
|
+
*
|
|
267
|
+
* Useful for setting `data-theme`, `dir`, `lang`, or other attributes that
|
|
268
|
+
* must be on `<html>` to avoid FOUC. The callback runs before SSR rendering
|
|
269
|
+
* so the attributes are available in the first byte of the response.
|
|
270
|
+
*
|
|
271
|
+
* If the template already has an attribute with the same name, the callback's
|
|
272
|
+
* value overrides it. Values are HTML-escaped automatically. Keys must be
|
|
273
|
+
* valid HTML attribute names (`/^[a-zA-Z][a-zA-Z0-9-]*$/`).
|
|
274
|
+
*
|
|
275
|
+
* Return `undefined`, `null`, or `{}` to skip injection.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* htmlAttributes: (request) => ({
|
|
280
|
+
* 'data-theme': getThemeCookie(request) ?? 'dark',
|
|
281
|
+
* dir: getDirection(request),
|
|
282
|
+
* })
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
htmlAttributes?: (request: Request) => Record<string, string> | null | undefined;
|
|
264
286
|
}
|
|
265
287
|
type NodeHandlerOptions = SSRHandlerOptions;
|
|
266
288
|
declare function createNodeHandler(options: NodeHandlerOptions): (req: IncomingMessage, res: ServerResponse) => void;
|
package/dist/node-handler.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createNodeHandler
|
|
3
|
-
} from "./shared/chunk-
|
|
4
|
-
import"./shared/chunk-
|
|
5
|
-
import"./shared/chunk-
|
|
3
|
+
} from "./shared/chunk-x8r5mh0q.js";
|
|
4
|
+
import"./shared/chunk-c22cgv96.js";
|
|
5
|
+
import"./shared/chunk-bp1ez7v4.js";
|
|
6
6
|
import"./shared/chunk-bt1px3c4.js";
|
|
7
7
|
export {
|
|
8
8
|
createNodeHandler
|
|
@@ -424,11 +424,50 @@ class SSRElement extends SSRNode {
|
|
|
424
424
|
set name(value) {
|
|
425
425
|
this.attrs.name = value;
|
|
426
426
|
}
|
|
427
|
+
_selectValue;
|
|
427
428
|
get value() {
|
|
429
|
+
if (this.tag === "select") {
|
|
430
|
+
return this._selectValue ?? "";
|
|
431
|
+
}
|
|
428
432
|
return this.attrs.value ?? "";
|
|
429
433
|
}
|
|
430
434
|
set value(value) {
|
|
431
|
-
this.
|
|
435
|
+
if (this.tag === "select") {
|
|
436
|
+
this._selectValue = value;
|
|
437
|
+
this._applySelectValue();
|
|
438
|
+
} else {
|
|
439
|
+
this.attrs.value = value;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
_applySelectValue() {
|
|
443
|
+
if (this._selectValue === undefined)
|
|
444
|
+
return;
|
|
445
|
+
this._applySelectValueToChildren(this.children);
|
|
446
|
+
}
|
|
447
|
+
_applySelectValueToChildren(children) {
|
|
448
|
+
for (const child of children) {
|
|
449
|
+
if (child instanceof SSRElement) {
|
|
450
|
+
if (child.tag === "option") {
|
|
451
|
+
const optValue = child.attrs.value ?? this._getChildTextContent(child);
|
|
452
|
+
if (optValue === this._selectValue) {
|
|
453
|
+
child.attrs.selected = "";
|
|
454
|
+
} else {
|
|
455
|
+
delete child.attrs.selected;
|
|
456
|
+
}
|
|
457
|
+
} else if (child.tag === "optgroup") {
|
|
458
|
+
this._applySelectValueToChildren(child.children);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
_getChildTextContent(el) {
|
|
464
|
+
return el.children.map((c) => {
|
|
465
|
+
if (typeof c === "string")
|
|
466
|
+
return c;
|
|
467
|
+
if (c instanceof SSRElement)
|
|
468
|
+
return this._getChildTextContent(c);
|
|
469
|
+
return "";
|
|
470
|
+
}).join("");
|
|
432
471
|
}
|
|
433
472
|
get src() {
|
|
434
473
|
return this.attrs.src ?? "";
|
|
@@ -499,9 +538,14 @@ class SSRElement extends SSRNode {
|
|
|
499
538
|
addEventListener(_event, _handler) {}
|
|
500
539
|
removeEventListener(_event, _handler) {}
|
|
501
540
|
toVNode() {
|
|
541
|
+
let attrs = { ...this.attrs };
|
|
542
|
+
if (this.tag === "select") {
|
|
543
|
+
this._applySelectValue();
|
|
544
|
+
delete attrs.value;
|
|
545
|
+
}
|
|
502
546
|
return {
|
|
503
547
|
tag: this.tag,
|
|
504
|
-
attrs
|
|
548
|
+
attrs,
|
|
505
549
|
children: this.children.map((child) => {
|
|
506
550
|
if (typeof child === "string") {
|
|
507
551
|
return this._innerHTML != null ? rawHtml(child) : child;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
setGlobalSSRTimeout,
|
|
7
7
|
ssrStorage,
|
|
8
8
|
toVNode
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-bp1ez7v4.js";
|
|
10
10
|
|
|
11
11
|
// src/html-serializer.ts
|
|
12
12
|
var VOID_ELEMENTS = new Set([
|
|
@@ -1050,9 +1050,10 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1050
1050
|
try {
|
|
1051
1051
|
setGlobalSSRTimeout(ssrTimeout);
|
|
1052
1052
|
const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
|
|
1053
|
+
const resolveKey = (key) => key.includes("${") ? resolveParamQueryKeys([key], match.params, searchParams)[0] : key;
|
|
1053
1054
|
const ctx = {
|
|
1054
1055
|
holes,
|
|
1055
|
-
getData: (key) => queryCache.get(key),
|
|
1056
|
+
getData: (key) => queryCache.get(resolveKey(key)),
|
|
1056
1057
|
session: options.prefetchSession,
|
|
1057
1058
|
params: match.params,
|
|
1058
1059
|
searchParams
|
|
@@ -1075,7 +1076,7 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1075
1076
|
appHoles.RouterView = () => html;
|
|
1076
1077
|
const appCtx = {
|
|
1077
1078
|
holes: appHoles,
|
|
1078
|
-
getData: (key) => queryCache.get(key),
|
|
1079
|
+
getData: (key) => queryCache.get(resolveKey(key)),
|
|
1079
1080
|
session: options.prefetchSession,
|
|
1080
1081
|
params: match.params,
|
|
1081
1082
|
searchParams
|
|
@@ -1158,7 +1159,7 @@ function resolveParamQueryKeys(queryKeys, params, searchParams) {
|
|
|
1158
1159
|
return value || defaultVal;
|
|
1159
1160
|
}
|
|
1160
1161
|
return value ?? "";
|
|
1161
|
-
}).replace(/\$\{(\w+)\}/g, (_, paramName) => params[paramName] ?? ""));
|
|
1162
|
+
}).replace(/\$\{(\w+)\}/g, (_, paramName) => params[paramName] ?? searchParams?.get(paramName) ?? ""));
|
|
1162
1163
|
}
|
|
1163
1164
|
function extractSearchParams(url) {
|
|
1164
1165
|
const qIdx = url.indexOf("?");
|
|
@@ -1461,6 +1462,39 @@ async function resolveSession(request, sessionResolver, nonce) {
|
|
|
1461
1462
|
}
|
|
1462
1463
|
|
|
1463
1464
|
// src/template-inject.ts
|
|
1465
|
+
var VALID_ATTR_KEY = /^[a-zA-Z][a-zA-Z0-9-]*$/;
|
|
1466
|
+
function injectHtmlAttributes(template, attrs) {
|
|
1467
|
+
const entries = Object.entries(attrs);
|
|
1468
|
+
if (entries.length === 0)
|
|
1469
|
+
return template;
|
|
1470
|
+
for (const [key] of entries) {
|
|
1471
|
+
if (!VALID_ATTR_KEY.test(key)) {
|
|
1472
|
+
throw new Error(`Invalid HTML attribute key: "${key}"`);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
const htmlTagMatch = template.match(/<html(\s[^>]*)?>|<html>/i);
|
|
1476
|
+
if (!htmlTagMatch || htmlTagMatch.index == null)
|
|
1477
|
+
return template;
|
|
1478
|
+
const existingAttrsStr = htmlTagMatch[1] ?? "";
|
|
1479
|
+
const existingAttrs = parseHtmlTagAttrs(existingAttrsStr);
|
|
1480
|
+
const merged = { ...existingAttrs };
|
|
1481
|
+
for (const [key, value] of entries) {
|
|
1482
|
+
merged[key] = escapeAttr(value);
|
|
1483
|
+
}
|
|
1484
|
+
const attrStr = Object.entries(merged).map(([k, v]) => v === "" && !(k in attrs) ? ` ${k}` : ` ${k}="${v}"`).join("");
|
|
1485
|
+
const originalTag = htmlTagMatch[0].match(/^<([a-zA-Z]+)/i)[1];
|
|
1486
|
+
const tagEnd = htmlTagMatch.index + htmlTagMatch[0].length;
|
|
1487
|
+
return template.slice(0, htmlTagMatch.index) + `<${originalTag}${attrStr}>` + template.slice(tagEnd);
|
|
1488
|
+
}
|
|
1489
|
+
function parseHtmlTagAttrs(attrStr) {
|
|
1490
|
+
const attrs = {};
|
|
1491
|
+
const re = /([a-zA-Z][a-zA-Z0-9-]*)(?:\s*=\s*"([^"]*)"|(?:\s*=\s*'([^']*)'))?/g;
|
|
1492
|
+
let m;
|
|
1493
|
+
while ((m = re.exec(attrStr)) !== null) {
|
|
1494
|
+
attrs[m[1]] = m[2] ?? m[3] ?? "";
|
|
1495
|
+
}
|
|
1496
|
+
return attrs;
|
|
1497
|
+
}
|
|
1464
1498
|
function injectIntoTemplate(options) {
|
|
1465
1499
|
const { template, appHtml, appCss, ssrData, nonce, headTags, sessionScript } = options;
|
|
1466
1500
|
let html;
|
|
@@ -1523,4 +1557,4 @@ function replaceAppDivContent(template, appHtml) {
|
|
|
1523
1557
|
return template.slice(0, contentStart) + appHtml + template.slice(i);
|
|
1524
1558
|
}
|
|
1525
1559
|
|
|
1526
|
-
export { escapeHtml, escapeAttr, serializeToHtml, toPrefetchSession, evaluateAccessRule, reconstructDescriptors, compileThemeCached, createRequestContext, matchUrlToPatterns, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, ssrRenderSinglePass, ssrRenderProgressive, ssrStreamNavQueries, createHoles, ssrRenderAot, isAotDebugEnabled, clearRouteCssCache, getAccessSetForSSR, createAccessSetScript, createSessionScript, resolveRouteModulepreload, precomputeHandlerState, resolveSession, injectIntoTemplate };
|
|
1560
|
+
export { escapeHtml, escapeAttr, serializeToHtml, toPrefetchSession, evaluateAccessRule, reconstructDescriptors, compileThemeCached, createRequestContext, matchUrlToPatterns, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, ssrRenderSinglePass, ssrRenderProgressive, ssrStreamNavQueries, createHoles, ssrRenderAot, isAotDebugEnabled, clearRouteCssCache, getAccessSetForSSR, createAccessSetScript, createSessionScript, resolveRouteModulepreload, precomputeHandlerState, resolveSession, injectHtmlAttributes, injectIntoTemplate };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
encodeChunk,
|
|
3
3
|
escapeAttr,
|
|
4
|
+
injectHtmlAttributes,
|
|
4
5
|
injectIntoTemplate,
|
|
5
6
|
precomputeHandlerState,
|
|
6
7
|
resolveRouteModulepreload,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
ssrRenderSinglePass,
|
|
12
13
|
ssrStreamNavQueries,
|
|
13
14
|
toPrefetchSession
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-c22cgv96.js";
|
|
15
16
|
|
|
16
17
|
// src/aot-manifest-loader.ts
|
|
17
18
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -108,7 +109,7 @@ function buildProgressiveResponse(options) {
|
|
|
108
109
|
"Content-Type": "text/html; charset=utf-8",
|
|
109
110
|
...headers
|
|
110
111
|
};
|
|
111
|
-
return new Response(stream, { status: 200, headers: responseHeaders });
|
|
112
|
+
return new Response(stream, { status: options.status ?? 200, headers: responseHeaders });
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
// src/ssr-handler.ts
|
|
@@ -124,7 +125,8 @@ function createSSRHandler(options) {
|
|
|
124
125
|
manifest,
|
|
125
126
|
progressiveHTML,
|
|
126
127
|
aotManifest,
|
|
127
|
-
aotDataResolver
|
|
128
|
+
aotDataResolver,
|
|
129
|
+
htmlAttributes
|
|
128
130
|
} = options;
|
|
129
131
|
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
130
132
|
return async (request) => {
|
|
@@ -141,11 +143,33 @@ function createSSRHandler(options) {
|
|
|
141
143
|
ssrAuth = result.ssrAuth;
|
|
142
144
|
}
|
|
143
145
|
const cookies = request.headers.get("Cookie") ?? undefined;
|
|
146
|
+
let requestTemplate = template;
|
|
147
|
+
let requestHeadTemplate = splitResult?.headTemplate;
|
|
148
|
+
if (htmlAttributes) {
|
|
149
|
+
try {
|
|
150
|
+
const attrs = htmlAttributes(request);
|
|
151
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
152
|
+
requestTemplate = injectHtmlAttributes(template, attrs);
|
|
153
|
+
if (requestHeadTemplate) {
|
|
154
|
+
requestHeadTemplate = injectHtmlAttributes(requestHeadTemplate, attrs);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.error("[SSR] htmlAttributes failed:", err instanceof Error ? err.message : err);
|
|
159
|
+
return new Response("Internal Server Error", {
|
|
160
|
+
status: 500,
|
|
161
|
+
headers: { "Content-Type": "text/plain" }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
144
165
|
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
145
166
|
if (useProgressive) {
|
|
146
|
-
return handleProgressiveHTMLRequest(module,
|
|
167
|
+
return handleProgressiveHTMLRequest(module, {
|
|
168
|
+
headTemplate: requestHeadTemplate ?? splitResult.headTemplate,
|
|
169
|
+
tailTemplate: splitResult.tailTemplate
|
|
170
|
+
}, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, cookies);
|
|
147
171
|
}
|
|
148
|
-
return handleHTMLRequest(module,
|
|
172
|
+
return handleHTMLRequest(module, requestTemplate, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest, aotDataResolver, cookies);
|
|
149
173
|
};
|
|
150
174
|
}
|
|
151
175
|
async function handleNavRequest(module, url, ssrTimeout) {
|
|
@@ -219,13 +243,15 @@ async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonc
|
|
|
219
243
|
headers.Link = linkHeader;
|
|
220
244
|
if (cacheControl)
|
|
221
245
|
headers["Cache-Control"] = cacheControl;
|
|
246
|
+
const status = result.matchedRoutePatterns?.length ? 200 : 404;
|
|
222
247
|
return buildProgressiveResponse({
|
|
223
248
|
headChunk,
|
|
224
249
|
renderStream: result.renderStream,
|
|
225
250
|
tailChunk: split.tailTemplate,
|
|
226
251
|
ssrData: result.ssrData,
|
|
227
252
|
nonce,
|
|
228
|
-
headers
|
|
253
|
+
headers,
|
|
254
|
+
status
|
|
229
255
|
});
|
|
230
256
|
} catch (err) {
|
|
231
257
|
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
@@ -278,7 +304,8 @@ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallb
|
|
|
278
304
|
headers.Link = linkHeader;
|
|
279
305
|
if (cacheControl)
|
|
280
306
|
headers["Cache-Control"] = cacheControl;
|
|
281
|
-
|
|
307
|
+
const status = result.matchedRoutePatterns?.length ? 200 : 404;
|
|
308
|
+
return new Response(html, { status, headers });
|
|
282
309
|
} catch (err) {
|
|
283
310
|
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
284
311
|
return new Response("Internal Server Error", {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
escapeAttr,
|
|
3
|
+
injectHtmlAttributes,
|
|
3
4
|
injectIntoTemplate,
|
|
4
5
|
precomputeHandlerState,
|
|
5
6
|
resolveRouteModulepreload,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
ssrRenderSinglePass,
|
|
11
12
|
ssrStreamNavQueries,
|
|
12
13
|
toPrefetchSession
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-c22cgv96.js";
|
|
14
15
|
|
|
15
16
|
// src/node-handler.ts
|
|
16
17
|
function createNodeHandler(options) {
|
|
@@ -25,7 +26,8 @@ function createNodeHandler(options) {
|
|
|
25
26
|
manifest,
|
|
26
27
|
progressiveHTML,
|
|
27
28
|
aotManifest,
|
|
28
|
-
aotDataResolver
|
|
29
|
+
aotDataResolver,
|
|
30
|
+
htmlAttributes
|
|
29
31
|
} = options;
|
|
30
32
|
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
31
33
|
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
@@ -38,20 +40,37 @@ function createNodeHandler(options) {
|
|
|
38
40
|
await handleNavRequest(req, res, module, pathname, ssrTimeout);
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
if (sessionResolver) {
|
|
43
|
+
let webRequest;
|
|
44
|
+
if (sessionResolver || htmlAttributes) {
|
|
44
45
|
const fullUrl = `http://${req.headers.host ?? "localhost"}${url}`;
|
|
45
|
-
|
|
46
|
+
webRequest = new Request(fullUrl, {
|
|
46
47
|
method: req.method ?? "GET",
|
|
47
48
|
headers: req.headers
|
|
48
49
|
});
|
|
50
|
+
}
|
|
51
|
+
let sessionScript = "";
|
|
52
|
+
let ssrAuth;
|
|
53
|
+
if (sessionResolver && webRequest) {
|
|
49
54
|
const result2 = await resolveSession(webRequest, sessionResolver, nonce);
|
|
50
55
|
sessionScript = result2.sessionScript;
|
|
51
56
|
ssrAuth = result2.ssrAuth;
|
|
52
57
|
}
|
|
58
|
+
let requestTemplate = template;
|
|
59
|
+
let requestHeadTemplate = splitResult?.headTemplate;
|
|
60
|
+
if (htmlAttributes && webRequest) {
|
|
61
|
+
const attrs = htmlAttributes(webRequest);
|
|
62
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
63
|
+
requestTemplate = injectHtmlAttributes(template, attrs);
|
|
64
|
+
if (requestHeadTemplate) {
|
|
65
|
+
requestHeadTemplate = injectHtmlAttributes(requestHeadTemplate, attrs);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
53
69
|
if (useProgressive) {
|
|
54
|
-
await handleProgressiveRequest(req, res, module,
|
|
70
|
+
await handleProgressiveRequest(req, res, module, {
|
|
71
|
+
headTemplate: requestHeadTemplate ?? splitResult.headTemplate,
|
|
72
|
+
tailTemplate: splitResult.tailTemplate
|
|
73
|
+
}, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
|
|
55
74
|
return;
|
|
56
75
|
}
|
|
57
76
|
const prefetchSession = ssrAuth ? toPrefetchSession(ssrAuth) : undefined;
|
|
@@ -79,7 +98,7 @@ function createNodeHandler(options) {
|
|
|
79
98
|
const allHeadTags = [result.headTags, resolvedModulepreloadTags].filter(Boolean).join(`
|
|
80
99
|
`);
|
|
81
100
|
const html = injectIntoTemplate({
|
|
82
|
-
template,
|
|
101
|
+
template: requestTemplate,
|
|
83
102
|
appHtml: result.html,
|
|
84
103
|
appCss: result.css,
|
|
85
104
|
ssrData: result.ssrData,
|
|
@@ -94,7 +113,8 @@ function createNodeHandler(options) {
|
|
|
94
113
|
headers.Link = linkHeader;
|
|
95
114
|
if (cacheControl)
|
|
96
115
|
headers["Cache-Control"] = cacheControl;
|
|
97
|
-
|
|
116
|
+
const status = result.matchedRoutePatterns?.length ? 200 : 404;
|
|
117
|
+
res.writeHead(status, headers);
|
|
98
118
|
res.end(html);
|
|
99
119
|
} catch (err) {
|
|
100
120
|
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
@@ -153,7 +173,8 @@ async function handleProgressiveRequest(req, res, module, split, url, ssrTimeout
|
|
|
153
173
|
headers.Link = linkHeader;
|
|
154
174
|
if (cacheControl)
|
|
155
175
|
headers["Cache-Control"] = cacheControl;
|
|
156
|
-
|
|
176
|
+
const status = result.matchedRoutePatterns?.length ? 200 : 404;
|
|
177
|
+
res.writeHead(status, headers);
|
|
157
178
|
res.write(headChunk);
|
|
158
179
|
let clientDisconnected = false;
|
|
159
180
|
req.on("close", () => {
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -395,6 +395,28 @@ interface SSRHandlerOptions {
|
|
|
395
395
|
* (JSON files, third-party APIs, custom DB clients).
|
|
396
396
|
*/
|
|
397
397
|
aotDataResolver?: AotDataResolver;
|
|
398
|
+
/**
|
|
399
|
+
* Derive attributes to set on the `<html>` tag from the incoming request.
|
|
400
|
+
*
|
|
401
|
+
* Useful for setting `data-theme`, `dir`, `lang`, or other attributes that
|
|
402
|
+
* must be on `<html>` to avoid FOUC. The callback runs before SSR rendering
|
|
403
|
+
* so the attributes are available in the first byte of the response.
|
|
404
|
+
*
|
|
405
|
+
* If the template already has an attribute with the same name, the callback's
|
|
406
|
+
* value overrides it. Values are HTML-escaped automatically. Keys must be
|
|
407
|
+
* valid HTML attribute names (`/^[a-zA-Z][a-zA-Z0-9-]*$/`).
|
|
408
|
+
*
|
|
409
|
+
* Return `undefined`, `null`, or `{}` to skip injection.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```ts
|
|
413
|
+
* htmlAttributes: (request) => ({
|
|
414
|
+
* 'data-theme': getThemeCookie(request) ?? 'dark',
|
|
415
|
+
* dir: getDirection(request),
|
|
416
|
+
* })
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
htmlAttributes?: (request: Request) => Record<string, string> | null | undefined;
|
|
398
420
|
}
|
|
399
421
|
declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
|
|
400
422
|
interface InjectIntoTemplateOptions {
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSSRHandler,
|
|
3
3
|
loadAotManifest
|
|
4
|
-
} from "../shared/chunk-
|
|
4
|
+
} from "../shared/chunk-nstzxw4e.js";
|
|
5
5
|
import {
|
|
6
6
|
injectIntoTemplate,
|
|
7
7
|
ssrRenderSinglePass,
|
|
8
8
|
ssrStreamNavQueries
|
|
9
|
-
} from "../shared/chunk-
|
|
10
|
-
import"../shared/chunk-
|
|
9
|
+
} from "../shared/chunk-c22cgv96.js";
|
|
10
|
+
import"../shared/chunk-bp1ez7v4.js";
|
|
11
11
|
import"../shared/chunk-bt1px3c4.js";
|
|
12
12
|
// src/prerender.ts
|
|
13
13
|
async function discoverRoutes(module) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/ui-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.52",
|
|
4
4
|
"description": "Vertz UI server-side rendering runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"import": "./dist/bun-plugin/fast-refresh-runtime.js",
|
|
40
40
|
"types": "./dist/bun-plugin/fast-refresh-runtime.d.ts"
|
|
41
41
|
},
|
|
42
|
+
"./state-inspector": {
|
|
43
|
+
"import": "./dist/bun-plugin/state-inspector.js",
|
|
44
|
+
"types": "./dist/bun-plugin/state-inspector.d.ts"
|
|
45
|
+
},
|
|
42
46
|
"./bun-dev-server": {
|
|
43
47
|
"import": "./dist/bun-dev-server.js",
|
|
44
48
|
"types": "./dist/bun-dev-server.d.ts"
|
|
@@ -57,7 +61,7 @@
|
|
|
57
61
|
"provenance": true
|
|
58
62
|
},
|
|
59
63
|
"scripts": {
|
|
60
|
-
"build": "bunup",
|
|
64
|
+
"build": "vtzx bunup",
|
|
61
65
|
"test": "bun test --timeout 60000 src/",
|
|
62
66
|
"test:integration": "bun test src/__tests__/bun-dev-server.integration.local.ts",
|
|
63
67
|
"test:e2e": "bunx playwright test",
|
|
@@ -67,15 +71,15 @@
|
|
|
67
71
|
"@ampproject/remapping": "^2.3.0",
|
|
68
72
|
"@capsizecss/unpack": "^4.0.0",
|
|
69
73
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
70
|
-
"@vertz/core": "
|
|
71
|
-
"@vertz/ui": "
|
|
74
|
+
"@vertz/core": "workspace:^",
|
|
75
|
+
"@vertz/ui": "workspace:^",
|
|
72
76
|
"magic-string": "^0.30.0",
|
|
73
77
|
"sharp": "^0.34.5"
|
|
74
78
|
},
|
|
75
79
|
"devDependencies": {
|
|
76
80
|
"@happy-dom/global-registrator": "^20.8.3",
|
|
77
81
|
"@playwright/test": "^1.58.2",
|
|
78
|
-
"@vertz/codegen": "
|
|
82
|
+
"@vertz/codegen": "workspace:^",
|
|
79
83
|
"bun-types": "^1.3.10",
|
|
80
84
|
"bunup": "^0.16.31",
|
|
81
85
|
"happy-dom": "^20.8.3",
|