@vertz/ui-server 0.2.37 → 0.2.41
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/dist/bun-dev-server.js +356 -45
- package/dist/bun-plugin/index.js +15 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +153 -9
- package/dist/node-handler.d.ts +7 -1
- package/dist/node-handler.js +2 -2
- package/dist/shared/{chunk-vcpk8fs6.js → chunk-15kac8x8.js} +201 -25
- package/dist/shared/{chunk-4qb2xcyb.js → chunk-gqvkycnj.js} +5 -3
- package/dist/shared/{chunk-rwa95knv.js → chunk-j18fewkf.js} +19 -4
- package/dist/ssr/index.d.ts +7 -1
- package/dist/ssr/index.js +2 -2
- package/package.json +6 -6
|
@@ -335,13 +335,102 @@ function createSSRDataChunk(key, data, nonce) {
|
|
|
335
335
|
// src/ssr-render.ts
|
|
336
336
|
import { compileTheme } from "@vertz/ui";
|
|
337
337
|
import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
|
|
338
|
+
|
|
339
|
+
// src/css-filter.ts
|
|
340
|
+
function extractClassNamesFromHTML(html) {
|
|
341
|
+
const classes = new Set;
|
|
342
|
+
const attrRegex = /\bclass(?:Name)?="([^"]*)"/g;
|
|
343
|
+
let match;
|
|
344
|
+
while ((match = attrRegex.exec(html)) !== null) {
|
|
345
|
+
const value = match[1];
|
|
346
|
+
for (const cls of value.split(/\s+/)) {
|
|
347
|
+
if (cls)
|
|
348
|
+
classes.add(cls);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return classes;
|
|
352
|
+
}
|
|
353
|
+
function extractClassSelectorsFromCSS(css) {
|
|
354
|
+
const selectors = new Set;
|
|
355
|
+
const selectorRegex = /\.([\w-]+)/g;
|
|
356
|
+
let match;
|
|
357
|
+
while ((match = selectorRegex.exec(css)) !== null) {
|
|
358
|
+
selectors.add(match[1]);
|
|
359
|
+
}
|
|
360
|
+
return selectors;
|
|
361
|
+
}
|
|
362
|
+
function isKeyframesBlock(css) {
|
|
363
|
+
return /^\s*@keyframes\s/.test(css);
|
|
364
|
+
}
|
|
365
|
+
function hasOnlyClassSelectors(css) {
|
|
366
|
+
let stripped = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
367
|
+
stripped = stripped.replace(/@keyframes\s+[\w-]+\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g, "");
|
|
368
|
+
stripped = stripped.replace(/@[\w-]+\s*\([^)]*\)\s*\{/g, "").replace(/^\s*\}/gm, "");
|
|
369
|
+
const ruleRegex = /([^{}]+)\{/g;
|
|
370
|
+
let match;
|
|
371
|
+
let foundAnySelector = false;
|
|
372
|
+
while ((match = ruleRegex.exec(stripped)) !== null) {
|
|
373
|
+
const selector = match[1].trim();
|
|
374
|
+
if (!selector)
|
|
375
|
+
continue;
|
|
376
|
+
foundAnySelector = true;
|
|
377
|
+
if (!selector.startsWith(".")) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return foundAnySelector;
|
|
382
|
+
}
|
|
383
|
+
function filterCSSByHTML(html, cssStrings) {
|
|
384
|
+
if (cssStrings.length === 0 || !html)
|
|
385
|
+
return [];
|
|
386
|
+
const htmlClasses = extractClassNamesFromHTML(html);
|
|
387
|
+
const kept = [];
|
|
388
|
+
const pendingKeyframes = [];
|
|
389
|
+
for (const css of cssStrings) {
|
|
390
|
+
if (isKeyframesBlock(css)) {
|
|
391
|
+
const nameMatch = /@keyframes\s+([\w-]+)/.exec(css);
|
|
392
|
+
if (nameMatch) {
|
|
393
|
+
pendingKeyframes.push({ css, name: nameMatch[1] });
|
|
394
|
+
}
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (!hasOnlyClassSelectors(css)) {
|
|
398
|
+
kept.push(css);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const cssClasses = extractClassSelectorsFromCSS(css);
|
|
402
|
+
let used = false;
|
|
403
|
+
for (const cls of cssClasses) {
|
|
404
|
+
if (htmlClasses.has(cls)) {
|
|
405
|
+
used = true;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (used)
|
|
410
|
+
kept.push(css);
|
|
411
|
+
}
|
|
412
|
+
if (pendingKeyframes.length > 0) {
|
|
413
|
+
const survivingCss = kept.join(`
|
|
414
|
+
`);
|
|
415
|
+
for (const kf of pendingKeyframes) {
|
|
416
|
+
if (survivingCss.includes(kf.name)) {
|
|
417
|
+
kept.push(kf.css);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return kept;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/ssr-render.ts
|
|
338
425
|
var compiledThemeCache = new WeakMap;
|
|
426
|
+
var compiledThemeWithMetricsCache = new WeakMap;
|
|
339
427
|
function compileThemeCached(theme, fallbackMetrics) {
|
|
340
|
-
const
|
|
428
|
+
const cache = fallbackMetrics ? compiledThemeWithMetricsCache : compiledThemeCache;
|
|
429
|
+
const cached = cache.get(theme);
|
|
341
430
|
if (cached)
|
|
342
431
|
return cached;
|
|
343
432
|
const compiled = compileTheme(theme, { fallbackMetrics });
|
|
344
|
-
|
|
433
|
+
cache.set(theme, compiled);
|
|
345
434
|
return compiled;
|
|
346
435
|
}
|
|
347
436
|
function createRequestContext(url) {
|
|
@@ -377,7 +466,7 @@ function resolveAppFactory(module) {
|
|
|
377
466
|
}
|
|
378
467
|
return createApp;
|
|
379
468
|
}
|
|
380
|
-
function collectCSS(themeCss, module) {
|
|
469
|
+
function collectCSS(themeCss, module, renderedHtml) {
|
|
381
470
|
const alreadyIncluded = new Set;
|
|
382
471
|
if (themeCss)
|
|
383
472
|
alreadyIncluded.add(themeCss);
|
|
@@ -386,8 +475,13 @@ function collectCSS(themeCss, module) {
|
|
|
386
475
|
alreadyIncluded.add(s);
|
|
387
476
|
}
|
|
388
477
|
const ssrCtx = ssrStorage.getStore();
|
|
389
|
-
const
|
|
390
|
-
const
|
|
478
|
+
const tracker = ssrCtx?.cssTracker;
|
|
479
|
+
const useTracker = tracker && tracker.size > 0;
|
|
480
|
+
const rawComponentCss = useTracker ? Array.from(tracker) : module.getInjectedCSS?.() ?? [];
|
|
481
|
+
let componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
|
|
482
|
+
if (!useTracker && componentCss.length > 0 && renderedHtml) {
|
|
483
|
+
componentCss = filterCSSByHTML(renderedHtml, componentCss);
|
|
484
|
+
}
|
|
391
485
|
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
392
486
|
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
393
487
|
`)}</style>` : "";
|
|
@@ -468,7 +562,7 @@ async function ssrRenderToString(module, url, options) {
|
|
|
468
562
|
const vnode = toVNode(app);
|
|
469
563
|
const stream = renderToStream(vnode);
|
|
470
564
|
const html = await streamToString(stream);
|
|
471
|
-
const css = collectCSS(themeCss, module);
|
|
565
|
+
const css = collectCSS(themeCss, module, html);
|
|
472
566
|
const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({ key, data })) : [];
|
|
473
567
|
return {
|
|
474
568
|
html,
|
|
@@ -718,7 +812,7 @@ async function ssrRenderSinglePass(module, url, options) {
|
|
|
718
812
|
const vnode = toVNode(app);
|
|
719
813
|
const stream = renderToStream(vnode);
|
|
720
814
|
const html = await streamToString(stream);
|
|
721
|
-
const css = collectCSS2(themeCss, module);
|
|
815
|
+
const css = collectCSS2(themeCss, module, html);
|
|
722
816
|
const ssrData = discoveredData.resolvedQueries.map(({ key, data }) => ({
|
|
723
817
|
key,
|
|
724
818
|
data
|
|
@@ -775,7 +869,7 @@ async function ssrRenderProgressive(module, url, options) {
|
|
|
775
869
|
const app = createApp();
|
|
776
870
|
const vnode = toVNode(app);
|
|
777
871
|
const renderStream = renderToStream(vnode);
|
|
778
|
-
const css = collectCSS2(themeCss, module);
|
|
872
|
+
const css = collectCSS2(themeCss, module, "");
|
|
779
873
|
const ssrData = discoveryResult.resolvedQueries.map(({ key, data }) => ({
|
|
780
874
|
key,
|
|
781
875
|
data
|
|
@@ -926,7 +1020,7 @@ async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, o
|
|
|
926
1020
|
matchedRoutePatterns: renderCtx.matchedRoutePatterns
|
|
927
1021
|
};
|
|
928
1022
|
}
|
|
929
|
-
const css = collectCSS2(themeCss, module);
|
|
1023
|
+
const css = collectCSS2(themeCss, module, html);
|
|
930
1024
|
const ssrData = data.resolvedQueries.map(({ key, data: d }) => ({
|
|
931
1025
|
key,
|
|
932
1026
|
data: d
|
|
@@ -999,7 +1093,7 @@ function extractMethodFromKey(key) {
|
|
|
999
1093
|
const segments = cleanPath.split("/").filter(Boolean);
|
|
1000
1094
|
return segments.length > 1 ? "get" : "list";
|
|
1001
1095
|
}
|
|
1002
|
-
function collectCSS2(themeCss, module) {
|
|
1096
|
+
function collectCSS2(themeCss, module, renderedHtml) {
|
|
1003
1097
|
const alreadyIncluded = new Set;
|
|
1004
1098
|
if (themeCss)
|
|
1005
1099
|
alreadyIncluded.add(themeCss);
|
|
@@ -1008,8 +1102,13 @@ function collectCSS2(themeCss, module) {
|
|
|
1008
1102
|
alreadyIncluded.add(s);
|
|
1009
1103
|
}
|
|
1010
1104
|
const ssrCtx = ssrStorage.getStore();
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1105
|
+
const tracker = ssrCtx?.cssTracker;
|
|
1106
|
+
const useTracker = tracker && tracker.size > 0;
|
|
1107
|
+
const rawComponentCss = useTracker ? Array.from(tracker) : module.getInjectedCSS?.() ?? [];
|
|
1108
|
+
let componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
|
|
1109
|
+
if (!useTracker && componentCss.length > 0 && renderedHtml) {
|
|
1110
|
+
componentCss = filterCSSByHTML(renderedHtml, componentCss);
|
|
1111
|
+
}
|
|
1013
1112
|
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1014
1113
|
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
1015
1114
|
`)}</style>` : "";
|
|
@@ -1081,7 +1180,8 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1081
1180
|
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1082
1181
|
}
|
|
1083
1182
|
const queryCache = new Map;
|
|
1084
|
-
const
|
|
1183
|
+
const searchParams = extractSearchParams(normalizedUrl);
|
|
1184
|
+
const resolvedQueryKeys = aotEntry.queryKeys ? resolveParamQueryKeys(aotEntry.queryKeys, match.params, searchParams) : undefined;
|
|
1085
1185
|
if (resolvedQueryKeys && resolvedQueryKeys.length > 0 && manifest?.routeEntries) {
|
|
1086
1186
|
const apiClient = module.api;
|
|
1087
1187
|
if (apiClient) {
|
|
@@ -1092,7 +1192,7 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1092
1192
|
const unresolvedKeys = resolvedQueryKeys.filter((k) => !queryCache.has(k));
|
|
1093
1193
|
if (unresolvedKeys.length > 0) {
|
|
1094
1194
|
try {
|
|
1095
|
-
const resolved = await options.aotDataResolver(match.pattern, match.params, unresolvedKeys);
|
|
1195
|
+
const resolved = await options.aotDataResolver(match.pattern, match.params, unresolvedKeys, searchParams);
|
|
1096
1196
|
for (const [key, value] of resolved) {
|
|
1097
1197
|
queryCache.set(key, value);
|
|
1098
1198
|
}
|
|
@@ -1114,13 +1214,37 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1114
1214
|
holes,
|
|
1115
1215
|
getData: (key) => queryCache.get(key),
|
|
1116
1216
|
session: options.prefetchSession,
|
|
1117
|
-
params: match.params
|
|
1217
|
+
params: match.params,
|
|
1218
|
+
searchParams
|
|
1118
1219
|
};
|
|
1119
1220
|
const data = {};
|
|
1120
1221
|
for (const [key, value] of queryCache) {
|
|
1121
1222
|
data[key] = value;
|
|
1122
1223
|
}
|
|
1123
|
-
|
|
1224
|
+
let html;
|
|
1225
|
+
try {
|
|
1226
|
+
html = aotEntry.render(data, ctx);
|
|
1227
|
+
} catch (renderErr) {
|
|
1228
|
+
console.error("[SSR] AOT render failed for", match.pattern, "— falling back to single-pass:", renderErr instanceof Error ? renderErr.message : renderErr);
|
|
1229
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1230
|
+
}
|
|
1231
|
+
if (aotManifest.app) {
|
|
1232
|
+
try {
|
|
1233
|
+
const appHoleNames = aotManifest.app.holes.filter((h) => h !== "RouterView");
|
|
1234
|
+
const appHoles = createHoles(appHoleNames, module, normalizedUrl, queryCache, options.ssrAuth);
|
|
1235
|
+
appHoles.RouterView = () => html;
|
|
1236
|
+
const appCtx = {
|
|
1237
|
+
holes: appHoles,
|
|
1238
|
+
getData: (key) => queryCache.get(key),
|
|
1239
|
+
session: options.prefetchSession,
|
|
1240
|
+
params: match.params,
|
|
1241
|
+
searchParams
|
|
1242
|
+
};
|
|
1243
|
+
html = aotManifest.app.render(data, appCtx);
|
|
1244
|
+
} catch (appErr) {
|
|
1245
|
+
console.error("[SSR] AOT app shell render failed — serving page without layout:", appErr instanceof Error ? appErr.message : appErr);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1124
1248
|
if (options.diagnostics && isAotDebugEnabled()) {
|
|
1125
1249
|
try {
|
|
1126
1250
|
const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
@@ -1129,7 +1253,8 @@ async function ssrRenderAot(module, url, options) {
|
|
|
1129
1253
|
}
|
|
1130
1254
|
} catch {}
|
|
1131
1255
|
}
|
|
1132
|
-
const
|
|
1256
|
+
const routeCss = mergeRouteCss(aotEntry.css, aotManifest.app?.css);
|
|
1257
|
+
const css = collectCSSFromModule(module, html, options.fallbackMetrics, routeCss, match.pattern);
|
|
1133
1258
|
const ssrData = [];
|
|
1134
1259
|
for (const [key, data2] of queryCache) {
|
|
1135
1260
|
ssrData.push({ key, data: data2 });
|
|
@@ -1186,8 +1311,20 @@ function unwrapResult2(result) {
|
|
|
1186
1311
|
}
|
|
1187
1312
|
return result;
|
|
1188
1313
|
}
|
|
1189
|
-
function resolveParamQueryKeys(queryKeys, params) {
|
|
1190
|
-
return queryKeys.map((key) => key.replace(/\$\{(\w+)
|
|
1314
|
+
function resolveParamQueryKeys(queryKeys, params, searchParams) {
|
|
1315
|
+
return queryKeys.map((key) => key.replace(/\$\{sp:(\w+)(?:\|([^}]*))?\}/g, (_, spName, defaultVal) => {
|
|
1316
|
+
const value = searchParams?.get(spName);
|
|
1317
|
+
if (defaultVal !== undefined) {
|
|
1318
|
+
return value || defaultVal;
|
|
1319
|
+
}
|
|
1320
|
+
return value ?? "";
|
|
1321
|
+
}).replace(/\$\{(\w+)\}/g, (_, paramName) => params[paramName] ?? ""));
|
|
1322
|
+
}
|
|
1323
|
+
function extractSearchParams(url) {
|
|
1324
|
+
const qIdx = url.indexOf("?");
|
|
1325
|
+
if (qIdx === -1)
|
|
1326
|
+
return;
|
|
1327
|
+
return new URLSearchParams(url.slice(qIdx));
|
|
1191
1328
|
}
|
|
1192
1329
|
function isAotDebugEnabled() {
|
|
1193
1330
|
const env = process.env.VERTZ_DEBUG;
|
|
@@ -1202,7 +1339,32 @@ function ensureDomShim3() {
|
|
|
1202
1339
|
domShimInstalled3 = true;
|
|
1203
1340
|
installDomShim();
|
|
1204
1341
|
}
|
|
1205
|
-
function
|
|
1342
|
+
function mergeRouteCss(routeCss, appCss) {
|
|
1343
|
+
if (!routeCss && !appCss)
|
|
1344
|
+
return;
|
|
1345
|
+
if (!appCss)
|
|
1346
|
+
return routeCss;
|
|
1347
|
+
if (!routeCss)
|
|
1348
|
+
return appCss;
|
|
1349
|
+
const seen = new Set(appCss);
|
|
1350
|
+
const merged = [...appCss];
|
|
1351
|
+
for (const rule of routeCss) {
|
|
1352
|
+
if (!seen.has(rule))
|
|
1353
|
+
merged.push(rule);
|
|
1354
|
+
}
|
|
1355
|
+
return merged;
|
|
1356
|
+
}
|
|
1357
|
+
var routeCssCache = new Map;
|
|
1358
|
+
function clearRouteCssCache() {
|
|
1359
|
+
routeCssCache.clear();
|
|
1360
|
+
}
|
|
1361
|
+
function collectCSSFromModule(module, renderedHtml, fallbackMetrics, routeCss, routePattern) {
|
|
1362
|
+
if (routePattern && routeCss) {
|
|
1363
|
+
const cached = routeCssCache.get(routePattern);
|
|
1364
|
+
if (cached) {
|
|
1365
|
+
return cached;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1206
1368
|
let themeCss = "";
|
|
1207
1369
|
let preloadTags = "";
|
|
1208
1370
|
if (module.theme) {
|
|
@@ -1221,9 +1383,19 @@ function collectCSSFromModule(module, fallbackMetrics) {
|
|
|
1221
1383
|
for (const s of module.styles)
|
|
1222
1384
|
alreadyIncluded.add(s);
|
|
1223
1385
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1386
|
+
let componentCss;
|
|
1387
|
+
if (routeCss && routeCss.length > 0) {
|
|
1388
|
+
componentCss = routeCss.filter((s) => !alreadyIncluded.has(s));
|
|
1389
|
+
} else {
|
|
1390
|
+
const ssrCtx = ssrStorage.getStore();
|
|
1391
|
+
const tracker = ssrCtx?.cssTracker;
|
|
1392
|
+
const useTracker = tracker && tracker.size > 0;
|
|
1393
|
+
const rawComponentCss = useTracker ? Array.from(tracker) : module.getInjectedCSS?.() ?? [];
|
|
1394
|
+
componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
|
|
1395
|
+
if (!useTracker && componentCss.length > 0 && renderedHtml) {
|
|
1396
|
+
componentCss = filterCSSByHTML(renderedHtml, componentCss);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1227
1399
|
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1228
1400
|
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
1229
1401
|
`)}</style>` : "";
|
|
@@ -1231,7 +1403,11 @@ function collectCSSFromModule(module, fallbackMetrics) {
|
|
|
1231
1403
|
`)}</style>` : "";
|
|
1232
1404
|
const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
1233
1405
|
`);
|
|
1234
|
-
|
|
1406
|
+
const result = { cssString, preloadTags };
|
|
1407
|
+
if (routePattern && routeCss) {
|
|
1408
|
+
routeCssCache.set(routePattern, result);
|
|
1409
|
+
}
|
|
1410
|
+
return result;
|
|
1235
1411
|
}
|
|
1236
1412
|
|
|
1237
1413
|
// src/ssr-access-set.ts
|
|
@@ -1507,4 +1683,4 @@ function replaceAppDivContent(template, appHtml) {
|
|
|
1507
1683
|
return template.slice(0, contentStart) + appHtml + template.slice(i);
|
|
1508
1684
|
}
|
|
1509
1685
|
|
|
1510
|
-
export { escapeHtml, escapeAttr, serializeToHtml, toPrefetchSession, evaluateAccessRule, reconstructDescriptors, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, compileThemeCached, createRequestContext, ssrRenderToString, ssrDiscoverQueries, ssrStreamNavQueries, matchUrlToPatterns, ssrRenderSinglePass, ssrRenderProgressive, createHoles, ssrRenderAot, isAotDebugEnabled, getAccessSetForSSR, createAccessSetScript, createSessionScript, resolveRouteModulepreload, precomputeHandlerState, resolveSession, injectIntoTemplate };
|
|
1686
|
+
export { escapeHtml, escapeAttr, serializeToHtml, toPrefetchSession, evaluateAccessRule, reconstructDescriptors, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, compileThemeCached, createRequestContext, ssrRenderToString, ssrDiscoverQueries, ssrStreamNavQueries, matchUrlToPatterns, ssrRenderSinglePass, ssrRenderProgressive, createHoles, ssrRenderAot, isAotDebugEnabled, clearRouteCssCache, getAccessSetForSSR, createAccessSetScript, createSessionScript, resolveRouteModulepreload, precomputeHandlerState, resolveSession, injectIntoTemplate };
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
ssrRenderSinglePass,
|
|
11
11
|
ssrStreamNavQueries,
|
|
12
12
|
toPrefetchSession
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-15kac8x8.js";
|
|
14
14
|
|
|
15
15
|
// src/node-handler.ts
|
|
16
16
|
function createNodeHandler(options) {
|
|
@@ -24,7 +24,8 @@ function createNodeHandler(options) {
|
|
|
24
24
|
sessionResolver,
|
|
25
25
|
manifest,
|
|
26
26
|
progressiveHTML,
|
|
27
|
-
aotManifest
|
|
27
|
+
aotManifest,
|
|
28
|
+
aotDataResolver
|
|
28
29
|
} = options;
|
|
29
30
|
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
30
31
|
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
@@ -60,7 +61,8 @@ function createNodeHandler(options) {
|
|
|
60
61
|
ssrTimeout,
|
|
61
62
|
fallbackMetrics,
|
|
62
63
|
ssrAuth,
|
|
63
|
-
prefetchSession
|
|
64
|
+
prefetchSession,
|
|
65
|
+
aotDataResolver
|
|
64
66
|
}) : await ssrRenderSinglePass(module, url, {
|
|
65
67
|
ssrTimeout,
|
|
66
68
|
fallbackMetrics,
|
|
@@ -11,14 +11,16 @@ import {
|
|
|
11
11
|
ssrRenderSinglePass,
|
|
12
12
|
ssrStreamNavQueries,
|
|
13
13
|
toPrefetchSession
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-15kac8x8.js";
|
|
15
15
|
|
|
16
16
|
// src/aot-manifest-loader.ts
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
async function loadAotManifest(serverDir) {
|
|
20
20
|
const manifestPath = join(serverDir, "aot-manifest.json");
|
|
21
|
-
const
|
|
21
|
+
const routesModuleTs = join(serverDir, "aot-routes.ts");
|
|
22
|
+
const routesModuleJs = join(serverDir, "aot-routes.js");
|
|
23
|
+
const routesModulePath = existsSync(routesModuleTs) ? routesModuleTs : routesModuleJs;
|
|
22
24
|
if (!existsSync(manifestPath) || !existsSync(routesModulePath)) {
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
@@ -46,13 +48,26 @@ async function loadAotManifest(serverDir) {
|
|
|
46
48
|
routes[pattern] = {
|
|
47
49
|
render: renderFn,
|
|
48
50
|
holes: entry.holes,
|
|
49
|
-
queryKeys: entry.queryKeys
|
|
51
|
+
queryKeys: entry.queryKeys,
|
|
52
|
+
css: entry.css
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
if (Object.keys(routes).length === 0) {
|
|
53
56
|
return null;
|
|
54
57
|
}
|
|
55
|
-
|
|
58
|
+
let app;
|
|
59
|
+
if (manifestJson.app) {
|
|
60
|
+
const appRenderFn = routesModule[manifestJson.app.renderFn];
|
|
61
|
+
if (typeof appRenderFn === "function") {
|
|
62
|
+
app = {
|
|
63
|
+
render: appRenderFn,
|
|
64
|
+
holes: manifestJson.app.holes,
|
|
65
|
+
queryKeys: manifestJson.app.queryKeys,
|
|
66
|
+
css: manifestJson.app.css
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { routes, app };
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
// src/ssr-progressive-response.ts
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -140,6 +140,8 @@ interface SSRAotContext {
|
|
|
140
140
|
session: PrefetchSession | undefined;
|
|
141
141
|
/** Route params for the current request. */
|
|
142
142
|
params: Record<string, string>;
|
|
143
|
+
/** URL search params for the current request (used by AOT functions with ${sp:name} keys). */
|
|
144
|
+
searchParams: URLSearchParams | undefined;
|
|
143
145
|
}
|
|
144
146
|
/** An AOT render function: takes props/data and context, returns HTML string. */
|
|
145
147
|
type AotRenderFn = (data: Record<string, unknown>, ctx: SSRAotContext) => string;
|
|
@@ -151,6 +153,8 @@ interface AotRouteEntry {
|
|
|
151
153
|
holes: string[];
|
|
152
154
|
/** Query cache keys this route reads via ctx.getData(). */
|
|
153
155
|
queryKeys?: string[];
|
|
156
|
+
/** Pre-filtered CSS rules for this route, determined at build time (#1988). */
|
|
157
|
+
css?: string[];
|
|
154
158
|
}
|
|
155
159
|
/**
|
|
156
160
|
* AOT manifest — maps route patterns to pre-compiled render functions.
|
|
@@ -160,6 +164,8 @@ interface AotRouteEntry {
|
|
|
160
164
|
interface AotManifest {
|
|
161
165
|
/** Route pattern → AOT entry. */
|
|
162
166
|
routes: Record<string, AotRouteEntry>;
|
|
167
|
+
/** Root layout (App) entry — wraps page content via its RouterView hole. */
|
|
168
|
+
app?: AotRouteEntry;
|
|
163
169
|
}
|
|
164
170
|
/**
|
|
165
171
|
* Resolves custom data for AOT-rendered routes.
|
|
@@ -172,7 +178,7 @@ interface AotManifest {
|
|
|
172
178
|
* @param unresolvedKeys - Query keys not yet populated by entity prefetch
|
|
173
179
|
* @returns Map of cache key → data, or empty Map to skip
|
|
174
180
|
*/
|
|
175
|
-
type AotDataResolver = (pattern: string, params: Record<string, string>, unresolvedKeys: string[]) => Promise<Map<string, unknown>> | Map<string, unknown>;
|
|
181
|
+
type AotDataResolver = (pattern: string, params: Record<string, string>, unresolvedKeys: string[], searchParams?: URLSearchParams) => Promise<Map<string, unknown>> | Map<string, unknown>;
|
|
176
182
|
/**
|
|
177
183
|
* Load AOT manifest and routes module from a server build directory.
|
|
178
184
|
*
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSSRHandler,
|
|
3
3
|
loadAotManifest
|
|
4
|
-
} from "../shared/chunk-
|
|
4
|
+
} from "../shared/chunk-j18fewkf.js";
|
|
5
5
|
import {
|
|
6
6
|
injectIntoTemplate,
|
|
7
7
|
ssrDiscoverQueries,
|
|
8
8
|
ssrRenderToString
|
|
9
|
-
} from "../shared/chunk-
|
|
9
|
+
} from "../shared/chunk-15kac8x8.js";
|
|
10
10
|
import"../shared/chunk-ybftdw1r.js";
|
|
11
11
|
// src/prerender.ts
|
|
12
12
|
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.41",
|
|
4
4
|
"description": "Vertz UI server-side rendering runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "bunup",
|
|
61
|
-
"test": "bun test src/",
|
|
61
|
+
"test": "bun test --timeout 60000 src/",
|
|
62
62
|
"test:integration": "bun test src/__tests__/bun-dev-server.integration.local.ts",
|
|
63
63
|
"test:e2e": "bunx playwright test",
|
|
64
64
|
"typecheck": "tsc --noEmit"
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"@ampproject/remapping": "^2.3.0",
|
|
68
68
|
"@capsizecss/unpack": "^4.0.0",
|
|
69
69
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
70
|
-
"@vertz/core": "^0.2.
|
|
71
|
-
"@vertz/ui": "^0.2.
|
|
72
|
-
"@vertz/ui-compiler": "^0.2.
|
|
70
|
+
"@vertz/core": "^0.2.39",
|
|
71
|
+
"@vertz/ui": "^0.2.39",
|
|
72
|
+
"@vertz/ui-compiler": "^0.2.39",
|
|
73
73
|
"magic-string": "^0.30.0",
|
|
74
74
|
"sharp": "^0.34.5",
|
|
75
75
|
"ts-morph": "^27.0.2"
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@happy-dom/global-registrator": "^20.8.3",
|
|
79
79
|
"@playwright/test": "^1.58.2",
|
|
80
|
-
"@vertz/codegen": "^0.2.
|
|
80
|
+
"@vertz/codegen": "^0.2.39",
|
|
81
81
|
"@vertz/ui-auth": "^0.2.19",
|
|
82
82
|
"bun-types": "^1.3.10",
|
|
83
83
|
"bunup": "^0.16.31",
|