@vertz/ui-server 0.2.30 → 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/dom-shim/index.js +1 -1
- package/dist/index.d.ts +818 -747
- package/dist/index.js +86 -150
- package/dist/node-handler.d.ts +223 -0
- package/dist/node-handler.js +8 -0
- package/dist/shared/{chunk-bd0sgykf.js → chunk-34fexgex.js} +247 -431
- package/dist/shared/chunk-es0406qq.js +227 -0
- package/dist/shared/chunk-gbcsa7h1.js +471 -0
- package/dist/shared/{chunk-gcwqkynf.js → chunk-ybftdw1r.js} +1 -1
- package/dist/ssr/index.d.ts +119 -57
- package/dist/ssr/index.js +6 -3
- package/package.json +9 -5
|
@@ -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,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 };
|
|
@@ -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 };
|