@vertz/ui-server 0.2.29 → 0.2.31
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.d.ts +24 -1
- package/dist/bun-dev-server.js +99 -68
- package/dist/bun-plugin/index.d.ts +2 -2
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +342 -318
- package/dist/index.js +25 -478
- package/dist/node-handler.d.ts +170 -0
- package/dist/node-handler.js +8 -0
- package/dist/shared/chunk-34fexgex.js +1284 -0
- package/dist/shared/chunk-es0406qq.js +227 -0
- package/dist/shared/chunk-wb5fv233.js +216 -0
- package/dist/shared/{chunk-gcwqkynf.js → chunk-ybftdw1r.js} +1 -1
- package/dist/ssr/index.d.ts +75 -2
- package/dist/ssr/index.js +5 -3
- package/package.json +9 -5
- package/dist/shared/chunk-yr65qdge.js +0 -764
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeAttr,
|
|
3
|
+
injectIntoTemplate,
|
|
4
|
+
precomputeHandlerState,
|
|
5
|
+
resolveRouteModulepreload,
|
|
6
|
+
resolveSession,
|
|
7
|
+
safeSerialize,
|
|
8
|
+
ssrRenderProgressive,
|
|
9
|
+
ssrRenderSinglePass,
|
|
10
|
+
ssrStreamNavQueries
|
|
11
|
+
} from "./chunk-34fexgex.js";
|
|
12
|
+
|
|
13
|
+
// src/node-handler.ts
|
|
14
|
+
function createNodeHandler(options) {
|
|
15
|
+
const {
|
|
16
|
+
module,
|
|
17
|
+
ssrTimeout,
|
|
18
|
+
nonce,
|
|
19
|
+
fallbackMetrics,
|
|
20
|
+
routeChunkManifest,
|
|
21
|
+
cacheControl,
|
|
22
|
+
sessionResolver,
|
|
23
|
+
manifest,
|
|
24
|
+
progressiveHTML
|
|
25
|
+
} = options;
|
|
26
|
+
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
27
|
+
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
28
|
+
return (req, res) => {
|
|
29
|
+
(async () => {
|
|
30
|
+
try {
|
|
31
|
+
const url = req.url ?? "/";
|
|
32
|
+
if (req.headers["x-vertz-nav"] === "1") {
|
|
33
|
+
const pathname = url.split("?")[0];
|
|
34
|
+
await handleNavRequest(req, res, module, pathname, ssrTimeout);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let sessionScript = "";
|
|
38
|
+
let ssrAuth;
|
|
39
|
+
if (sessionResolver) {
|
|
40
|
+
const fullUrl = `http://${req.headers.host ?? "localhost"}${url}`;
|
|
41
|
+
const webRequest = new Request(fullUrl, {
|
|
42
|
+
method: req.method ?? "GET",
|
|
43
|
+
headers: req.headers
|
|
44
|
+
});
|
|
45
|
+
const result2 = await resolveSession(webRequest, sessionResolver, nonce);
|
|
46
|
+
sessionScript = result2.sessionScript;
|
|
47
|
+
ssrAuth = result2.ssrAuth;
|
|
48
|
+
}
|
|
49
|
+
if (useProgressive) {
|
|
50
|
+
await handleProgressiveRequest(req, res, module, splitResult, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const result = await ssrRenderSinglePass(module, url, {
|
|
54
|
+
ssrTimeout,
|
|
55
|
+
fallbackMetrics,
|
|
56
|
+
ssrAuth,
|
|
57
|
+
manifest
|
|
58
|
+
});
|
|
59
|
+
if (result.redirect) {
|
|
60
|
+
res.writeHead(302, { Location: result.redirect.to });
|
|
61
|
+
res.end();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const resolvedModulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, modulepreloadTags);
|
|
65
|
+
const allHeadTags = [result.headTags, resolvedModulepreloadTags].filter(Boolean).join(`
|
|
66
|
+
`);
|
|
67
|
+
const html = injectIntoTemplate({
|
|
68
|
+
template,
|
|
69
|
+
appHtml: result.html,
|
|
70
|
+
appCss: result.css,
|
|
71
|
+
ssrData: result.ssrData,
|
|
72
|
+
nonce,
|
|
73
|
+
headTags: allHeadTags || undefined,
|
|
74
|
+
sessionScript
|
|
75
|
+
});
|
|
76
|
+
const headers = {
|
|
77
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
78
|
+
};
|
|
79
|
+
if (linkHeader)
|
|
80
|
+
headers.Link = linkHeader;
|
|
81
|
+
if (cacheControl)
|
|
82
|
+
headers["Cache-Control"] = cacheControl;
|
|
83
|
+
res.writeHead(200, headers);
|
|
84
|
+
res.end(html);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
87
|
+
if (!res.headersSent) {
|
|
88
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
89
|
+
}
|
|
90
|
+
res.end("Internal Server Error");
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function handleProgressiveRequest(req, res, module, split, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
|
|
96
|
+
const result = await ssrRenderProgressive(module, url, {
|
|
97
|
+
ssrTimeout,
|
|
98
|
+
fallbackMetrics,
|
|
99
|
+
ssrAuth,
|
|
100
|
+
manifest
|
|
101
|
+
});
|
|
102
|
+
if (result.redirect) {
|
|
103
|
+
res.writeHead(302, { Location: result.redirect.to });
|
|
104
|
+
res.end();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const resolvedModulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
|
|
108
|
+
let headChunk = split.headTemplate;
|
|
109
|
+
const headCloseIdx = headChunk.lastIndexOf("</head>");
|
|
110
|
+
if (headCloseIdx !== -1) {
|
|
111
|
+
const injections = [];
|
|
112
|
+
if (result.css)
|
|
113
|
+
injections.push(result.css);
|
|
114
|
+
if (result.headTags)
|
|
115
|
+
injections.push(result.headTags);
|
|
116
|
+
if (resolvedModulepreloadTags)
|
|
117
|
+
injections.push(resolvedModulepreloadTags);
|
|
118
|
+
if (sessionScript)
|
|
119
|
+
injections.push(sessionScript);
|
|
120
|
+
if (injections.length > 0) {
|
|
121
|
+
headChunk = headChunk.slice(0, headCloseIdx) + injections.join(`
|
|
122
|
+
`) + `
|
|
123
|
+
` + headChunk.slice(headCloseIdx);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
if (result.css)
|
|
127
|
+
headChunk += result.css;
|
|
128
|
+
if (result.headTags)
|
|
129
|
+
headChunk += result.headTags;
|
|
130
|
+
if (resolvedModulepreloadTags)
|
|
131
|
+
headChunk += resolvedModulepreloadTags;
|
|
132
|
+
if (sessionScript)
|
|
133
|
+
headChunk += sessionScript;
|
|
134
|
+
}
|
|
135
|
+
const headers = {
|
|
136
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
137
|
+
};
|
|
138
|
+
if (linkHeader)
|
|
139
|
+
headers.Link = linkHeader;
|
|
140
|
+
if (cacheControl)
|
|
141
|
+
headers["Cache-Control"] = cacheControl;
|
|
142
|
+
res.writeHead(200, headers);
|
|
143
|
+
res.write(headChunk);
|
|
144
|
+
let clientDisconnected = false;
|
|
145
|
+
req.on("close", () => {
|
|
146
|
+
clientDisconnected = true;
|
|
147
|
+
});
|
|
148
|
+
if (result.renderStream) {
|
|
149
|
+
const reader = result.renderStream.getReader();
|
|
150
|
+
let renderError;
|
|
151
|
+
try {
|
|
152
|
+
for (;; ) {
|
|
153
|
+
if (clientDisconnected) {
|
|
154
|
+
reader.cancel();
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
const { done, value } = await reader.read();
|
|
158
|
+
if (done)
|
|
159
|
+
break;
|
|
160
|
+
const canContinue = res.write(value);
|
|
161
|
+
if (!canContinue && !clientDisconnected) {
|
|
162
|
+
await new Promise((resolve) => res.once("drain", resolve));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
renderError = err instanceof Error ? err : new Error(String(err));
|
|
167
|
+
} finally {
|
|
168
|
+
reader.releaseLock();
|
|
169
|
+
}
|
|
170
|
+
if (renderError && !clientDisconnected) {
|
|
171
|
+
console.error("[SSR] Render error after head sent:", renderError.message);
|
|
172
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
173
|
+
const errorScript = `<script${nonceAttr}>document.dispatchEvent(new CustomEvent('vertz:ssr-error',` + `{detail:{message:${safeSerialize(renderError.message)}}}))</script>`;
|
|
174
|
+
res.write(errorScript);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (clientDisconnected)
|
|
178
|
+
return;
|
|
179
|
+
let tail = "";
|
|
180
|
+
if (result.ssrData.length > 0) {
|
|
181
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
182
|
+
tail += `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(result.ssrData)};</script>`;
|
|
183
|
+
}
|
|
184
|
+
tail += split.tailTemplate;
|
|
185
|
+
res.end(tail);
|
|
186
|
+
}
|
|
187
|
+
async function handleNavRequest(req, res, module, url, ssrTimeout) {
|
|
188
|
+
res.writeHead(200, {
|
|
189
|
+
"Content-Type": "text/event-stream",
|
|
190
|
+
"Cache-Control": "no-cache"
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
|
|
194
|
+
let clientDisconnected = false;
|
|
195
|
+
req.on("close", () => {
|
|
196
|
+
clientDisconnected = true;
|
|
197
|
+
});
|
|
198
|
+
const reader = stream.getReader();
|
|
199
|
+
try {
|
|
200
|
+
for (;; ) {
|
|
201
|
+
if (clientDisconnected) {
|
|
202
|
+
reader.cancel();
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
const { done, value } = await reader.read();
|
|
206
|
+
if (done)
|
|
207
|
+
break;
|
|
208
|
+
const canContinue = res.write(value);
|
|
209
|
+
if (!canContinue && !clientDisconnected) {
|
|
210
|
+
await new Promise((resolve) => res.once("drain", resolve));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
reader.releaseLock();
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
res.write(`event: done
|
|
218
|
+
data: {}
|
|
219
|
+
|
|
220
|
+
`);
|
|
221
|
+
}
|
|
222
|
+
if (!res.writableEnded) {
|
|
223
|
+
res.end();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { createNodeHandler };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeChunk,
|
|
3
|
+
escapeAttr,
|
|
4
|
+
injectIntoTemplate,
|
|
5
|
+
precomputeHandlerState,
|
|
6
|
+
resolveRouteModulepreload,
|
|
7
|
+
resolveSession,
|
|
8
|
+
safeSerialize,
|
|
9
|
+
ssrRenderProgressive,
|
|
10
|
+
ssrRenderSinglePass,
|
|
11
|
+
ssrStreamNavQueries
|
|
12
|
+
} from "./chunk-34fexgex.js";
|
|
13
|
+
|
|
14
|
+
// src/ssr-progressive-response.ts
|
|
15
|
+
function buildProgressiveResponse(options) {
|
|
16
|
+
const { headChunk, renderStream, tailChunk, ssrData, nonce, headers } = options;
|
|
17
|
+
const stream = new ReadableStream({
|
|
18
|
+
async start(controller) {
|
|
19
|
+
controller.enqueue(encodeChunk(headChunk));
|
|
20
|
+
const reader = renderStream.getReader();
|
|
21
|
+
let renderError;
|
|
22
|
+
try {
|
|
23
|
+
for (;; ) {
|
|
24
|
+
const { done, value } = await reader.read();
|
|
25
|
+
if (done)
|
|
26
|
+
break;
|
|
27
|
+
controller.enqueue(value);
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
renderError = err instanceof Error ? err : new Error(String(err));
|
|
31
|
+
}
|
|
32
|
+
if (renderError) {
|
|
33
|
+
console.error("[SSR] Render error after head sent:", renderError.message);
|
|
34
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
35
|
+
const errorScript = `<script${nonceAttr}>document.dispatchEvent(new CustomEvent('vertz:ssr-error',` + `{detail:{message:${safeSerialize(renderError.message)}}}))</script>`;
|
|
36
|
+
controller.enqueue(encodeChunk(errorScript));
|
|
37
|
+
}
|
|
38
|
+
let tail = "";
|
|
39
|
+
if (ssrData.length > 0) {
|
|
40
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
41
|
+
tail += `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>`;
|
|
42
|
+
}
|
|
43
|
+
tail += tailChunk;
|
|
44
|
+
controller.enqueue(encodeChunk(tail));
|
|
45
|
+
controller.close();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const responseHeaders = {
|
|
49
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
50
|
+
...headers
|
|
51
|
+
};
|
|
52
|
+
return new Response(stream, { status: 200, headers: responseHeaders });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/ssr-handler.ts
|
|
56
|
+
function createSSRHandler(options) {
|
|
57
|
+
const {
|
|
58
|
+
module,
|
|
59
|
+
ssrTimeout,
|
|
60
|
+
nonce,
|
|
61
|
+
fallbackMetrics,
|
|
62
|
+
routeChunkManifest,
|
|
63
|
+
cacheControl,
|
|
64
|
+
sessionResolver,
|
|
65
|
+
manifest,
|
|
66
|
+
progressiveHTML
|
|
67
|
+
} = options;
|
|
68
|
+
const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
|
|
69
|
+
return async (request) => {
|
|
70
|
+
const url = new URL(request.url);
|
|
71
|
+
const pathname = url.pathname;
|
|
72
|
+
if (request.headers.get("x-vertz-nav") === "1") {
|
|
73
|
+
return handleNavRequest(module, pathname, ssrTimeout);
|
|
74
|
+
}
|
|
75
|
+
let sessionScript = "";
|
|
76
|
+
let ssrAuth;
|
|
77
|
+
if (sessionResolver) {
|
|
78
|
+
const result = await resolveSession(request, sessionResolver, nonce);
|
|
79
|
+
sessionScript = result.sessionScript;
|
|
80
|
+
ssrAuth = result.ssrAuth;
|
|
81
|
+
}
|
|
82
|
+
const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
|
|
83
|
+
if (useProgressive) {
|
|
84
|
+
return handleProgressiveHTMLRequest(module, splitResult, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
|
|
85
|
+
}
|
|
86
|
+
return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function handleNavRequest(module, url, ssrTimeout) {
|
|
90
|
+
try {
|
|
91
|
+
const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
|
|
92
|
+
return new Response(stream, {
|
|
93
|
+
status: 200,
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "text/event-stream",
|
|
96
|
+
"Cache-Control": "no-cache"
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} catch {
|
|
100
|
+
return new Response(`event: done
|
|
101
|
+
data: {}
|
|
102
|
+
|
|
103
|
+
`, {
|
|
104
|
+
status: 200,
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "text/event-stream",
|
|
107
|
+
"Cache-Control": "no-cache"
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
|
|
113
|
+
try {
|
|
114
|
+
const result = await ssrRenderProgressive(module, url, {
|
|
115
|
+
ssrTimeout,
|
|
116
|
+
fallbackMetrics,
|
|
117
|
+
ssrAuth,
|
|
118
|
+
manifest
|
|
119
|
+
});
|
|
120
|
+
if (result.redirect) {
|
|
121
|
+
return new Response(null, {
|
|
122
|
+
status: 302,
|
|
123
|
+
headers: { Location: result.redirect.to }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
|
|
127
|
+
let headChunk = split.headTemplate;
|
|
128
|
+
const headCloseIdx = headChunk.lastIndexOf("</head>");
|
|
129
|
+
if (headCloseIdx !== -1) {
|
|
130
|
+
const injections = [];
|
|
131
|
+
if (result.css)
|
|
132
|
+
injections.push(result.css);
|
|
133
|
+
if (result.headTags)
|
|
134
|
+
injections.push(result.headTags);
|
|
135
|
+
if (modulepreloadTags)
|
|
136
|
+
injections.push(modulepreloadTags);
|
|
137
|
+
if (sessionScript)
|
|
138
|
+
injections.push(sessionScript);
|
|
139
|
+
if (injections.length > 0) {
|
|
140
|
+
headChunk = headChunk.slice(0, headCloseIdx) + injections.join(`
|
|
141
|
+
`) + `
|
|
142
|
+
` + headChunk.slice(headCloseIdx);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
if (result.css)
|
|
146
|
+
headChunk += result.css;
|
|
147
|
+
if (result.headTags)
|
|
148
|
+
headChunk += result.headTags;
|
|
149
|
+
if (modulepreloadTags)
|
|
150
|
+
headChunk += modulepreloadTags;
|
|
151
|
+
if (sessionScript)
|
|
152
|
+
headChunk += sessionScript;
|
|
153
|
+
}
|
|
154
|
+
const headers = {};
|
|
155
|
+
if (linkHeader)
|
|
156
|
+
headers.Link = linkHeader;
|
|
157
|
+
if (cacheControl)
|
|
158
|
+
headers["Cache-Control"] = cacheControl;
|
|
159
|
+
return buildProgressiveResponse({
|
|
160
|
+
headChunk,
|
|
161
|
+
renderStream: result.renderStream,
|
|
162
|
+
tailChunk: split.tailTemplate,
|
|
163
|
+
ssrData: result.ssrData,
|
|
164
|
+
nonce,
|
|
165
|
+
headers
|
|
166
|
+
});
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
169
|
+
return new Response("Internal Server Error", {
|
|
170
|
+
status: 500,
|
|
171
|
+
headers: { "Content-Type": "text/plain" }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await ssrRenderSinglePass(module, url, {
|
|
178
|
+
ssrTimeout,
|
|
179
|
+
fallbackMetrics,
|
|
180
|
+
ssrAuth,
|
|
181
|
+
manifest
|
|
182
|
+
});
|
|
183
|
+
if (result.redirect) {
|
|
184
|
+
return new Response(null, {
|
|
185
|
+
status: 302,
|
|
186
|
+
headers: { Location: result.redirect.to }
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
|
|
190
|
+
const allHeadTags = [result.headTags, modulepreloadTags].filter(Boolean).join(`
|
|
191
|
+
`);
|
|
192
|
+
const html = injectIntoTemplate({
|
|
193
|
+
template,
|
|
194
|
+
appHtml: result.html,
|
|
195
|
+
appCss: result.css,
|
|
196
|
+
ssrData: result.ssrData,
|
|
197
|
+
nonce,
|
|
198
|
+
headTags: allHeadTags || undefined,
|
|
199
|
+
sessionScript
|
|
200
|
+
});
|
|
201
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
202
|
+
if (linkHeader)
|
|
203
|
+
headers.Link = linkHeader;
|
|
204
|
+
if (cacheControl)
|
|
205
|
+
headers["Cache-Control"] = cacheControl;
|
|
206
|
+
return new Response(html, { status: 200, headers });
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
|
|
209
|
+
return new Response("Internal Server Error", {
|
|
210
|
+
status: 500,
|
|
211
|
+
headers: { "Content-Type": "text/plain" }
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { createSSRHandler };
|
|
@@ -707,4 +707,4 @@ function toVNode(element) {
|
|
|
707
707
|
return { tag: "span", attrs: {}, children: [String(element)] };
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
-
export {
|
|
710
|
+
export { SSRNode, SSRComment, rawHtml, SSRTextNode, SSRDocumentFragment, SSRElement, createSSRAdapter, ssrStorage, isInSSR, getSSRUrl, registerSSRQuery, getSSRQueries, setGlobalSSRTimeout, clearGlobalSSRTimeout, getGlobalSSRTimeout, installDomShim, removeDomShim, toVNode };
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -128,7 +128,59 @@ declare function stripScriptsFromStaticHTML(html: string): string;
|
|
|
128
128
|
* - Routes without `prerender` or `generateParams` are not collected
|
|
129
129
|
*/
|
|
130
130
|
declare function collectPrerenderPaths(routes: CompiledRoute2[], prefix?: string): Promise<string[]>;
|
|
131
|
-
import { FontFallbackMetrics as
|
|
131
|
+
import { FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
|
|
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
|
+
}
|
|
132
184
|
import { AccessSet } from "@vertz/ui/auth";
|
|
133
185
|
interface SessionData {
|
|
134
186
|
user: {
|
|
@@ -182,7 +234,7 @@ interface SSRHandlerOptions {
|
|
|
182
234
|
*/
|
|
183
235
|
nonce?: string;
|
|
184
236
|
/** Pre-computed font fallback metrics (computed at server startup). */
|
|
185
|
-
fallbackMetrics?: Record<string,
|
|
237
|
+
fallbackMetrics?: Record<string, FontFallbackMetrics4>;
|
|
186
238
|
/** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
|
|
187
239
|
modulepreload?: string[];
|
|
188
240
|
/**
|
|
@@ -200,6 +252,27 @@ interface SSRHandlerOptions {
|
|
|
200
252
|
* optionally `window.__VERTZ_ACCESS_SET__` for instant auth hydration.
|
|
201
253
|
*/
|
|
202
254
|
sessionResolver?: SessionResolver;
|
|
255
|
+
/**
|
|
256
|
+
* Prefetch manifest for single-pass SSR optimization.
|
|
257
|
+
*
|
|
258
|
+
* When provided with route entries and an API client export, enables
|
|
259
|
+
* zero-discovery rendering — queries are prefetched from the manifest
|
|
260
|
+
* without executing the component tree, then a single render pass
|
|
261
|
+
* produces the HTML. Without a manifest, SSR still uses the single-pass
|
|
262
|
+
* discovery-then-render approach (cheaper than two-pass).
|
|
263
|
+
*/
|
|
264
|
+
manifest?: SSRPrefetchManifest;
|
|
265
|
+
/**
|
|
266
|
+
* Enable progressive HTML streaming. Default: false.
|
|
267
|
+
*
|
|
268
|
+
* When true, the Response body is a ReadableStream that sends `<head>`
|
|
269
|
+
* content (CSS, preloads, fonts) before `<body>` rendering is complete.
|
|
270
|
+
* This improves TTFB and FCP.
|
|
271
|
+
*
|
|
272
|
+
* Has no effect on zero-discovery routes (manifest with routeEntries),
|
|
273
|
+
* which always use buffered rendering.
|
|
274
|
+
*/
|
|
275
|
+
progressiveHTML?: boolean;
|
|
203
276
|
}
|
|
204
277
|
declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
|
|
205
278
|
interface InjectIntoTemplateOptions {
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createSSRHandler
|
|
2
|
+
createSSRHandler
|
|
3
|
+
} from "../shared/chunk-wb5fv233.js";
|
|
4
|
+
import {
|
|
3
5
|
injectIntoTemplate,
|
|
4
6
|
ssrDiscoverQueries,
|
|
5
7
|
ssrRenderToString
|
|
6
|
-
} from "../shared/chunk-
|
|
7
|
-
import"../shared/chunk-
|
|
8
|
+
} from "../shared/chunk-34fexgex.js";
|
|
9
|
+
import"../shared/chunk-ybftdw1r.js";
|
|
8
10
|
|
|
9
11
|
// src/prerender.ts
|
|
10
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.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Vertz UI server-side rendering runtime",
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"./fetch-scope": {
|
|
44
44
|
"import": "./dist/fetch-scope.js",
|
|
45
45
|
"types": "./dist/fetch-scope.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./node": {
|
|
48
|
+
"import": "./dist/node-handler.js",
|
|
49
|
+
"types": "./dist/node-handler.d.ts"
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
"files": [
|
|
@@ -58,15 +62,15 @@
|
|
|
58
62
|
"@ampproject/remapping": "^2.3.0",
|
|
59
63
|
"@capsizecss/unpack": "^4.0.0",
|
|
60
64
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
61
|
-
"@vertz/core": "^0.2.
|
|
62
|
-
"@vertz/ui": "^0.2.
|
|
63
|
-
"@vertz/ui-compiler": "^0.2.
|
|
65
|
+
"@vertz/core": "^0.2.30",
|
|
66
|
+
"@vertz/ui": "^0.2.30",
|
|
67
|
+
"@vertz/ui-compiler": "^0.2.30",
|
|
64
68
|
"magic-string": "^0.30.0",
|
|
65
69
|
"sharp": "^0.34.5",
|
|
66
70
|
"ts-morph": "^27.0.2"
|
|
67
71
|
},
|
|
68
72
|
"devDependencies": {
|
|
69
|
-
"@vertz/codegen": "^0.2.
|
|
73
|
+
"@vertz/codegen": "^0.2.30",
|
|
70
74
|
"@vertz/ui-auth": "^0.2.19",
|
|
71
75
|
"bun-types": "^1.3.10",
|
|
72
76
|
"bunup": "^0.16.31",
|