@vertz/ui-server 0.2.14 → 0.2.16
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 +44 -2
- package/dist/bun-dev-server.js +361 -36
- package/dist/bun-plugin/index.d.ts +73 -1
- package/dist/bun-plugin/index.js +964 -17
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +84 -12
- package/dist/index.js +127 -41
- package/dist/shared/{chunk-98972e43.js → chunk-969qgkdf.js} +186 -23
- package/dist/shared/{chunk-n1arq9xq.js → chunk-9jjdzz8c.js} +2 -2
- package/dist/shared/chunk-gggnhyqj.js +57 -0
- package/dist/ssr/index.d.ts +116 -9
- package/dist/ssr/index.js +79 -2
- package/package.json +8 -5
package/dist/bun-dev-server.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
imageContentType,
|
|
4
|
+
isValidImageName
|
|
5
|
+
} from "./shared/chunk-gggnhyqj.js";
|
|
2
6
|
import {
|
|
3
7
|
__require
|
|
4
8
|
} from "./shared/chunk-eb80r8e8.js";
|
|
@@ -40,6 +44,53 @@ function createDebugLogger(logDir) {
|
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
// src/dev-image-proxy.ts
|
|
48
|
+
function jsonError(error, status) {
|
|
49
|
+
return new Response(JSON.stringify({ error, status }), {
|
|
50
|
+
status,
|
|
51
|
+
headers: { "Content-Type": "application/json" }
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
55
|
+
async function handleDevImageProxy(request) {
|
|
56
|
+
const url = new URL(request.url);
|
|
57
|
+
const sourceUrl = url.searchParams.get("url");
|
|
58
|
+
if (!sourceUrl) {
|
|
59
|
+
return jsonError('Missing required "url" parameter', 400);
|
|
60
|
+
}
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = new URL(sourceUrl);
|
|
64
|
+
} catch {
|
|
65
|
+
return jsonError('Invalid "url" parameter', 400);
|
|
66
|
+
}
|
|
67
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
68
|
+
return jsonError("Only HTTP(S) URLs are allowed", 400);
|
|
69
|
+
}
|
|
70
|
+
let response;
|
|
71
|
+
try {
|
|
72
|
+
response = await fetch(sourceUrl, {
|
|
73
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
77
|
+
return jsonError("Image fetch timed out", 504);
|
|
78
|
+
}
|
|
79
|
+
return jsonError("Failed to fetch image", 502);
|
|
80
|
+
}
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
return jsonError(`Upstream returned status ${response.status}`, 502);
|
|
83
|
+
}
|
|
84
|
+
const contentType = response.headers.get("Content-Type") ?? "application/octet-stream";
|
|
85
|
+
return new Response(response.body, {
|
|
86
|
+
status: 200,
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": contentType,
|
|
89
|
+
"Cache-Control": "no-cache"
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
// src/diagnostics-collector.ts
|
|
44
95
|
class DiagnosticsCollector {
|
|
45
96
|
startTime = Date.now();
|
|
@@ -59,6 +110,10 @@ class DiagnosticsCollector {
|
|
|
59
110
|
manifestFileCount = 0;
|
|
60
111
|
manifestDurationMs = 0;
|
|
61
112
|
manifestWarnings = [];
|
|
113
|
+
manifestHmrUpdateCount = 0;
|
|
114
|
+
manifestLastHmrUpdate = null;
|
|
115
|
+
manifestLastHmrFile = null;
|
|
116
|
+
manifestLastHmrChanged = null;
|
|
62
117
|
errorCurrent = null;
|
|
63
118
|
errorLastCategory = null;
|
|
64
119
|
errorLastMessage = null;
|
|
@@ -67,6 +122,10 @@ class DiagnosticsCollector {
|
|
|
67
122
|
watcherLastChangeTime = null;
|
|
68
123
|
static MAX_RUNTIME_ERRORS = 10;
|
|
69
124
|
runtimeErrorsBuffer = [];
|
|
125
|
+
fieldSelectionManifestFileCount = 0;
|
|
126
|
+
fieldSelectionEntries = new Map;
|
|
127
|
+
static MAX_FIELD_MISSES = 50;
|
|
128
|
+
fieldMissesBuffer = [];
|
|
70
129
|
recordPluginConfig(filter, hmr, fastRefresh) {
|
|
71
130
|
this.pluginFilter = filter;
|
|
72
131
|
this.pluginHmr = hmr;
|
|
@@ -94,6 +153,12 @@ class DiagnosticsCollector {
|
|
|
94
153
|
this.manifestDurationMs = durationMs;
|
|
95
154
|
this.manifestWarnings = warnings;
|
|
96
155
|
}
|
|
156
|
+
recordManifestUpdate(file, changed, _durationMs) {
|
|
157
|
+
this.manifestHmrUpdateCount++;
|
|
158
|
+
this.manifestLastHmrUpdate = new Date().toISOString();
|
|
159
|
+
this.manifestLastHmrFile = file;
|
|
160
|
+
this.manifestLastHmrChanged = changed;
|
|
161
|
+
}
|
|
97
162
|
recordHMRAssets(bundledScriptUrl, bootstrapDiscovered) {
|
|
98
163
|
this.hmrBundledScriptUrl = bundledScriptUrl;
|
|
99
164
|
this.hmrBootstrapDiscovered = bootstrapDiscovered;
|
|
@@ -126,6 +191,24 @@ class DiagnosticsCollector {
|
|
|
126
191
|
clearRuntimeErrors() {
|
|
127
192
|
this.runtimeErrorsBuffer = [];
|
|
128
193
|
}
|
|
194
|
+
recordFieldSelectionManifest(fileCount) {
|
|
195
|
+
this.fieldSelectionManifestFileCount = fileCount;
|
|
196
|
+
}
|
|
197
|
+
recordFieldSelection(file, entry) {
|
|
198
|
+
this.fieldSelectionEntries.set(file, entry);
|
|
199
|
+
}
|
|
200
|
+
recordFieldMiss(type, id, field, querySource) {
|
|
201
|
+
this.fieldMissesBuffer.push({
|
|
202
|
+
type,
|
|
203
|
+
id,
|
|
204
|
+
field,
|
|
205
|
+
querySource,
|
|
206
|
+
timestamp: new Date().toISOString()
|
|
207
|
+
});
|
|
208
|
+
if (this.fieldMissesBuffer.length > DiagnosticsCollector.MAX_FIELD_MISSES) {
|
|
209
|
+
this.fieldMissesBuffer = this.fieldMissesBuffer.slice(this.fieldMissesBuffer.length - DiagnosticsCollector.MAX_FIELD_MISSES);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
129
212
|
getSnapshot() {
|
|
130
213
|
return {
|
|
131
214
|
status: "ok",
|
|
@@ -152,7 +235,11 @@ class DiagnosticsCollector {
|
|
|
152
235
|
manifest: {
|
|
153
236
|
fileCount: this.manifestFileCount,
|
|
154
237
|
durationMs: this.manifestDurationMs,
|
|
155
|
-
warnings: [...this.manifestWarnings]
|
|
238
|
+
warnings: [...this.manifestWarnings],
|
|
239
|
+
hmrUpdateCount: this.manifestHmrUpdateCount,
|
|
240
|
+
lastHmrUpdate: this.manifestLastHmrUpdate,
|
|
241
|
+
lastHmrFile: this.manifestLastHmrFile,
|
|
242
|
+
lastHmrChanged: this.manifestLastHmrChanged
|
|
156
243
|
},
|
|
157
244
|
errors: {
|
|
158
245
|
current: this.errorCurrent,
|
|
@@ -166,7 +253,12 @@ class DiagnosticsCollector {
|
|
|
166
253
|
lastChangedFile: this.watcherLastChangedFile,
|
|
167
254
|
lastChangeTime: this.watcherLastChangeTime
|
|
168
255
|
},
|
|
169
|
-
runtimeErrors: [...this.runtimeErrorsBuffer]
|
|
256
|
+
runtimeErrors: [...this.runtimeErrorsBuffer],
|
|
257
|
+
fieldSelection: {
|
|
258
|
+
manifestFileCount: this.fieldSelectionManifestFileCount,
|
|
259
|
+
entries: Object.fromEntries(this.fieldSelectionEntries),
|
|
260
|
+
misses: [...this.fieldMissesBuffer]
|
|
261
|
+
}
|
|
170
262
|
};
|
|
171
263
|
}
|
|
172
264
|
}
|
|
@@ -193,6 +285,109 @@ function runWithScopedFetch(interceptor, fn) {
|
|
|
193
285
|
return fetchScope.run(interceptor, fn);
|
|
194
286
|
}
|
|
195
287
|
|
|
288
|
+
// src/font-metrics.ts
|
|
289
|
+
import { readFile } from "fs/promises";
|
|
290
|
+
import { join as join2 } from "path";
|
|
291
|
+
import { fromBuffer } from "@capsizecss/unpack";
|
|
292
|
+
var SYSTEM_FONT_METRICS = {
|
|
293
|
+
Arial: {
|
|
294
|
+
ascent: 1854,
|
|
295
|
+
descent: -434,
|
|
296
|
+
lineGap: 67,
|
|
297
|
+
unitsPerEm: 2048,
|
|
298
|
+
xWidthAvg: 904
|
|
299
|
+
},
|
|
300
|
+
"Times New Roman": {
|
|
301
|
+
ascent: 1825,
|
|
302
|
+
descent: -443,
|
|
303
|
+
lineGap: 87,
|
|
304
|
+
unitsPerEm: 2048,
|
|
305
|
+
xWidthAvg: 819
|
|
306
|
+
},
|
|
307
|
+
"Courier New": {
|
|
308
|
+
ascent: 1705,
|
|
309
|
+
descent: -615,
|
|
310
|
+
lineGap: 0,
|
|
311
|
+
unitsPerEm: 2048,
|
|
312
|
+
xWidthAvg: 1229
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
function detectFallbackFont(fallback) {
|
|
316
|
+
for (const f of fallback) {
|
|
317
|
+
const lower = f.toLowerCase();
|
|
318
|
+
if (lower === "sans-serif" || lower === "system-ui")
|
|
319
|
+
return "Arial";
|
|
320
|
+
if (lower === "serif")
|
|
321
|
+
return "Times New Roman";
|
|
322
|
+
if (lower === "monospace")
|
|
323
|
+
return "Courier New";
|
|
324
|
+
}
|
|
325
|
+
return "Arial";
|
|
326
|
+
}
|
|
327
|
+
function formatPercent(value) {
|
|
328
|
+
return `${(value * 100).toFixed(2)}%`;
|
|
329
|
+
}
|
|
330
|
+
function computeFallbackMetrics(fontMetrics, fallbackFont) {
|
|
331
|
+
const systemMetrics = SYSTEM_FONT_METRICS[fallbackFont];
|
|
332
|
+
const fontNormalizedWidth = fontMetrics.xWidthAvg / fontMetrics.unitsPerEm;
|
|
333
|
+
const systemNormalizedWidth = systemMetrics.xWidthAvg / systemMetrics.unitsPerEm;
|
|
334
|
+
const sizeAdjust = fontNormalizedWidth / systemNormalizedWidth;
|
|
335
|
+
const ascentOverride = fontMetrics.ascent / (fontMetrics.unitsPerEm * sizeAdjust);
|
|
336
|
+
const descentOverride = Math.abs(fontMetrics.descent) / (fontMetrics.unitsPerEm * sizeAdjust);
|
|
337
|
+
const lineGapOverride = fontMetrics.lineGap / (fontMetrics.unitsPerEm * sizeAdjust);
|
|
338
|
+
return {
|
|
339
|
+
ascentOverride: formatPercent(ascentOverride),
|
|
340
|
+
descentOverride: formatPercent(descentOverride),
|
|
341
|
+
lineGapOverride: formatPercent(lineGapOverride),
|
|
342
|
+
sizeAdjust: formatPercent(sizeAdjust),
|
|
343
|
+
fallbackFont
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function getPrimarySrcPath(descriptor) {
|
|
347
|
+
const { src } = descriptor;
|
|
348
|
+
if (!src)
|
|
349
|
+
return null;
|
|
350
|
+
if (typeof src === "string")
|
|
351
|
+
return src;
|
|
352
|
+
const first = src[0];
|
|
353
|
+
if (first)
|
|
354
|
+
return first.path;
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
function resolveFilePath(urlPath, rootDir) {
|
|
358
|
+
const cleaned = urlPath.startsWith("/") ? urlPath.slice(1) : urlPath;
|
|
359
|
+
return join2(rootDir, cleaned);
|
|
360
|
+
}
|
|
361
|
+
async function extractFontMetrics(fonts, rootDir) {
|
|
362
|
+
const result = {};
|
|
363
|
+
for (const [key, descriptor] of Object.entries(fonts)) {
|
|
364
|
+
const adjustFontFallback = descriptor.adjustFontFallback ?? true;
|
|
365
|
+
if (adjustFontFallback === false)
|
|
366
|
+
continue;
|
|
367
|
+
const srcPath = getPrimarySrcPath(descriptor);
|
|
368
|
+
if (!srcPath)
|
|
369
|
+
continue;
|
|
370
|
+
if (!srcPath.toLowerCase().endsWith(".woff2"))
|
|
371
|
+
continue;
|
|
372
|
+
try {
|
|
373
|
+
const filePath = resolveFilePath(srcPath, rootDir);
|
|
374
|
+
const buffer = await readFile(filePath);
|
|
375
|
+
const metrics = await fromBuffer(buffer);
|
|
376
|
+
const fallbackFont = typeof adjustFontFallback === "string" ? adjustFontFallback : detectFallbackFont(descriptor.fallback);
|
|
377
|
+
result[key] = computeFallbackMetrics({
|
|
378
|
+
ascent: metrics.ascent,
|
|
379
|
+
descent: metrics.descent,
|
|
380
|
+
lineGap: metrics.lineGap,
|
|
381
|
+
unitsPerEm: metrics.unitsPerEm,
|
|
382
|
+
xWidthAvg: metrics.xWidthAvg
|
|
383
|
+
}, fallbackFont);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.warn(`[vertz] Failed to extract font metrics for "${key}" from "${srcPath}":`, error instanceof Error ? error.message : error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
|
|
196
391
|
// src/source-map-resolver.ts
|
|
197
392
|
import { readFileSync } from "fs";
|
|
198
393
|
import { resolve as resolvePath } from "path";
|
|
@@ -338,6 +533,17 @@ function createSourceMapResolver(projectRoot) {
|
|
|
338
533
|
};
|
|
339
534
|
}
|
|
340
535
|
|
|
536
|
+
// src/ssr-access-set.ts
|
|
537
|
+
function createAccessSetScript(accessSet, nonce) {
|
|
538
|
+
const json = JSON.stringify(accessSet);
|
|
539
|
+
const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
540
|
+
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
541
|
+
return `<script${nonceAttr}>window.__VERTZ_ACCESS_SET__=${escaped}</script>`;
|
|
542
|
+
}
|
|
543
|
+
function escapeAttr(s) {
|
|
544
|
+
return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
545
|
+
}
|
|
546
|
+
|
|
341
547
|
// src/ssr-render.ts
|
|
342
548
|
import { compileTheme } from "@vertz/ui";
|
|
343
549
|
import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
|
|
@@ -745,14 +951,14 @@ function installDomShim() {
|
|
|
745
951
|
querySelector: () => null,
|
|
746
952
|
querySelectorAll: () => [],
|
|
747
953
|
getElementById: () => null,
|
|
954
|
+
addEventListener: () => {},
|
|
955
|
+
removeEventListener: () => {},
|
|
748
956
|
cookie: ""
|
|
749
957
|
};
|
|
750
958
|
globalThis.document = fakeDocument;
|
|
751
959
|
if (typeof window === "undefined") {
|
|
752
960
|
globalThis.window = {
|
|
753
961
|
location: { pathname: ssrStorage.getStore()?.url || "/", search: "", hash: "" },
|
|
754
|
-
addEventListener: () => {},
|
|
755
|
-
removeEventListener: () => {},
|
|
756
962
|
history: {
|
|
757
963
|
pushState: () => {},
|
|
758
964
|
replaceState: () => {}
|
|
@@ -865,14 +1071,14 @@ var RAW_TEXT_ELEMENTS = new Set(["script", "style"]);
|
|
|
865
1071
|
function escapeHtml(text) {
|
|
866
1072
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
867
1073
|
}
|
|
868
|
-
function
|
|
1074
|
+
function escapeAttr2(value) {
|
|
869
1075
|
const str = typeof value === "string" ? value : String(value);
|
|
870
1076
|
return str.replace(/&/g, "&").replace(/"/g, """);
|
|
871
1077
|
}
|
|
872
1078
|
function serializeAttrs(attrs) {
|
|
873
1079
|
const parts = [];
|
|
874
1080
|
for (const [key, value] of Object.entries(attrs)) {
|
|
875
|
-
parts.push(` ${key}="${
|
|
1081
|
+
parts.push(` ${key}="${escapeAttr2(value)}"`);
|
|
876
1082
|
}
|
|
877
1083
|
return parts.join("");
|
|
878
1084
|
}
|
|
@@ -937,7 +1143,7 @@ async function streamToString(stream) {
|
|
|
937
1143
|
function createTemplateChunk(slotId, resolvedHtml, nonce) {
|
|
938
1144
|
const tmplId = `v-tmpl-${slotId}`;
|
|
939
1145
|
const slotRef = `v-slot-${slotId}`;
|
|
940
|
-
const nonceAttr = nonce != null ? ` nonce="${
|
|
1146
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr2(nonce)}"` : "";
|
|
941
1147
|
return `<template id="${tmplId}">${resolvedHtml}</template>` + `<script${nonceAttr}>` + `(function(){` + `var s=document.getElementById("${slotRef}");` + `var t=document.getElementById("${tmplId}");` + `if(s&&t){s.replaceWith(t.content.cloneNode(true));t.remove()}` + `})()` + "</script>";
|
|
942
1148
|
}
|
|
943
1149
|
|
|
@@ -964,7 +1170,7 @@ function renderToStream(tree, options) {
|
|
|
964
1170
|
}
|
|
965
1171
|
const { tag, attrs, children } = node;
|
|
966
1172
|
const isRawText = RAW_TEXT_ELEMENTS.has(tag);
|
|
967
|
-
const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${
|
|
1173
|
+
const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${escapeAttr2(v)}"`).join("");
|
|
968
1174
|
if (VOID_ELEMENTS.has(tag)) {
|
|
969
1175
|
return `<${tag}${attrStr}>`;
|
|
970
1176
|
}
|
|
@@ -1020,7 +1226,7 @@ function createRequestContext(url) {
|
|
|
1020
1226
|
contextScope: null,
|
|
1021
1227
|
entityStore: new EntityStore,
|
|
1022
1228
|
envelopeStore: new QueryEnvelopeStore,
|
|
1023
|
-
queryCache: new MemoryCache,
|
|
1229
|
+
queryCache: new MemoryCache({ maxSize: Infinity }),
|
|
1024
1230
|
inflight: new Map,
|
|
1025
1231
|
queries: [],
|
|
1026
1232
|
errors: []
|
|
@@ -1041,9 +1247,6 @@ function resolveAppFactory(module) {
|
|
|
1041
1247
|
return createApp;
|
|
1042
1248
|
}
|
|
1043
1249
|
function collectCSS(themeCss, module) {
|
|
1044
|
-
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1045
|
-
const globalTags = module.styles ? module.styles.map((s) => `<style data-vertz-css>${s}</style>`).join(`
|
|
1046
|
-
`) : "";
|
|
1047
1250
|
const alreadyIncluded = new Set;
|
|
1048
1251
|
if (themeCss)
|
|
1049
1252
|
alreadyIncluded.add(themeCss);
|
|
@@ -1052,9 +1255,12 @@ function collectCSS(themeCss, module) {
|
|
|
1052
1255
|
alreadyIncluded.add(s);
|
|
1053
1256
|
}
|
|
1054
1257
|
const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
|
|
1055
|
-
const
|
|
1056
|
-
`
|
|
1057
|
-
|
|
1258
|
+
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1259
|
+
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
1260
|
+
`)}</style>` : "";
|
|
1261
|
+
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
1262
|
+
`)}</style>` : "";
|
|
1263
|
+
return [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
1058
1264
|
`);
|
|
1059
1265
|
}
|
|
1060
1266
|
async function ssrRenderToString(module, url, options) {
|
|
@@ -1067,14 +1273,40 @@ async function ssrRenderToString(module, url, options) {
|
|
|
1067
1273
|
setGlobalSSRTimeout(ssrTimeout);
|
|
1068
1274
|
const createApp = resolveAppFactory(module);
|
|
1069
1275
|
let themeCss = "";
|
|
1276
|
+
let themePreloadTags = "";
|
|
1070
1277
|
if (module.theme) {
|
|
1071
1278
|
try {
|
|
1072
|
-
|
|
1279
|
+
const compiled = compileTheme(module.theme, {
|
|
1280
|
+
fallbackMetrics: options?.fallbackMetrics
|
|
1281
|
+
});
|
|
1282
|
+
themeCss = compiled.css;
|
|
1283
|
+
themePreloadTags = compiled.preloadTags;
|
|
1073
1284
|
} catch (e) {
|
|
1074
1285
|
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
1075
1286
|
}
|
|
1076
1287
|
}
|
|
1077
1288
|
createApp();
|
|
1289
|
+
const store = ssrStorage.getStore();
|
|
1290
|
+
if (store) {
|
|
1291
|
+
if (store.pendingRouteComponents?.size) {
|
|
1292
|
+
const entries = Array.from(store.pendingRouteComponents.entries());
|
|
1293
|
+
const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
|
|
1294
|
+
promise.then((mod) => ({ route, factory: mod.default })),
|
|
1295
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
|
|
1296
|
+
])));
|
|
1297
|
+
store.resolvedComponents = new Map;
|
|
1298
|
+
for (const result of results) {
|
|
1299
|
+
if (result.status === "fulfilled") {
|
|
1300
|
+
const { route, factory } = result.value;
|
|
1301
|
+
store.resolvedComponents.set(route, factory);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
store.pendingRouteComponents = undefined;
|
|
1305
|
+
}
|
|
1306
|
+
if (!store.resolvedComponents) {
|
|
1307
|
+
store.resolvedComponents = new Map;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1078
1310
|
const queries = getSSRQueries();
|
|
1079
1311
|
const resolvedQueries = [];
|
|
1080
1312
|
if (queries.length > 0) {
|
|
@@ -1086,7 +1318,6 @@ async function ssrRenderToString(module, url, options) {
|
|
|
1086
1318
|
}),
|
|
1087
1319
|
new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
|
|
1088
1320
|
])));
|
|
1089
|
-
const store = ssrStorage.getStore();
|
|
1090
1321
|
if (store)
|
|
1091
1322
|
store.queries = [];
|
|
1092
1323
|
}
|
|
@@ -1099,7 +1330,13 @@ async function ssrRenderToString(module, url, options) {
|
|
|
1099
1330
|
key,
|
|
1100
1331
|
data: JSON.parse(JSON.stringify(data))
|
|
1101
1332
|
})) : [];
|
|
1102
|
-
return {
|
|
1333
|
+
return {
|
|
1334
|
+
html,
|
|
1335
|
+
css,
|
|
1336
|
+
ssrData,
|
|
1337
|
+
headTags: themePreloadTags,
|
|
1338
|
+
discoveredRoutes: ctx.discoveredRoutes
|
|
1339
|
+
};
|
|
1103
1340
|
} finally {
|
|
1104
1341
|
clearGlobalSSRTimeout();
|
|
1105
1342
|
}
|
|
@@ -1203,6 +1440,17 @@ data: ${safeSerialize(entry)}
|
|
|
1203
1440
|
});
|
|
1204
1441
|
}
|
|
1205
1442
|
|
|
1443
|
+
// src/ssr-session.ts
|
|
1444
|
+
function createSessionScript(session, nonce) {
|
|
1445
|
+
const json = JSON.stringify(session);
|
|
1446
|
+
const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1447
|
+
const nonceAttr = nonce ? ` nonce="${escapeAttr3(nonce)}"` : "";
|
|
1448
|
+
return `<script${nonceAttr}>window.__VERTZ_SESSION__=${escaped}</script>`;
|
|
1449
|
+
}
|
|
1450
|
+
function escapeAttr3(s) {
|
|
1451
|
+
return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1206
1454
|
// src/bun-dev-server.ts
|
|
1207
1455
|
var MAX_TERMINAL_STACK_FRAMES = 5;
|
|
1208
1456
|
function formatTerminalRuntimeError(errors, parsedStack) {
|
|
@@ -1431,7 +1679,8 @@ function generateSSRPageHtml({
|
|
|
1431
1679
|
ssrData,
|
|
1432
1680
|
scriptTag,
|
|
1433
1681
|
editor = "vscode",
|
|
1434
|
-
headTags = ""
|
|
1682
|
+
headTags = "",
|
|
1683
|
+
sessionScript = ""
|
|
1435
1684
|
}) {
|
|
1436
1685
|
const ssrDataScript = ssrData.length > 0 ? `<script>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>` : "";
|
|
1437
1686
|
return `<!doctype html>
|
|
@@ -1447,6 +1696,7 @@ function generateSSRPageHtml({
|
|
|
1447
1696
|
</head>
|
|
1448
1697
|
<body>
|
|
1449
1698
|
<div id="app">${bodyHtml}</div>
|
|
1699
|
+
${sessionScript}
|
|
1450
1700
|
${ssrDataScript}
|
|
1451
1701
|
${scriptTag}
|
|
1452
1702
|
</body>
|
|
@@ -1509,6 +1759,13 @@ function buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc) {
|
|
|
1509
1759
|
}
|
|
1510
1760
|
return `<script type="module" src="${clientSrc}"></script>`;
|
|
1511
1761
|
}
|
|
1762
|
+
function clearSSRRequireCache() {
|
|
1763
|
+
const keys = Object.keys(__require.cache);
|
|
1764
|
+
for (const key of keys) {
|
|
1765
|
+
delete __require.cache[key];
|
|
1766
|
+
}
|
|
1767
|
+
return keys.length;
|
|
1768
|
+
}
|
|
1512
1769
|
function createBunDevServer(options) {
|
|
1513
1770
|
const {
|
|
1514
1771
|
entry,
|
|
@@ -1522,7 +1779,8 @@ function createBunDevServer(options) {
|
|
|
1522
1779
|
projectRoot = process.cwd(),
|
|
1523
1780
|
logRequests = true,
|
|
1524
1781
|
editor: editorOption,
|
|
1525
|
-
headTags = ""
|
|
1782
|
+
headTags = "",
|
|
1783
|
+
sessionResolver
|
|
1526
1784
|
} = options;
|
|
1527
1785
|
const editor = detectEditor(editorOption);
|
|
1528
1786
|
if (apiHandler) {
|
|
@@ -1776,7 +2034,7 @@ function createBunDevServer(options) {
|
|
|
1776
2034
|
}));
|
|
1777
2035
|
}
|
|
1778
2036
|
});
|
|
1779
|
-
const { plugin: serverPlugin } = createVertzBunPlugin({
|
|
2037
|
+
const { plugin: serverPlugin, updateManifest: updateServerManifest } = createVertzBunPlugin({
|
|
1780
2038
|
hmr: false,
|
|
1781
2039
|
fastRefresh: false,
|
|
1782
2040
|
logger,
|
|
@@ -1793,6 +2051,14 @@ function createBunDevServer(options) {
|
|
|
1793
2051
|
console.error("[Server] Failed to load SSR module:", e);
|
|
1794
2052
|
process.exit(1);
|
|
1795
2053
|
}
|
|
2054
|
+
let fontFallbackMetrics;
|
|
2055
|
+
if (ssrMod.theme?.fonts) {
|
|
2056
|
+
try {
|
|
2057
|
+
fontFallbackMetrics = await extractFontMetrics(ssrMod.theme.fonts, projectRoot);
|
|
2058
|
+
} catch (e) {
|
|
2059
|
+
console.warn("[Server] Failed to extract font metrics:", e);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1796
2062
|
mkdirSync(devDir, { recursive: true });
|
|
1797
2063
|
const frInitPath = resolve(devDir, "fast-refresh-init.ts");
|
|
1798
2064
|
writeFileSync2(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
|
|
@@ -1877,6 +2143,31 @@ if (import.meta.hot) import.meta.hot.accept();
|
|
|
1877
2143
|
if (pathname === "/__vertz_diagnostics") {
|
|
1878
2144
|
return Response.json(diagnostics.getSnapshot());
|
|
1879
2145
|
}
|
|
2146
|
+
if (pathname === "/_vertz/image") {
|
|
2147
|
+
return handleDevImageProxy(request);
|
|
2148
|
+
}
|
|
2149
|
+
if (pathname.startsWith("/__vertz_img/")) {
|
|
2150
|
+
const imgName = pathname.slice("/__vertz_img/".length);
|
|
2151
|
+
if (!isValidImageName(imgName)) {
|
|
2152
|
+
return new Response("Forbidden", { status: 403 });
|
|
2153
|
+
}
|
|
2154
|
+
const imagesDir = resolve(projectRoot, ".vertz", "images");
|
|
2155
|
+
const imgPath = resolve(imagesDir, imgName);
|
|
2156
|
+
if (!imgPath.startsWith(imagesDir)) {
|
|
2157
|
+
return new Response("Forbidden", { status: 403 });
|
|
2158
|
+
}
|
|
2159
|
+
const file = Bun.file(imgPath);
|
|
2160
|
+
if (await file.exists()) {
|
|
2161
|
+
const ext = imgName.split(".").pop();
|
|
2162
|
+
return new Response(file, {
|
|
2163
|
+
headers: {
|
|
2164
|
+
"Content-Type": imageContentType(ext),
|
|
2165
|
+
"Cache-Control": "public, max-age=31536000, immutable"
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
return new Response("Not Found", { status: 404 });
|
|
2170
|
+
}
|
|
1880
2171
|
if (openapi && request.method === "GET" && pathname === "/api/openapi.json") {
|
|
1881
2172
|
return serveOpenAPISpec();
|
|
1882
2173
|
}
|
|
@@ -1937,16 +2228,38 @@ data: {}
|
|
|
1937
2228
|
skipSSRPaths,
|
|
1938
2229
|
originalFetch: globalThis.fetch
|
|
1939
2230
|
}) : null;
|
|
2231
|
+
let sessionScript = "";
|
|
2232
|
+
if (sessionResolver) {
|
|
2233
|
+
try {
|
|
2234
|
+
const sessionResult = await sessionResolver(request);
|
|
2235
|
+
if (sessionResult) {
|
|
2236
|
+
const scripts = [];
|
|
2237
|
+
scripts.push(createSessionScript(sessionResult.session));
|
|
2238
|
+
if (sessionResult.accessSet != null) {
|
|
2239
|
+
scripts.push(createAccessSetScript(sessionResult.accessSet));
|
|
2240
|
+
}
|
|
2241
|
+
sessionScript = scripts.join(`
|
|
2242
|
+
`);
|
|
2243
|
+
}
|
|
2244
|
+
} catch (resolverErr) {
|
|
2245
|
+
console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
1940
2248
|
const doRender = async () => {
|
|
1941
2249
|
logger.log("ssr", "render-start", { url: pathname });
|
|
1942
2250
|
const ssrStart = performance.now();
|
|
1943
|
-
const result = await ssrRenderToString(ssrMod, pathname, {
|
|
2251
|
+
const result = await ssrRenderToString(ssrMod, pathname, {
|
|
2252
|
+
ssrTimeout: 300,
|
|
2253
|
+
fallbackMetrics: fontFallbackMetrics
|
|
2254
|
+
});
|
|
1944
2255
|
logger.log("ssr", "render-done", {
|
|
1945
2256
|
url: pathname,
|
|
1946
2257
|
durationMs: Math.round(performance.now() - ssrStart),
|
|
1947
2258
|
htmlBytes: result.html.length
|
|
1948
2259
|
});
|
|
1949
2260
|
const scriptTag = buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc);
|
|
2261
|
+
const combinedHeadTags = [headTags, result.headTags].filter(Boolean).join(`
|
|
2262
|
+
`);
|
|
1950
2263
|
const html = generateSSRPageHtml({
|
|
1951
2264
|
title,
|
|
1952
2265
|
css: result.css,
|
|
@@ -1954,7 +2267,8 @@ data: {}
|
|
|
1954
2267
|
ssrData: result.ssrData,
|
|
1955
2268
|
scriptTag,
|
|
1956
2269
|
editor,
|
|
1957
|
-
headTags
|
|
2270
|
+
headTags: combinedHeadTags,
|
|
2271
|
+
sessionScript
|
|
1958
2272
|
});
|
|
1959
2273
|
return new Response(html, {
|
|
1960
2274
|
status: 200,
|
|
@@ -2188,14 +2502,19 @@ data: {}
|
|
|
2188
2502
|
} catch {}
|
|
2189
2503
|
if (stopped)
|
|
2190
2504
|
return;
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2505
|
+
if (filename.endsWith(".ts") || filename.endsWith(".tsx")) {
|
|
2506
|
+
const changedFilePath = resolve(srcDir, filename);
|
|
2507
|
+
try {
|
|
2508
|
+
const manifestStartMs = performance.now();
|
|
2509
|
+
const source = await Bun.file(changedFilePath).text();
|
|
2510
|
+
const { changed } = updateServerManifest(changedFilePath, source);
|
|
2511
|
+
const manifestDurationMs = Math.round(performance.now() - manifestStartMs);
|
|
2512
|
+
diagnostics.recordManifestUpdate(lastChangedFile, changed, manifestDurationMs);
|
|
2513
|
+
} catch {}
|
|
2198
2514
|
}
|
|
2515
|
+
if (stopped)
|
|
2516
|
+
return;
|
|
2517
|
+
const cacheCleared = clearSSRRequireCache();
|
|
2199
2518
|
logger.log("watcher", "cache-cleared", { entries: cacheCleared });
|
|
2200
2519
|
const ssrWrapperPath = resolve(devDir, "ssr-reload-entry.ts");
|
|
2201
2520
|
mkdirSync(devDir, { recursive: true });
|
|
@@ -2205,6 +2524,11 @@ data: {}
|
|
|
2205
2524
|
try {
|
|
2206
2525
|
const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
|
|
2207
2526
|
ssrMod = freshMod;
|
|
2527
|
+
if (freshMod.theme?.fonts) {
|
|
2528
|
+
try {
|
|
2529
|
+
fontFallbackMetrics = await extractFontMetrics(freshMod.theme.fonts, projectRoot);
|
|
2530
|
+
} catch {}
|
|
2531
|
+
}
|
|
2208
2532
|
const durationMs = Math.round(performance.now() - ssrReloadStart);
|
|
2209
2533
|
diagnostics.recordSSRReload(true, durationMs);
|
|
2210
2534
|
logger.log("watcher", "ssr-reload", { status: "ok", durationMs });
|
|
@@ -2218,18 +2542,18 @@ data: {}
|
|
|
2218
2542
|
await new Promise((r) => setTimeout(r, 500));
|
|
2219
2543
|
if (stopped)
|
|
2220
2544
|
return;
|
|
2221
|
-
|
|
2222
|
-
for (const key of retryKeys) {
|
|
2223
|
-
if (key.startsWith(srcDir) || key.startsWith(entryPath)) {
|
|
2224
|
-
delete __require.cache[key];
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2545
|
+
clearSSRRequireCache();
|
|
2227
2546
|
mkdirSync(devDir, { recursive: true });
|
|
2228
2547
|
writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
|
|
2229
2548
|
`);
|
|
2230
2549
|
try {
|
|
2231
2550
|
const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
|
|
2232
2551
|
ssrMod = freshMod;
|
|
2552
|
+
if (freshMod.theme?.fonts) {
|
|
2553
|
+
try {
|
|
2554
|
+
fontFallbackMetrics = await extractFontMetrics(freshMod.theme.fonts, projectRoot);
|
|
2555
|
+
} catch {}
|
|
2556
|
+
}
|
|
2233
2557
|
const durationMs = Math.round(performance.now() - ssrReloadStart);
|
|
2234
2558
|
diagnostics.recordSSRReload(true, durationMs);
|
|
2235
2559
|
logger.log("watcher", "ssr-reload", { status: "ok", durationMs, retry: true });
|
|
@@ -2287,5 +2611,6 @@ export {
|
|
|
2287
2611
|
createRuntimeErrorDeduplicator,
|
|
2288
2612
|
createFetchInterceptor,
|
|
2289
2613
|
createBunDevServer,
|
|
2614
|
+
clearSSRRequireCache,
|
|
2290
2615
|
buildScriptTag
|
|
2291
2616
|
};
|