@vertz/ui-server 0.2.31 → 0.2.32
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 +47 -2
- package/dist/index.d.ts +301 -233
- package/dist/index.js +79 -149
- package/dist/node-handler.d.ts +76 -23
- package/dist/shared/chunk-gbcsa7h1.js +471 -0
- package/dist/ssr/index.d.ts +119 -57
- package/dist/ssr/index.js +4 -3
- package/package.json +1 -1
- package/dist/shared/chunk-wb5fv233.js +0 -216
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileThemeCached,
|
|
3
|
+
createRequestContext,
|
|
4
|
+
encodeChunk,
|
|
5
|
+
escapeAttr,
|
|
6
|
+
injectIntoTemplate,
|
|
7
|
+
matchUrlToPatterns,
|
|
8
|
+
precomputeHandlerState,
|
|
9
|
+
reconstructDescriptors,
|
|
10
|
+
resolveRouteModulepreload,
|
|
11
|
+
resolveSession,
|
|
12
|
+
safeSerialize,
|
|
13
|
+
serializeToHtml,
|
|
14
|
+
ssrRenderProgressive,
|
|
15
|
+
ssrRenderSinglePass,
|
|
16
|
+
ssrStreamNavQueries,
|
|
17
|
+
toPrefetchSession
|
|
18
|
+
} from "./chunk-34fexgex.js";
|
|
19
|
+
import {
|
|
20
|
+
clearGlobalSSRTimeout,
|
|
21
|
+
installDomShim,
|
|
22
|
+
setGlobalSSRTimeout,
|
|
23
|
+
ssrStorage,
|
|
24
|
+
toVNode
|
|
25
|
+
} from "./chunk-ybftdw1r.js";
|
|
26
|
+
|
|
27
|
+
// src/aot-manifest-loader.ts
|
|
28
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
async function loadAotManifest(serverDir) {
|
|
31
|
+
const manifestPath = join(serverDir, "aot-manifest.json");
|
|
32
|
+
const routesModulePath = join(serverDir, "aot-routes.js");
|
|
33
|
+
if (!existsSync(manifestPath) || !existsSync(routesModulePath)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
let manifestJson;
|
|
37
|
+
try {
|
|
38
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
39
|
+
manifestJson = JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!manifestJson.routes || Object.keys(manifestJson.routes).length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let routesModule;
|
|
47
|
+
try {
|
|
48
|
+
routesModule = await import(routesModulePath);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const routes = {};
|
|
53
|
+
for (const [pattern, entry] of Object.entries(manifestJson.routes)) {
|
|
54
|
+
const renderFn = routesModule[entry.renderFn];
|
|
55
|
+
if (typeof renderFn !== "function")
|
|
56
|
+
continue;
|
|
57
|
+
routes[pattern] = {
|
|
58
|
+
render: renderFn,
|
|
59
|
+
holes: entry.holes,
|
|
60
|
+
queryKeys: entry.queryKeys
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(routes).length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { routes };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/ssr-aot-pipeline.ts
|
|
70
|
+
function createHoles(holeNames, module, url, queryCache, ssrAuth) {
|
|
71
|
+
if (holeNames.length === 0)
|
|
72
|
+
return {};
|
|
73
|
+
const holes = {};
|
|
74
|
+
for (const name of holeNames) {
|
|
75
|
+
holes[name] = () => {
|
|
76
|
+
const holeCtx = createRequestContext(url);
|
|
77
|
+
for (const [key, data] of queryCache) {
|
|
78
|
+
holeCtx.queryCache.set(key, data);
|
|
79
|
+
}
|
|
80
|
+
if (ssrAuth) {
|
|
81
|
+
holeCtx.ssrAuth = ssrAuth;
|
|
82
|
+
}
|
|
83
|
+
holeCtx.resolvedComponents = new Map;
|
|
84
|
+
return ssrStorage.run(holeCtx, () => {
|
|
85
|
+
ensureDomShim();
|
|
86
|
+
const factory = resolveHoleComponent(module, name);
|
|
87
|
+
if (!factory) {
|
|
88
|
+
return `<!-- AOT hole: ${name} not found -->`;
|
|
89
|
+
}
|
|
90
|
+
const node = factory();
|
|
91
|
+
const vnode = toVNode(node);
|
|
92
|
+
return serializeToHtml(vnode);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return holes;
|
|
97
|
+
}
|
|
98
|
+
function resolveHoleComponent(module, name) {
|
|
99
|
+
const moduleRecord = module;
|
|
100
|
+
const exported = moduleRecord[name];
|
|
101
|
+
if (typeof exported === "function") {
|
|
102
|
+
return exported;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
async function ssrRenderAot(module, url, options) {
|
|
107
|
+
const { aotManifest, manifest } = options;
|
|
108
|
+
const ssrTimeout = options.ssrTimeout ?? 300;
|
|
109
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
110
|
+
const fallbackOptions = {
|
|
111
|
+
ssrTimeout,
|
|
112
|
+
fallbackMetrics: options.fallbackMetrics,
|
|
113
|
+
ssrAuth: options.ssrAuth,
|
|
114
|
+
manifest,
|
|
115
|
+
prefetchSession: options.prefetchSession
|
|
116
|
+
};
|
|
117
|
+
const aotPatterns = Object.keys(aotManifest.routes);
|
|
118
|
+
const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
|
|
119
|
+
if (matches.length === 0) {
|
|
120
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
121
|
+
}
|
|
122
|
+
const match = matches[matches.length - 1];
|
|
123
|
+
if (!match) {
|
|
124
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
125
|
+
}
|
|
126
|
+
const aotEntry = aotManifest.routes[match.pattern];
|
|
127
|
+
if (!aotEntry) {
|
|
128
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
129
|
+
}
|
|
130
|
+
const queryCache = new Map;
|
|
131
|
+
if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0 && manifest?.routeEntries) {
|
|
132
|
+
const apiClient = module.api;
|
|
133
|
+
if (apiClient) {
|
|
134
|
+
await prefetchForAot(aotEntry.queryKeys, manifest.routeEntries, match, apiClient, ssrTimeout, queryCache);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
139
|
+
const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
|
|
140
|
+
const ctx = {
|
|
141
|
+
holes,
|
|
142
|
+
getData: (key) => queryCache.get(key),
|
|
143
|
+
session: options.prefetchSession,
|
|
144
|
+
params: match.params
|
|
145
|
+
};
|
|
146
|
+
const data = {};
|
|
147
|
+
for (const [key, value] of queryCache) {
|
|
148
|
+
data[key] = value;
|
|
149
|
+
}
|
|
150
|
+
const html = aotEntry.render(data, ctx);
|
|
151
|
+
if (options.diagnostics && isAotDebugEnabled()) {
|
|
152
|
+
try {
|
|
153
|
+
const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
154
|
+
if (domResult.html !== html) {
|
|
155
|
+
options.diagnostics.recordDivergence(match.pattern, html, domResult.html);
|
|
156
|
+
}
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
const css = collectCSSFromModule(module, options.fallbackMetrics);
|
|
160
|
+
const ssrData = [];
|
|
161
|
+
for (const [key, data2] of queryCache) {
|
|
162
|
+
ssrData.push({ key, data: data2 });
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
html,
|
|
166
|
+
css: css.cssString,
|
|
167
|
+
ssrData,
|
|
168
|
+
headTags: css.preloadTags,
|
|
169
|
+
matchedRoutePatterns: [match.pattern]
|
|
170
|
+
};
|
|
171
|
+
} finally {
|
|
172
|
+
clearGlobalSSRTimeout();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function prefetchForAot(queryKeys, routeEntries, match, apiClient, ssrTimeout, queryCache) {
|
|
176
|
+
const entry = routeEntries[match.pattern];
|
|
177
|
+
if (!entry)
|
|
178
|
+
return;
|
|
179
|
+
const queryKeySet = new Set(queryKeys);
|
|
180
|
+
const fetchJobs = [];
|
|
181
|
+
for (const query of entry.queries) {
|
|
182
|
+
if (!query.entity || !query.operation)
|
|
183
|
+
continue;
|
|
184
|
+
const aotKey = `${query.entity}-${query.operation}`;
|
|
185
|
+
if (!queryKeySet.has(aotKey))
|
|
186
|
+
continue;
|
|
187
|
+
const descriptors = reconstructDescriptors([query], match.params, apiClient);
|
|
188
|
+
if (descriptors.length > 0 && descriptors[0]) {
|
|
189
|
+
fetchJobs.push({ aotKey, fetchFn: descriptors[0].fetch });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (fetchJobs.length === 0)
|
|
193
|
+
return;
|
|
194
|
+
await Promise.allSettled(fetchJobs.map(({ aotKey, fetchFn }) => {
|
|
195
|
+
let timer;
|
|
196
|
+
return Promise.race([
|
|
197
|
+
fetchFn().then((result) => {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
const data = unwrapResult(result);
|
|
200
|
+
queryCache.set(aotKey, data);
|
|
201
|
+
}),
|
|
202
|
+
new Promise((r) => {
|
|
203
|
+
timer = setTimeout(r, ssrTimeout);
|
|
204
|
+
})
|
|
205
|
+
]);
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
function unwrapResult(result) {
|
|
209
|
+
if (result && typeof result === "object" && "ok" in result && "data" in result) {
|
|
210
|
+
const r = result;
|
|
211
|
+
if (r.ok)
|
|
212
|
+
return r.data;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
function isAotDebugEnabled() {
|
|
217
|
+
const env = process.env.VERTZ_DEBUG;
|
|
218
|
+
if (!env)
|
|
219
|
+
return false;
|
|
220
|
+
return env === "1" || env.split(",").includes("aot");
|
|
221
|
+
}
|
|
222
|
+
var domShimInstalled = false;
|
|
223
|
+
function ensureDomShim() {
|
|
224
|
+
if (domShimInstalled && typeof document !== "undefined")
|
|
225
|
+
return;
|
|
226
|
+
domShimInstalled = true;
|
|
227
|
+
installDomShim();
|
|
228
|
+
}
|
|
229
|
+
function collectCSSFromModule(module, fallbackMetrics) {
|
|
230
|
+
let themeCss = "";
|
|
231
|
+
let preloadTags = "";
|
|
232
|
+
if (module.theme) {
|
|
233
|
+
try {
|
|
234
|
+
const compiled = compileThemeCached(module.theme, fallbackMetrics);
|
|
235
|
+
themeCss = compiled.css;
|
|
236
|
+
preloadTags = compiled.preloadTags;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const alreadyIncluded = new Set;
|
|
242
|
+
if (themeCss)
|
|
243
|
+
alreadyIncluded.add(themeCss);
|
|
244
|
+
if (module.styles) {
|
|
245
|
+
for (const s of module.styles)
|
|
246
|
+
alreadyIncluded.add(s);
|
|
247
|
+
}
|
|
248
|
+
const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
|
|
249
|
+
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
250
|
+
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
251
|
+
`)}</style>` : "";
|
|
252
|
+
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
253
|
+
`)}</style>` : "";
|
|
254
|
+
const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
255
|
+
`);
|
|
256
|
+
return { cssString, preloadTags };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/ssr-progressive-response.ts
|
|
260
|
+
function buildProgressiveResponse(options) {
|
|
261
|
+
const { headChunk, renderStream, tailChunk, ssrData, nonce, headers } = options;
|
|
262
|
+
const stream = new ReadableStream({
|
|
263
|
+
async start(controller) {
|
|
264
|
+
controller.enqueue(encodeChunk(headChunk));
|
|
265
|
+
const reader = renderStream.getReader();
|
|
266
|
+
let renderError;
|
|
267
|
+
try {
|
|
268
|
+
for (;; ) {
|
|
269
|
+
const { done, value } = await reader.read();
|
|
270
|
+
if (done)
|
|
271
|
+
break;
|
|
272
|
+
controller.enqueue(value);
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
renderError = err instanceof Error ? err : new Error(String(err));
|
|
276
|
+
}
|
|
277
|
+
if (renderError) {
|
|
278
|
+
console.error("[SSR] Render error after head sent:", renderError.message);
|
|
279
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
280
|
+
const errorScript = `<script${nonceAttr}>document.dispatchEvent(new CustomEvent('vertz:ssr-error',` + `{detail:{message:${safeSerialize(renderError.message)}}}))</script>`;
|
|
281
|
+
controller.enqueue(encodeChunk(errorScript));
|
|
282
|
+
}
|
|
283
|
+
let tail = "";
|
|
284
|
+
if (ssrData.length > 0) {
|
|
285
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
286
|
+
tail += `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>`;
|
|
287
|
+
}
|
|
288
|
+
tail += tailChunk;
|
|
289
|
+
controller.enqueue(encodeChunk(tail));
|
|
290
|
+
controller.close();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const responseHeaders = {
|
|
294
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
295
|
+
...headers
|
|
296
|
+
};
|
|
297
|
+
return new Response(stream, { status: 200, headers: responseHeaders });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/ssr-handler.ts
|
|
301
|
+
function createSSRHandler(options) {
|
|
302
|
+
const {
|
|
303
|
+
module,
|
|
304
|
+
ssrTimeout,
|
|
305
|
+
nonce,
|
|
306
|
+
fallbackMetrics,
|
|
307
|
+
routeChunkManifest,
|
|
308
|
+
cacheControl,
|
|
309
|
+
sessionResolver,
|
|
310
|
+
manifest,
|
|
311
|
+
progressiveHTML,
|
|
312
|
+
aotManifest
|
|
313
|
+
} = options;
|
|
314
|
+
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
315
|
+
return async (request) => {
|
|
316
|
+
const url = new URL(request.url);
|
|
317
|
+
const pathname = url.pathname;
|
|
318
|
+
if (request.headers.get("x-vertz-nav") === "1") {
|
|
319
|
+
return handleNavRequest(module, pathname, ssrTimeout);
|
|
320
|
+
}
|
|
321
|
+
let sessionScript = "";
|
|
322
|
+
let ssrAuth;
|
|
323
|
+
if (sessionResolver) {
|
|
324
|
+
const result = await resolveSession(request, sessionResolver, nonce);
|
|
325
|
+
sessionScript = result.sessionScript;
|
|
326
|
+
ssrAuth = result.ssrAuth;
|
|
327
|
+
}
|
|
328
|
+
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
329
|
+
if (useProgressive) {
|
|
330
|
+
return handleProgressiveHTMLRequest(module, splitResult, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
|
|
331
|
+
}
|
|
332
|
+
return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest);
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function handleNavRequest(module, url, ssrTimeout) {
|
|
336
|
+
try {
|
|
337
|
+
const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
|
|
338
|
+
return new Response(stream, {
|
|
339
|
+
status: 200,
|
|
340
|
+
headers: {
|
|
341
|
+
"Content-Type": "text/event-stream",
|
|
342
|
+
"Cache-Control": "no-cache"
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
} catch {
|
|
346
|
+
return new Response(`event: done
|
|
347
|
+
data: {}
|
|
348
|
+
|
|
349
|
+
`, {
|
|
350
|
+
status: 200,
|
|
351
|
+
headers: {
|
|
352
|
+
"Content-Type": "text/event-stream",
|
|
353
|
+
"Cache-Control": "no-cache"
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
|
|
359
|
+
try {
|
|
360
|
+
const result = await ssrRenderProgressive(module, url, {
|
|
361
|
+
ssrTimeout,
|
|
362
|
+
fallbackMetrics,
|
|
363
|
+
ssrAuth,
|
|
364
|
+
manifest
|
|
365
|
+
});
|
|
366
|
+
if (result.redirect) {
|
|
367
|
+
return new Response(null, {
|
|
368
|
+
status: 302,
|
|
369
|
+
headers: { Location: result.redirect.to }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
|
|
373
|
+
let headChunk = split.headTemplate;
|
|
374
|
+
const headCloseIdx = headChunk.lastIndexOf("</head>");
|
|
375
|
+
if (headCloseIdx !== -1) {
|
|
376
|
+
const injections = [];
|
|
377
|
+
if (result.css)
|
|
378
|
+
injections.push(result.css);
|
|
379
|
+
if (result.headTags)
|
|
380
|
+
injections.push(result.headTags);
|
|
381
|
+
if (modulepreloadTags)
|
|
382
|
+
injections.push(modulepreloadTags);
|
|
383
|
+
if (sessionScript)
|
|
384
|
+
injections.push(sessionScript);
|
|
385
|
+
if (injections.length > 0) {
|
|
386
|
+
headChunk = headChunk.slice(0, headCloseIdx) + injections.join(`
|
|
387
|
+
`) + `
|
|
388
|
+
` + headChunk.slice(headCloseIdx);
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
if (result.css)
|
|
392
|
+
headChunk += result.css;
|
|
393
|
+
if (result.headTags)
|
|
394
|
+
headChunk += result.headTags;
|
|
395
|
+
if (modulepreloadTags)
|
|
396
|
+
headChunk += modulepreloadTags;
|
|
397
|
+
if (sessionScript)
|
|
398
|
+
headChunk += sessionScript;
|
|
399
|
+
}
|
|
400
|
+
const headers = {};
|
|
401
|
+
if (linkHeader)
|
|
402
|
+
headers.Link = linkHeader;
|
|
403
|
+
if (cacheControl)
|
|
404
|
+
headers["Cache-Control"] = cacheControl;
|
|
405
|
+
return buildProgressiveResponse({
|
|
406
|
+
headChunk,
|
|
407
|
+
renderStream: result.renderStream,
|
|
408
|
+
tailChunk: split.tailTemplate,
|
|
409
|
+
ssrData: result.ssrData,
|
|
410
|
+
nonce,
|
|
411
|
+
headers
|
|
412
|
+
});
|
|
413
|
+
} catch (err) {
|
|
414
|
+
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
415
|
+
return new Response("Internal Server Error", {
|
|
416
|
+
status: 500,
|
|
417
|
+
headers: { "Content-Type": "text/plain" }
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest) {
|
|
422
|
+
try {
|
|
423
|
+
const prefetchSession = ssrAuth ? toPrefetchSession(ssrAuth) : undefined;
|
|
424
|
+
const result = aotManifest ? await ssrRenderAot(module, url, {
|
|
425
|
+
aotManifest,
|
|
426
|
+
manifest,
|
|
427
|
+
ssrTimeout,
|
|
428
|
+
fallbackMetrics,
|
|
429
|
+
ssrAuth,
|
|
430
|
+
prefetchSession
|
|
431
|
+
}) : await ssrRenderSinglePass(module, url, {
|
|
432
|
+
ssrTimeout,
|
|
433
|
+
fallbackMetrics,
|
|
434
|
+
ssrAuth,
|
|
435
|
+
manifest,
|
|
436
|
+
prefetchSession
|
|
437
|
+
});
|
|
438
|
+
if (result.redirect) {
|
|
439
|
+
return new Response(null, {
|
|
440
|
+
status: 302,
|
|
441
|
+
headers: { Location: result.redirect.to }
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
|
|
445
|
+
const allHeadTags = [result.headTags, modulepreloadTags].filter(Boolean).join(`
|
|
446
|
+
`);
|
|
447
|
+
const html = injectIntoTemplate({
|
|
448
|
+
template,
|
|
449
|
+
appHtml: result.html,
|
|
450
|
+
appCss: result.css,
|
|
451
|
+
ssrData: result.ssrData,
|
|
452
|
+
nonce,
|
|
453
|
+
headTags: allHeadTags || undefined,
|
|
454
|
+
sessionScript
|
|
455
|
+
});
|
|
456
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
457
|
+
if (linkHeader)
|
|
458
|
+
headers.Link = linkHeader;
|
|
459
|
+
if (cacheControl)
|
|
460
|
+
headers["Cache-Control"] = cacheControl;
|
|
461
|
+
return new Response(html, { status: 200, headers });
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
464
|
+
return new Response("Internal Server Error", {
|
|
465
|
+
status: 500,
|
|
466
|
+
headers: { "Content-Type": "text/plain" }
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export { loadAotManifest, createHoles, ssrRenderAot, isAotDebugEnabled, createSSRHandler };
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SSR prefetch access rule evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates serialized entity access rules against the current session
|
|
5
|
+
* to determine whether a query should be prefetched during SSR.
|
|
6
|
+
*
|
|
7
|
+
* The serialized rules come from the prefetch manifest (generated at build time).
|
|
8
|
+
* The session comes from the JWT decoded at request time.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Serialized access rule — the JSON-friendly format stored in the manifest.
|
|
12
|
+
* Mirrors SerializedRule from @vertz/server/auth/rules but defined here
|
|
13
|
+
* to avoid importing the server package into the SSR pipeline.
|
|
14
|
+
*/
|
|
15
|
+
type SerializedAccessRule = {
|
|
16
|
+
type: "public";
|
|
17
|
+
} | {
|
|
18
|
+
type: "authenticated";
|
|
19
|
+
} | {
|
|
20
|
+
type: "role";
|
|
21
|
+
roles: string[];
|
|
22
|
+
} | {
|
|
23
|
+
type: "entitlement";
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: "where";
|
|
27
|
+
conditions: Record<string, unknown>;
|
|
28
|
+
} | {
|
|
29
|
+
type: "all";
|
|
30
|
+
rules: SerializedAccessRule[];
|
|
31
|
+
} | {
|
|
32
|
+
type: "any";
|
|
33
|
+
rules: SerializedAccessRule[];
|
|
34
|
+
} | {
|
|
35
|
+
type: "fva";
|
|
36
|
+
maxAge: number;
|
|
37
|
+
} | {
|
|
38
|
+
type: "deny";
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Minimal session shape needed for prefetch access evaluation.
|
|
42
|
+
* Extracted from the JWT at SSR request time.
|
|
43
|
+
*/
|
|
44
|
+
type PrefetchSession = {
|
|
45
|
+
status: "authenticated";
|
|
46
|
+
roles?: string[];
|
|
47
|
+
entitlements?: Record<string, boolean>;
|
|
48
|
+
tenantId?: string;
|
|
49
|
+
} | {
|
|
50
|
+
status: "unauthenticated";
|
|
51
|
+
};
|
|
2
52
|
import { CompiledRoute, FontFallbackMetrics, Theme } from "@vertz/ui";
|
|
3
53
|
import { SSRAuth as SSRAuth_jq1nwm } from "@vertz/ui/internals";
|
|
4
54
|
interface SSRModule {
|
|
@@ -67,6 +117,60 @@ declare function ssrRenderToString(module: SSRModule, url: string, options?: {
|
|
|
67
117
|
declare function ssrDiscoverQueries(module: SSRModule, url: string, options?: {
|
|
68
118
|
ssrTimeout?: number;
|
|
69
119
|
}): Promise<SSRDiscoverResult>;
|
|
120
|
+
import { ExtractedQuery } from "@vertz/ui-compiler";
|
|
121
|
+
/** Serialized entity access rules from the prefetch manifest. */
|
|
122
|
+
type EntityAccessMap = Record<string, Partial<Record<string, SerializedAccessRule>>>;
|
|
123
|
+
interface SSRPrefetchManifest {
|
|
124
|
+
/** Route patterns present in the manifest. */
|
|
125
|
+
routePatterns: string[];
|
|
126
|
+
/** Entity access rules keyed by entity name → operation → serialized rule. */
|
|
127
|
+
entityAccess?: EntityAccessMap;
|
|
128
|
+
/** Route entries with query binding metadata for zero-discovery prefetch. */
|
|
129
|
+
routeEntries?: Record<string, {
|
|
130
|
+
queries: ExtractedQuery[];
|
|
131
|
+
}>;
|
|
132
|
+
}
|
|
133
|
+
/** Context passed to AOT render functions for accessing data and runtime holes. */
|
|
134
|
+
interface SSRAotContext {
|
|
135
|
+
/** Pre-generated closures for runtime-rendered components. */
|
|
136
|
+
holes: Record<string, () => string>;
|
|
137
|
+
/** Access query data by cache key. */
|
|
138
|
+
getData(key: string): unknown;
|
|
139
|
+
/** Auth session for conditional rendering. */
|
|
140
|
+
session: PrefetchSession | undefined;
|
|
141
|
+
/** Route params for the current request. */
|
|
142
|
+
params: Record<string, string>;
|
|
143
|
+
}
|
|
144
|
+
/** An AOT render function: takes props/data and context, returns HTML string. */
|
|
145
|
+
type AotRenderFn = (data: Record<string, unknown>, ctx: SSRAotContext) => string;
|
|
146
|
+
/** Per-route AOT entry in the manifest. */
|
|
147
|
+
interface AotRouteEntry {
|
|
148
|
+
/** The pre-compiled render function. */
|
|
149
|
+
render: AotRenderFn;
|
|
150
|
+
/** Component names that need runtime fallback (holes). */
|
|
151
|
+
holes: string[];
|
|
152
|
+
/** Query cache keys this route reads via ctx.getData(). */
|
|
153
|
+
queryKeys?: string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* AOT manifest — maps route patterns to pre-compiled render functions.
|
|
157
|
+
*
|
|
158
|
+
* Generated at build time by the AOT compiler pipeline.
|
|
159
|
+
*/
|
|
160
|
+
interface AotManifest {
|
|
161
|
+
/** Route pattern → AOT entry. */
|
|
162
|
+
routes: Record<string, AotRouteEntry>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Load AOT manifest and routes module from a server build directory.
|
|
166
|
+
*
|
|
167
|
+
* Returns `null` if either `aot-manifest.json` or `aot-routes.js` is missing,
|
|
168
|
+
* or if no routes can be wired to render functions.
|
|
169
|
+
*
|
|
170
|
+
* @param serverDir - Path to `dist/server/` directory
|
|
171
|
+
*/
|
|
172
|
+
declare function loadAotManifest(serverDir: string): Promise<AotManifest | null>;
|
|
173
|
+
import { CompiledRoute as CompiledRoute2, FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
|
|
70
174
|
interface PrerenderResult {
|
|
71
175
|
/** The route path that was pre-rendered. */
|
|
72
176
|
path: string;
|
|
@@ -79,7 +183,7 @@ interface PrerenderOptions {
|
|
|
79
183
|
/** CSP nonce for inline scripts. */
|
|
80
184
|
nonce?: string;
|
|
81
185
|
/** Pre-computed font fallback metrics for zero-CLS font loading. */
|
|
82
|
-
fallbackMetrics?: Record<string,
|
|
186
|
+
fallbackMetrics?: Record<string, FontFallbackMetrics4>;
|
|
83
187
|
}
|
|
84
188
|
/**
|
|
85
189
|
* Discover all route patterns from an SSR module.
|
|
@@ -128,59 +232,7 @@ declare function stripScriptsFromStaticHTML(html: string): string;
|
|
|
128
232
|
* - Routes without `prerender` or `generateParams` are not collected
|
|
129
233
|
*/
|
|
130
234
|
declare function collectPrerenderPaths(routes: CompiledRoute2[], prefix?: string): Promise<string[]>;
|
|
131
|
-
import { FontFallbackMetrics as
|
|
132
|
-
import { ExtractedQuery } from "@vertz/ui-compiler";
|
|
133
|
-
/**
|
|
134
|
-
* SSR prefetch access rule evaluator.
|
|
135
|
-
*
|
|
136
|
-
* Evaluates serialized entity access rules against the current session
|
|
137
|
-
* to determine whether a query should be prefetched during SSR.
|
|
138
|
-
*
|
|
139
|
-
* The serialized rules come from the prefetch manifest (generated at build time).
|
|
140
|
-
* The session comes from the JWT decoded at request time.
|
|
141
|
-
*/
|
|
142
|
-
/**
|
|
143
|
-
* Serialized access rule — the JSON-friendly format stored in the manifest.
|
|
144
|
-
* Mirrors SerializedRule from @vertz/server/auth/rules but defined here
|
|
145
|
-
* to avoid importing the server package into the SSR pipeline.
|
|
146
|
-
*/
|
|
147
|
-
type SerializedAccessRule = {
|
|
148
|
-
type: "public";
|
|
149
|
-
} | {
|
|
150
|
-
type: "authenticated";
|
|
151
|
-
} | {
|
|
152
|
-
type: "role";
|
|
153
|
-
roles: string[];
|
|
154
|
-
} | {
|
|
155
|
-
type: "entitlement";
|
|
156
|
-
value: string;
|
|
157
|
-
} | {
|
|
158
|
-
type: "where";
|
|
159
|
-
conditions: Record<string, unknown>;
|
|
160
|
-
} | {
|
|
161
|
-
type: "all";
|
|
162
|
-
rules: SerializedAccessRule[];
|
|
163
|
-
} | {
|
|
164
|
-
type: "any";
|
|
165
|
-
rules: SerializedAccessRule[];
|
|
166
|
-
} | {
|
|
167
|
-
type: "fva";
|
|
168
|
-
maxAge: number;
|
|
169
|
-
} | {
|
|
170
|
-
type: "deny";
|
|
171
|
-
};
|
|
172
|
-
/** Serialized entity access rules from the prefetch manifest. */
|
|
173
|
-
type EntityAccessMap = Record<string, Partial<Record<string, SerializedAccessRule>>>;
|
|
174
|
-
interface SSRPrefetchManifest {
|
|
175
|
-
/** Route patterns present in the manifest. */
|
|
176
|
-
routePatterns: string[];
|
|
177
|
-
/** Entity access rules keyed by entity name → operation → serialized rule. */
|
|
178
|
-
entityAccess?: EntityAccessMap;
|
|
179
|
-
/** Route entries with query binding metadata for zero-discovery prefetch. */
|
|
180
|
-
routeEntries?: Record<string, {
|
|
181
|
-
queries: ExtractedQuery[];
|
|
182
|
-
}>;
|
|
183
|
-
}
|
|
235
|
+
import { FontFallbackMetrics as FontFallbackMetrics5 } from "@vertz/ui";
|
|
184
236
|
import { AccessSet } from "@vertz/ui/auth";
|
|
185
237
|
interface SessionData {
|
|
186
238
|
user: {
|
|
@@ -234,7 +286,7 @@ interface SSRHandlerOptions {
|
|
|
234
286
|
*/
|
|
235
287
|
nonce?: string;
|
|
236
288
|
/** Pre-computed font fallback metrics (computed at server startup). */
|
|
237
|
-
fallbackMetrics?: Record<string,
|
|
289
|
+
fallbackMetrics?: Record<string, FontFallbackMetrics5>;
|
|
238
290
|
/** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
|
|
239
291
|
modulepreload?: string[];
|
|
240
292
|
/**
|
|
@@ -273,6 +325,16 @@ interface SSRHandlerOptions {
|
|
|
273
325
|
* which always use buffered rendering.
|
|
274
326
|
*/
|
|
275
327
|
progressiveHTML?: boolean;
|
|
328
|
+
/**
|
|
329
|
+
* AOT manifest with pre-compiled render functions.
|
|
330
|
+
*
|
|
331
|
+
* When provided, routes matching AOT entries are rendered via string-builder
|
|
332
|
+
* functions (no DOM shim), bypassing the reactive runtime for 4-6x speedup.
|
|
333
|
+
* Routes not in the manifest fall back to `ssrRenderSinglePass()`.
|
|
334
|
+
*
|
|
335
|
+
* Load via `loadAotManifest(serverDir)` at startup.
|
|
336
|
+
*/
|
|
337
|
+
aotManifest?: AotManifest;
|
|
276
338
|
}
|
|
277
339
|
declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
|
|
278
340
|
interface InjectIntoTemplateOptions {
|
|
@@ -295,4 +357,4 @@ interface InjectIntoTemplateOptions {
|
|
|
295
357
|
* injects CSS before </head>, and ssrData before </body>.
|
|
296
358
|
*/
|
|
297
359
|
declare function injectIntoTemplate(options: InjectIntoTemplateOptions): string;
|
|
298
|
-
export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, collectPrerenderPaths, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions };
|
|
360
|
+
export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, loadAotManifest, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, collectPrerenderPaths, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions };
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createSSRHandler
|
|
3
|
-
|
|
2
|
+
createSSRHandler,
|
|
3
|
+
loadAotManifest
|
|
4
|
+
} from "../shared/chunk-gbcsa7h1.js";
|
|
4
5
|
import {
|
|
5
6
|
injectIntoTemplate,
|
|
6
7
|
ssrDiscoverQueries,
|
|
7
8
|
ssrRenderToString
|
|
8
9
|
} from "../shared/chunk-34fexgex.js";
|
|
9
10
|
import"../shared/chunk-ybftdw1r.js";
|
|
10
|
-
|
|
11
11
|
// src/prerender.ts
|
|
12
12
|
async function discoverRoutes(module) {
|
|
13
13
|
const result = await ssrRenderToString(module, "/");
|
|
@@ -115,6 +115,7 @@ export {
|
|
|
115
115
|
ssrRenderToString,
|
|
116
116
|
ssrDiscoverQueries,
|
|
117
117
|
prerenderRoutes,
|
|
118
|
+
loadAotManifest,
|
|
118
119
|
injectIntoTemplate,
|
|
119
120
|
filterPrerenderableRoutes,
|
|
120
121
|
discoverRoutes,
|