@vertz/ui-server 0.2.0 → 0.2.4
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/README.md +298 -310
- package/dist/bun-dev-server.d.ts +122 -0
- package/dist/bun-dev-server.js +2247 -0
- package/dist/bun-plugin/fast-refresh-dom-state.d.ts +51 -0
- package/dist/bun-plugin/fast-refresh-dom-state.js +10 -0
- package/dist/bun-plugin/fast-refresh-runtime.d.ts +43 -0
- package/dist/bun-plugin/fast-refresh-runtime.js +150 -0
- package/dist/bun-plugin/index.d.ts +120 -0
- package/dist/bun-plugin/index.js +216 -0
- package/dist/dom-shim/index.d.ts +37 -6
- package/dist/dom-shim/index.js +12 -324
- package/dist/index.d.ts +331 -64
- package/dist/index.js +285 -292
- package/dist/jsx-runtime/index.js +15 -2
- package/dist/shared/chunk-2qsqp9xj.js +150 -0
- package/dist/shared/chunk-32688jav.js +564 -0
- package/dist/shared/chunk-4t0ekdyv.js +513 -0
- package/dist/shared/chunk-eb80r8e8.js +4 -0
- package/dist/ssr/index.d.ts +86 -0
- package/dist/ssr/index.js +11 -0
- package/package.json +35 -18
|
@@ -0,0 +1,2247 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./shared/chunk-eb80r8e8.js";
|
|
5
|
+
|
|
6
|
+
// src/bun-dev-server.ts
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, watch, writeFileSync as writeFileSync2 } from "fs";
|
|
9
|
+
import { dirname, normalize, resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// src/debug-logger.ts
|
|
12
|
+
import { appendFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
function createDebugLogger(logDir) {
|
|
15
|
+
const envValue = process.env.VERTZ_DEBUG;
|
|
16
|
+
if (!envValue) {
|
|
17
|
+
return {
|
|
18
|
+
log() {},
|
|
19
|
+
isEnabled() {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const enableAll = envValue === "1";
|
|
25
|
+
const enabledCategories = enableAll ? null : new Set(envValue.split(","));
|
|
26
|
+
const logFile = join(logDir, "debug.log");
|
|
27
|
+
writeFileSync(logFile, "");
|
|
28
|
+
function isEnabled(category) {
|
|
29
|
+
return enableAll || enabledCategories.has(category);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
log(category, message, data) {
|
|
33
|
+
if (!isEnabled(category))
|
|
34
|
+
return;
|
|
35
|
+
const entry = { cat: category, msg: message, ...data };
|
|
36
|
+
appendFileSync(logFile, JSON.stringify(entry) + `
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
isEnabled
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/diagnostics-collector.ts
|
|
44
|
+
class DiagnosticsCollector {
|
|
45
|
+
startTime = Date.now();
|
|
46
|
+
pluginFilter = "";
|
|
47
|
+
pluginHmr = false;
|
|
48
|
+
pluginFastRefresh = false;
|
|
49
|
+
processedFilesSet = new Set;
|
|
50
|
+
processedCount = 0;
|
|
51
|
+
ssrModuleStatus = "pending";
|
|
52
|
+
ssrLastReloadTime = null;
|
|
53
|
+
ssrLastReloadDurationMs = null;
|
|
54
|
+
ssrLastReloadError = null;
|
|
55
|
+
ssrReloadCount = 0;
|
|
56
|
+
ssrFailedReloadCount = 0;
|
|
57
|
+
hmrBundledScriptUrl = null;
|
|
58
|
+
hmrBootstrapDiscovered = false;
|
|
59
|
+
errorCurrent = null;
|
|
60
|
+
errorLastCategory = null;
|
|
61
|
+
errorLastMessage = null;
|
|
62
|
+
wsConnectedClients = 0;
|
|
63
|
+
watcherLastChangedFile = null;
|
|
64
|
+
watcherLastChangeTime = null;
|
|
65
|
+
recordPluginConfig(filter, hmr, fastRefresh) {
|
|
66
|
+
this.pluginFilter = filter;
|
|
67
|
+
this.pluginHmr = hmr;
|
|
68
|
+
this.pluginFastRefresh = fastRefresh;
|
|
69
|
+
}
|
|
70
|
+
recordPluginProcess(file) {
|
|
71
|
+
this.processedFilesSet.add(file);
|
|
72
|
+
this.processedCount++;
|
|
73
|
+
}
|
|
74
|
+
recordSSRReload(success, durationMs, error) {
|
|
75
|
+
this.ssrReloadCount++;
|
|
76
|
+
this.ssrLastReloadTime = new Date().toISOString();
|
|
77
|
+
this.ssrLastReloadDurationMs = durationMs;
|
|
78
|
+
if (success) {
|
|
79
|
+
this.ssrModuleStatus = "loaded";
|
|
80
|
+
this.ssrLastReloadError = null;
|
|
81
|
+
} else {
|
|
82
|
+
this.ssrModuleStatus = "error";
|
|
83
|
+
this.ssrLastReloadError = error ?? null;
|
|
84
|
+
this.ssrFailedReloadCount++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
recordHMRAssets(bundledScriptUrl, bootstrapDiscovered) {
|
|
88
|
+
this.hmrBundledScriptUrl = bundledScriptUrl;
|
|
89
|
+
this.hmrBootstrapDiscovered = bootstrapDiscovered;
|
|
90
|
+
}
|
|
91
|
+
recordError(category, message) {
|
|
92
|
+
this.errorCurrent = category;
|
|
93
|
+
this.errorLastCategory = category;
|
|
94
|
+
this.errorLastMessage = message;
|
|
95
|
+
}
|
|
96
|
+
recordErrorClear() {
|
|
97
|
+
this.errorCurrent = null;
|
|
98
|
+
}
|
|
99
|
+
recordWebSocketChange(count) {
|
|
100
|
+
this.wsConnectedClients = count;
|
|
101
|
+
}
|
|
102
|
+
recordFileChange(file) {
|
|
103
|
+
this.watcherLastChangedFile = file;
|
|
104
|
+
this.watcherLastChangeTime = new Date().toISOString();
|
|
105
|
+
}
|
|
106
|
+
getSnapshot() {
|
|
107
|
+
return {
|
|
108
|
+
status: "ok",
|
|
109
|
+
uptime: (Date.now() - this.startTime) / 1000,
|
|
110
|
+
plugin: {
|
|
111
|
+
filter: this.pluginFilter,
|
|
112
|
+
hmr: this.pluginHmr,
|
|
113
|
+
fastRefresh: this.pluginFastRefresh,
|
|
114
|
+
processedFiles: Array.from(this.processedFilesSet),
|
|
115
|
+
processedCount: this.processedCount
|
|
116
|
+
},
|
|
117
|
+
ssr: {
|
|
118
|
+
moduleStatus: this.ssrModuleStatus,
|
|
119
|
+
lastReloadTime: this.ssrLastReloadTime,
|
|
120
|
+
lastReloadDurationMs: this.ssrLastReloadDurationMs,
|
|
121
|
+
lastReloadError: this.ssrLastReloadError,
|
|
122
|
+
reloadCount: this.ssrReloadCount,
|
|
123
|
+
failedReloadCount: this.ssrFailedReloadCount
|
|
124
|
+
},
|
|
125
|
+
hmr: {
|
|
126
|
+
bundledScriptUrl: this.hmrBundledScriptUrl,
|
|
127
|
+
bootstrapDiscovered: this.hmrBootstrapDiscovered
|
|
128
|
+
},
|
|
129
|
+
errors: {
|
|
130
|
+
current: this.errorCurrent,
|
|
131
|
+
lastCategory: this.errorLastCategory,
|
|
132
|
+
lastMessage: this.errorLastMessage
|
|
133
|
+
},
|
|
134
|
+
websocket: {
|
|
135
|
+
connectedClients: this.wsConnectedClients
|
|
136
|
+
},
|
|
137
|
+
watcher: {
|
|
138
|
+
lastChangedFile: this.watcherLastChangedFile,
|
|
139
|
+
lastChangeTime: this.watcherLastChangeTime
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/source-map-resolver.ts
|
|
146
|
+
import { readFileSync } from "fs";
|
|
147
|
+
import { resolve as resolvePath } from "path";
|
|
148
|
+
import { originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
|
|
149
|
+
function extractInlineSourceMap(jsContent) {
|
|
150
|
+
const match = jsContent.match(/\/\/# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/);
|
|
151
|
+
if (!match?.[1])
|
|
152
|
+
return null;
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(atob(match[1]));
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function resolvePosition(sourceMapJSON, line, column) {
|
|
160
|
+
const tracer = new TraceMap(sourceMapJSON);
|
|
161
|
+
const pos = originalPositionFor(tracer, { line, column });
|
|
162
|
+
if (pos.source == null)
|
|
163
|
+
return null;
|
|
164
|
+
return {
|
|
165
|
+
source: pos.source,
|
|
166
|
+
line: pos.line ?? 1,
|
|
167
|
+
column: pos.column ?? 0,
|
|
168
|
+
name: pos.name
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function readLineText(filePath, line) {
|
|
172
|
+
try {
|
|
173
|
+
const content = readFileSync(filePath, "utf-8");
|
|
174
|
+
const lines = content.split(`
|
|
175
|
+
`);
|
|
176
|
+
const idx = line - 1;
|
|
177
|
+
if (idx < 0 || idx >= lines.length)
|
|
178
|
+
return;
|
|
179
|
+
return lines[idx];
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function parseStackFrames(stack) {
|
|
185
|
+
const frames = [];
|
|
186
|
+
const lines = stack.split(`
|
|
187
|
+
`);
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
const namedMatch = line.match(/^\s+at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
190
|
+
if (namedMatch?.[1] && namedMatch[2]) {
|
|
191
|
+
frames.push({
|
|
192
|
+
functionName: namedMatch[1],
|
|
193
|
+
file: namedMatch[2],
|
|
194
|
+
line: Number(namedMatch[3]),
|
|
195
|
+
column: Number(namedMatch[4])
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const anonMatch = line.match(/^\s+at\s+((?:https?:\/\/).+?):(\d+):(\d+)/);
|
|
200
|
+
if (anonMatch?.[1]) {
|
|
201
|
+
frames.push({
|
|
202
|
+
functionName: null,
|
|
203
|
+
file: anonMatch[1],
|
|
204
|
+
line: Number(anonMatch[2]),
|
|
205
|
+
column: Number(anonMatch[3])
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return frames;
|
|
210
|
+
}
|
|
211
|
+
function createSourceMapResolver(projectRoot) {
|
|
212
|
+
const cache = new Map;
|
|
213
|
+
async function fetchAndCache(url, fetchFn) {
|
|
214
|
+
if (cache.has(url))
|
|
215
|
+
return cache.get(url) ?? null;
|
|
216
|
+
try {
|
|
217
|
+
const res = await fetchFn(url);
|
|
218
|
+
const jsContent = await res.text();
|
|
219
|
+
const sourceMap = extractInlineSourceMap(jsContent);
|
|
220
|
+
cache.set(url, sourceMap);
|
|
221
|
+
return sourceMap;
|
|
222
|
+
} catch {
|
|
223
|
+
cache.set(url, null);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
async resolve(bundledUrl, line, column, fetchFn) {
|
|
229
|
+
const sourceMap = await fetchAndCache(bundledUrl, fetchFn);
|
|
230
|
+
if (!sourceMap)
|
|
231
|
+
return null;
|
|
232
|
+
const pos = resolvePosition(sourceMap, line, column);
|
|
233
|
+
if (!pos)
|
|
234
|
+
return null;
|
|
235
|
+
return {
|
|
236
|
+
...pos,
|
|
237
|
+
absSource: resolvePath(projectRoot, pos.source)
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
async resolveStack(stack, message, fetchFn) {
|
|
241
|
+
const frames = parseStackFrames(stack);
|
|
242
|
+
const resolvedFrames = [];
|
|
243
|
+
let topResolved = null;
|
|
244
|
+
for (const frame of frames) {
|
|
245
|
+
if (frame.file.includes("/_bun/")) {
|
|
246
|
+
const resolved = await this.resolve(frame.file, frame.line, frame.column, fetchFn);
|
|
247
|
+
if (resolved) {
|
|
248
|
+
if (!topResolved)
|
|
249
|
+
topResolved = resolved;
|
|
250
|
+
resolvedFrames.push({
|
|
251
|
+
functionName: frame.functionName,
|
|
252
|
+
file: resolved.source,
|
|
253
|
+
absFile: resolved.absSource,
|
|
254
|
+
line: resolved.line,
|
|
255
|
+
column: resolved.column
|
|
256
|
+
});
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
resolvedFrames.push({
|
|
261
|
+
functionName: frame.functionName,
|
|
262
|
+
file: frame.file,
|
|
263
|
+
absFile: frame.file,
|
|
264
|
+
line: frame.line,
|
|
265
|
+
column: frame.column
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const errors = [];
|
|
269
|
+
if (topResolved) {
|
|
270
|
+
const lineText = readLineText(topResolved.absSource, topResolved.line);
|
|
271
|
+
errors.push({
|
|
272
|
+
message,
|
|
273
|
+
file: topResolved.source,
|
|
274
|
+
absFile: topResolved.absSource,
|
|
275
|
+
line: topResolved.line,
|
|
276
|
+
column: topResolved.column,
|
|
277
|
+
lineText
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
errors.push({ message });
|
|
281
|
+
}
|
|
282
|
+
return { errors, parsedStack: resolvedFrames };
|
|
283
|
+
},
|
|
284
|
+
invalidate() {
|
|
285
|
+
cache.clear();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/ssr-render.ts
|
|
291
|
+
import { compileTheme } from "@vertz/ui";
|
|
292
|
+
|
|
293
|
+
// src/dom-shim/index.ts
|
|
294
|
+
import { setAdapter } from "@vertz/ui/internals";
|
|
295
|
+
|
|
296
|
+
// src/dom-shim/ssr-node.ts
|
|
297
|
+
class SSRNode {
|
|
298
|
+
childNodes = [];
|
|
299
|
+
parentNode = null;
|
|
300
|
+
get firstChild() {
|
|
301
|
+
return this.childNodes[0] ?? null;
|
|
302
|
+
}
|
|
303
|
+
get nextSibling() {
|
|
304
|
+
if (!this.parentNode)
|
|
305
|
+
return null;
|
|
306
|
+
const index = this.parentNode.childNodes.indexOf(this);
|
|
307
|
+
return this.parentNode.childNodes[index + 1] ?? null;
|
|
308
|
+
}
|
|
309
|
+
removeChild(child) {
|
|
310
|
+
const index = this.childNodes.indexOf(child);
|
|
311
|
+
if (index !== -1) {
|
|
312
|
+
this.childNodes.splice(index, 1);
|
|
313
|
+
child.parentNode = null;
|
|
314
|
+
}
|
|
315
|
+
return child;
|
|
316
|
+
}
|
|
317
|
+
insertBefore(newNode, referenceNode) {
|
|
318
|
+
if (!referenceNode) {
|
|
319
|
+
this.childNodes.push(newNode);
|
|
320
|
+
newNode.parentNode = this;
|
|
321
|
+
} else {
|
|
322
|
+
const index = this.childNodes.indexOf(referenceNode);
|
|
323
|
+
if (index !== -1) {
|
|
324
|
+
this.childNodes.splice(index, 0, newNode);
|
|
325
|
+
newNode.parentNode = this;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return newNode;
|
|
329
|
+
}
|
|
330
|
+
replaceChild(newNode, oldNode) {
|
|
331
|
+
const index = this.childNodes.indexOf(oldNode);
|
|
332
|
+
if (index !== -1) {
|
|
333
|
+
this.childNodes[index] = newNode;
|
|
334
|
+
newNode.parentNode = this;
|
|
335
|
+
oldNode.parentNode = null;
|
|
336
|
+
}
|
|
337
|
+
return oldNode;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/dom-shim/ssr-comment.ts
|
|
342
|
+
class SSRComment extends SSRNode {
|
|
343
|
+
text;
|
|
344
|
+
constructor(text) {
|
|
345
|
+
super();
|
|
346
|
+
this.text = text;
|
|
347
|
+
}
|
|
348
|
+
get data() {
|
|
349
|
+
return this.text;
|
|
350
|
+
}
|
|
351
|
+
set data(value) {
|
|
352
|
+
this.text = value;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/types.ts
|
|
357
|
+
function rawHtml(html) {
|
|
358
|
+
return { __raw: true, html };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/dom-shim/ssr-text-node.ts
|
|
362
|
+
class SSRTextNode extends SSRNode {
|
|
363
|
+
text;
|
|
364
|
+
constructor(text) {
|
|
365
|
+
super();
|
|
366
|
+
this.text = text;
|
|
367
|
+
}
|
|
368
|
+
get data() {
|
|
369
|
+
return this.text;
|
|
370
|
+
}
|
|
371
|
+
set data(value) {
|
|
372
|
+
this.text = value;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/dom-shim/ssr-fragment.ts
|
|
377
|
+
class SSRDocumentFragment extends SSRNode {
|
|
378
|
+
children = [];
|
|
379
|
+
appendChild(child) {
|
|
380
|
+
if (child instanceof SSRTextNode) {
|
|
381
|
+
this.children.push(child.text);
|
|
382
|
+
this.childNodes.push(child);
|
|
383
|
+
child.parentNode = this;
|
|
384
|
+
} else if (child instanceof SSRDocumentFragment) {
|
|
385
|
+
this.children.push(...child.children);
|
|
386
|
+
this.childNodes.push(...child.childNodes);
|
|
387
|
+
for (const fragmentChild of child.childNodes) {
|
|
388
|
+
fragmentChild.parentNode = this;
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
this.children.push(child);
|
|
392
|
+
this.childNodes.push(child);
|
|
393
|
+
child.parentNode = this;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/dom-shim/ssr-element.ts
|
|
399
|
+
function createStyleProxy(element) {
|
|
400
|
+
const styles = {};
|
|
401
|
+
return new Proxy(styles, {
|
|
402
|
+
set(_target, prop, value) {
|
|
403
|
+
if (typeof prop === "string") {
|
|
404
|
+
styles[prop] = value;
|
|
405
|
+
const pairs = Object.entries(styles).map(([k, v]) => {
|
|
406
|
+
const key = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
407
|
+
return `${key}: ${v}`;
|
|
408
|
+
});
|
|
409
|
+
element.attrs.style = pairs.join("; ");
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
},
|
|
413
|
+
get(_target, prop) {
|
|
414
|
+
if (typeof prop === "string") {
|
|
415
|
+
return styles[prop] ?? "";
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
class SSRElement extends SSRNode {
|
|
423
|
+
tag;
|
|
424
|
+
attrs = {};
|
|
425
|
+
children = [];
|
|
426
|
+
_classList = new Set;
|
|
427
|
+
_textContent = null;
|
|
428
|
+
_innerHTML = null;
|
|
429
|
+
style;
|
|
430
|
+
constructor(tag) {
|
|
431
|
+
super();
|
|
432
|
+
this.tag = tag;
|
|
433
|
+
this.style = createStyleProxy(this);
|
|
434
|
+
}
|
|
435
|
+
setAttribute(name, value) {
|
|
436
|
+
if (name === "class") {
|
|
437
|
+
this._classList = new Set(value.split(/\s+/).filter(Boolean));
|
|
438
|
+
}
|
|
439
|
+
this.attrs[name] = value;
|
|
440
|
+
}
|
|
441
|
+
getAttribute(name) {
|
|
442
|
+
return this.attrs[name] ?? null;
|
|
443
|
+
}
|
|
444
|
+
removeAttribute(name) {
|
|
445
|
+
delete this.attrs[name];
|
|
446
|
+
if (name === "class") {
|
|
447
|
+
this._classList.clear();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
appendChild(child) {
|
|
451
|
+
if (child instanceof SSRComment) {
|
|
452
|
+
this.children.push(child);
|
|
453
|
+
this.childNodes.push(child);
|
|
454
|
+
child.parentNode = this;
|
|
455
|
+
} else if (child instanceof SSRTextNode) {
|
|
456
|
+
this.children.push(child.text);
|
|
457
|
+
this.childNodes.push(child);
|
|
458
|
+
child.parentNode = this;
|
|
459
|
+
} else if (child instanceof SSRDocumentFragment) {
|
|
460
|
+
for (const fragmentChild of child.childNodes) {
|
|
461
|
+
if (fragmentChild instanceof SSRComment) {
|
|
462
|
+
this.children.push(fragmentChild);
|
|
463
|
+
} else if (fragmentChild instanceof SSRTextNode) {
|
|
464
|
+
this.children.push(fragmentChild.text);
|
|
465
|
+
} else if (fragmentChild instanceof SSRElement) {
|
|
466
|
+
this.children.push(fragmentChild);
|
|
467
|
+
}
|
|
468
|
+
this.childNodes.push(fragmentChild);
|
|
469
|
+
fragmentChild.parentNode = this;
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
this.children.push(child);
|
|
473
|
+
this.childNodes.push(child);
|
|
474
|
+
child.parentNode = this;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
insertBefore(newNode, referenceNode) {
|
|
478
|
+
const refIdx = referenceNode ? this._findChildIndex(referenceNode) : -1;
|
|
479
|
+
const result = super.insertBefore(newNode, referenceNode);
|
|
480
|
+
if (newNode instanceof SSRDocumentFragment) {
|
|
481
|
+
const fragmentChildren = [];
|
|
482
|
+
for (const fc of newNode.childNodes) {
|
|
483
|
+
if (fc instanceof SSRComment)
|
|
484
|
+
fragmentChildren.push(fc);
|
|
485
|
+
else if (fc instanceof SSRTextNode)
|
|
486
|
+
fragmentChildren.push(fc.text);
|
|
487
|
+
else if (fc instanceof SSRElement)
|
|
488
|
+
fragmentChildren.push(fc);
|
|
489
|
+
}
|
|
490
|
+
if (!referenceNode || refIdx === -1) {
|
|
491
|
+
this.children.push(...fragmentChildren);
|
|
492
|
+
} else {
|
|
493
|
+
this.children.splice(refIdx, 0, ...fragmentChildren);
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
const child = newNode instanceof SSRComment ? newNode : newNode instanceof SSRTextNode ? newNode.text : newNode instanceof SSRElement ? newNode : null;
|
|
497
|
+
if (child != null) {
|
|
498
|
+
if (!referenceNode || refIdx === -1) {
|
|
499
|
+
this.children.push(child);
|
|
500
|
+
} else {
|
|
501
|
+
this.children.splice(refIdx, 0, child);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
replaceChild(newNode, oldNode) {
|
|
508
|
+
const oldIdx = this._findChildIndex(oldNode);
|
|
509
|
+
const result = super.replaceChild(newNode, oldNode);
|
|
510
|
+
if (oldIdx !== -1) {
|
|
511
|
+
const newChild = newNode instanceof SSRComment ? newNode : newNode instanceof SSRTextNode ? newNode.text : newNode instanceof SSRElement ? newNode : null;
|
|
512
|
+
if (newChild != null) {
|
|
513
|
+
this.children[oldIdx] = newChild;
|
|
514
|
+
} else {
|
|
515
|
+
this.children.splice(oldIdx, 1);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
_findChildIndex(node) {
|
|
521
|
+
return this.childNodes.indexOf(node);
|
|
522
|
+
}
|
|
523
|
+
removeChild(child) {
|
|
524
|
+
const idx = this._findChildIndex(child);
|
|
525
|
+
const result = super.removeChild(child);
|
|
526
|
+
if (idx !== -1) {
|
|
527
|
+
this.children.splice(idx, 1);
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
get classList() {
|
|
532
|
+
const self = this;
|
|
533
|
+
return {
|
|
534
|
+
add(cls) {
|
|
535
|
+
self._classList.add(cls);
|
|
536
|
+
self.attrs.class = [...self._classList].join(" ");
|
|
537
|
+
},
|
|
538
|
+
remove(cls) {
|
|
539
|
+
self._classList.delete(cls);
|
|
540
|
+
const val = [...self._classList].join(" ");
|
|
541
|
+
if (val) {
|
|
542
|
+
self.attrs.class = val;
|
|
543
|
+
} else {
|
|
544
|
+
delete self.attrs.class;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
set className(value) {
|
|
550
|
+
this._classList = new Set(value.split(/\s+/).filter(Boolean));
|
|
551
|
+
if (value) {
|
|
552
|
+
this.attrs.class = value;
|
|
553
|
+
} else {
|
|
554
|
+
delete this.attrs.class;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
get className() {
|
|
558
|
+
return this.attrs.class ?? "";
|
|
559
|
+
}
|
|
560
|
+
set textContent(value) {
|
|
561
|
+
this._textContent = value;
|
|
562
|
+
this.children = value ? [value] : [];
|
|
563
|
+
this.childNodes = [];
|
|
564
|
+
}
|
|
565
|
+
get textContent() {
|
|
566
|
+
return this._textContent;
|
|
567
|
+
}
|
|
568
|
+
set innerHTML(value) {
|
|
569
|
+
this._innerHTML = value;
|
|
570
|
+
this.children = value ? [value] : [];
|
|
571
|
+
this.childNodes = [];
|
|
572
|
+
}
|
|
573
|
+
get innerHTML() {
|
|
574
|
+
return this._innerHTML ?? "";
|
|
575
|
+
}
|
|
576
|
+
addEventListener(_event, _handler) {}
|
|
577
|
+
removeEventListener(_event, _handler) {}
|
|
578
|
+
toVNode() {
|
|
579
|
+
return {
|
|
580
|
+
tag: this.tag,
|
|
581
|
+
attrs: { ...this.attrs },
|
|
582
|
+
children: this.children.map((child) => {
|
|
583
|
+
if (typeof child === "string") {
|
|
584
|
+
return this._innerHTML != null ? rawHtml(child) : child;
|
|
585
|
+
}
|
|
586
|
+
if (child instanceof SSRComment)
|
|
587
|
+
return rawHtml(`<!--${child.text}-->`);
|
|
588
|
+
if (typeof child.toVNode === "function")
|
|
589
|
+
return child.toVNode();
|
|
590
|
+
return String(child);
|
|
591
|
+
})
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/ssr-adapter.ts
|
|
597
|
+
var BRAND = Symbol.for("vertz:render-node");
|
|
598
|
+
Object.defineProperty(SSRNode.prototype, BRAND, {
|
|
599
|
+
value: true,
|
|
600
|
+
enumerable: false,
|
|
601
|
+
configurable: false,
|
|
602
|
+
writable: false
|
|
603
|
+
});
|
|
604
|
+
function createSSRAdapter() {
|
|
605
|
+
return {
|
|
606
|
+
createElement: (tag) => new SSRElement(tag),
|
|
607
|
+
createElementNS: (_ns, tag) => new SSRElement(tag),
|
|
608
|
+
createTextNode: (text) => new SSRTextNode(text),
|
|
609
|
+
createComment: (text) => new SSRComment(text),
|
|
610
|
+
createDocumentFragment: () => new SSRDocumentFragment,
|
|
611
|
+
isNode: (value) => value != null && typeof value === "object" && (BRAND in value)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/dom-shim/index.ts
|
|
616
|
+
var SHIM_GLOBALS = [
|
|
617
|
+
"document",
|
|
618
|
+
"window",
|
|
619
|
+
"Node",
|
|
620
|
+
"HTMLElement",
|
|
621
|
+
"HTMLAnchorElement",
|
|
622
|
+
"HTMLDivElement",
|
|
623
|
+
"HTMLInputElement",
|
|
624
|
+
"HTMLButtonElement",
|
|
625
|
+
"HTMLSelectElement",
|
|
626
|
+
"HTMLTextAreaElement",
|
|
627
|
+
"DocumentFragment",
|
|
628
|
+
"MouseEvent",
|
|
629
|
+
"Event"
|
|
630
|
+
];
|
|
631
|
+
var savedGlobals = null;
|
|
632
|
+
var shimInstalled = false;
|
|
633
|
+
var installedGlobals = [];
|
|
634
|
+
function installGlobal(name, value) {
|
|
635
|
+
if (globalThis[name] === undefined) {
|
|
636
|
+
Object.defineProperty(globalThis, name, {
|
|
637
|
+
value,
|
|
638
|
+
writable: true,
|
|
639
|
+
configurable: true
|
|
640
|
+
});
|
|
641
|
+
installedGlobals.push(name);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function installDomShim() {
|
|
645
|
+
for (const g of installedGlobals) {
|
|
646
|
+
delete globalThis[g];
|
|
647
|
+
}
|
|
648
|
+
installedGlobals = [];
|
|
649
|
+
setAdapter(createSSRAdapter());
|
|
650
|
+
const isSSRContext = typeof globalThis.__SSR_URL__ !== "undefined";
|
|
651
|
+
if (typeof document !== "undefined" && !isSSRContext && !shimInstalled) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (!shimInstalled) {
|
|
655
|
+
savedGlobals = new Map;
|
|
656
|
+
for (const g of SHIM_GLOBALS) {
|
|
657
|
+
if (g in globalThis) {
|
|
658
|
+
savedGlobals.set(g, globalThis[g]);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
shimInstalled = true;
|
|
663
|
+
const fakeDocument = {
|
|
664
|
+
createElement(tag) {
|
|
665
|
+
return new SSRElement(tag);
|
|
666
|
+
},
|
|
667
|
+
createTextNode(text) {
|
|
668
|
+
return new SSRTextNode(text);
|
|
669
|
+
},
|
|
670
|
+
createComment(text) {
|
|
671
|
+
return new SSRComment(text);
|
|
672
|
+
},
|
|
673
|
+
createDocumentFragment() {
|
|
674
|
+
return new SSRDocumentFragment;
|
|
675
|
+
},
|
|
676
|
+
head: new SSRElement("head"),
|
|
677
|
+
body: new SSRElement("body"),
|
|
678
|
+
querySelector: () => null,
|
|
679
|
+
querySelectorAll: () => [],
|
|
680
|
+
getElementById: () => null,
|
|
681
|
+
cookie: ""
|
|
682
|
+
};
|
|
683
|
+
globalThis.document = fakeDocument;
|
|
684
|
+
if (typeof window === "undefined") {
|
|
685
|
+
globalThis.window = {
|
|
686
|
+
location: { pathname: globalThis.__SSR_URL__ || "/", search: "", hash: "" },
|
|
687
|
+
addEventListener: () => {},
|
|
688
|
+
removeEventListener: () => {},
|
|
689
|
+
history: {
|
|
690
|
+
pushState: () => {},
|
|
691
|
+
replaceState: () => {}
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
} else {
|
|
695
|
+
globalThis.window.location = {
|
|
696
|
+
...globalThis.window.location || {},
|
|
697
|
+
pathname: globalThis.__SSR_URL__ || "/"
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
globalThis.Node = SSRNode;
|
|
701
|
+
globalThis.HTMLElement = SSRElement;
|
|
702
|
+
globalThis.HTMLAnchorElement = SSRElement;
|
|
703
|
+
globalThis.HTMLDivElement = SSRElement;
|
|
704
|
+
globalThis.HTMLInputElement = SSRElement;
|
|
705
|
+
globalThis.HTMLButtonElement = SSRElement;
|
|
706
|
+
globalThis.HTMLSelectElement = SSRElement;
|
|
707
|
+
globalThis.HTMLTextAreaElement = SSRElement;
|
|
708
|
+
globalThis.DocumentFragment = SSRDocumentFragment;
|
|
709
|
+
globalThis.MouseEvent = class MockMouseEvent {
|
|
710
|
+
};
|
|
711
|
+
globalThis.Event = class MockEvent {
|
|
712
|
+
};
|
|
713
|
+
const storageStub = {
|
|
714
|
+
getItem: () => null,
|
|
715
|
+
setItem: () => {},
|
|
716
|
+
removeItem: () => {},
|
|
717
|
+
clear: () => {},
|
|
718
|
+
key: () => null,
|
|
719
|
+
length: 0
|
|
720
|
+
};
|
|
721
|
+
installGlobal("localStorage", storageStub);
|
|
722
|
+
installGlobal("sessionStorage", { ...storageStub });
|
|
723
|
+
installGlobal("navigator", {
|
|
724
|
+
userAgent: "",
|
|
725
|
+
language: "en",
|
|
726
|
+
languages: ["en"],
|
|
727
|
+
onLine: true,
|
|
728
|
+
cookieEnabled: false,
|
|
729
|
+
hardwareConcurrency: 1,
|
|
730
|
+
maxTouchPoints: 0,
|
|
731
|
+
platform: "",
|
|
732
|
+
vendor: ""
|
|
733
|
+
});
|
|
734
|
+
const NoopObserver = class {
|
|
735
|
+
observe() {}
|
|
736
|
+
unobserve() {}
|
|
737
|
+
disconnect() {}
|
|
738
|
+
takeRecords() {
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
installGlobal("IntersectionObserver", NoopObserver);
|
|
743
|
+
installGlobal("ResizeObserver", NoopObserver);
|
|
744
|
+
installGlobal("MutationObserver", NoopObserver);
|
|
745
|
+
let nextFrameId = 1;
|
|
746
|
+
installGlobal("requestAnimationFrame", () => nextFrameId++);
|
|
747
|
+
installGlobal("cancelAnimationFrame", () => {});
|
|
748
|
+
installGlobal("requestIdleCallback", () => nextFrameId++);
|
|
749
|
+
installGlobal("cancelIdleCallback", () => {});
|
|
750
|
+
installGlobal("CustomEvent", class MockCustomEvent {
|
|
751
|
+
type;
|
|
752
|
+
detail;
|
|
753
|
+
constructor(type, init) {
|
|
754
|
+
this.type = type;
|
|
755
|
+
this.detail = init?.detail ?? null;
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function removeDomShim() {
|
|
760
|
+
setAdapter(null);
|
|
761
|
+
if (!shimInstalled) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
shimInstalled = false;
|
|
765
|
+
if (savedGlobals) {
|
|
766
|
+
for (const g of SHIM_GLOBALS) {
|
|
767
|
+
if (savedGlobals.has(g)) {
|
|
768
|
+
globalThis[g] = savedGlobals.get(g);
|
|
769
|
+
} else {
|
|
770
|
+
delete globalThis[g];
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
savedGlobals = null;
|
|
774
|
+
} else {
|
|
775
|
+
for (const g of SHIM_GLOBALS) {
|
|
776
|
+
delete globalThis[g];
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
for (const g of installedGlobals) {
|
|
780
|
+
delete globalThis[g];
|
|
781
|
+
}
|
|
782
|
+
installedGlobals = [];
|
|
783
|
+
}
|
|
784
|
+
function toVNode(element) {
|
|
785
|
+
if (element instanceof SSRElement) {
|
|
786
|
+
return element.toVNode();
|
|
787
|
+
}
|
|
788
|
+
if (element instanceof SSRDocumentFragment) {
|
|
789
|
+
return {
|
|
790
|
+
tag: "fragment",
|
|
791
|
+
attrs: {},
|
|
792
|
+
children: element.children.map((child) => {
|
|
793
|
+
if (typeof child === "string")
|
|
794
|
+
return child;
|
|
795
|
+
return child.toVNode();
|
|
796
|
+
})
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (typeof element === "object" && "tag" in element) {
|
|
800
|
+
return element;
|
|
801
|
+
}
|
|
802
|
+
return { tag: "span", attrs: {}, children: [String(element)] };
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/html-serializer.ts
|
|
806
|
+
var VOID_ELEMENTS = new Set([
|
|
807
|
+
"area",
|
|
808
|
+
"base",
|
|
809
|
+
"br",
|
|
810
|
+
"col",
|
|
811
|
+
"embed",
|
|
812
|
+
"hr",
|
|
813
|
+
"img",
|
|
814
|
+
"input",
|
|
815
|
+
"link",
|
|
816
|
+
"meta",
|
|
817
|
+
"param",
|
|
818
|
+
"source",
|
|
819
|
+
"track",
|
|
820
|
+
"wbr"
|
|
821
|
+
]);
|
|
822
|
+
var RAW_TEXT_ELEMENTS = new Set(["script", "style"]);
|
|
823
|
+
function escapeHtml(text) {
|
|
824
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
825
|
+
}
|
|
826
|
+
function escapeAttr(value) {
|
|
827
|
+
const str = typeof value === "string" ? value : String(value);
|
|
828
|
+
return str.replace(/&/g, "&").replace(/"/g, """);
|
|
829
|
+
}
|
|
830
|
+
function serializeAttrs(attrs) {
|
|
831
|
+
const parts = [];
|
|
832
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
833
|
+
parts.push(` ${key}="${escapeAttr(value)}"`);
|
|
834
|
+
}
|
|
835
|
+
return parts.join("");
|
|
836
|
+
}
|
|
837
|
+
function isRawHtml(value) {
|
|
838
|
+
return typeof value === "object" && "__raw" in value && value.__raw === true;
|
|
839
|
+
}
|
|
840
|
+
function serializeToHtml(node) {
|
|
841
|
+
if (typeof node === "string") {
|
|
842
|
+
return escapeHtml(node);
|
|
843
|
+
}
|
|
844
|
+
if (isRawHtml(node)) {
|
|
845
|
+
return node.html;
|
|
846
|
+
}
|
|
847
|
+
const { tag, attrs, children } = node;
|
|
848
|
+
const attrStr = serializeAttrs(attrs);
|
|
849
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
850
|
+
return `<${tag}${attrStr}>`;
|
|
851
|
+
}
|
|
852
|
+
const isRawText = RAW_TEXT_ELEMENTS.has(tag);
|
|
853
|
+
const childrenHtml = children.map((child) => {
|
|
854
|
+
if (typeof child === "string" && isRawText) {
|
|
855
|
+
return child;
|
|
856
|
+
}
|
|
857
|
+
return serializeToHtml(child);
|
|
858
|
+
}).join("");
|
|
859
|
+
return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// src/slot-placeholder.ts
|
|
863
|
+
var slotCounter = 0;
|
|
864
|
+
function createSlotPlaceholder(fallback) {
|
|
865
|
+
const id = slotCounter++;
|
|
866
|
+
const placeholder = {
|
|
867
|
+
tag: "div",
|
|
868
|
+
attrs: { id: `v-slot-${id}` },
|
|
869
|
+
children: typeof fallback === "string" ? [fallback] : [fallback],
|
|
870
|
+
_slotId: id
|
|
871
|
+
};
|
|
872
|
+
return placeholder;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/streaming.ts
|
|
876
|
+
var encoder = new TextEncoder;
|
|
877
|
+
var decoder = new TextDecoder;
|
|
878
|
+
function encodeChunk(html) {
|
|
879
|
+
return encoder.encode(html);
|
|
880
|
+
}
|
|
881
|
+
async function streamToString(stream) {
|
|
882
|
+
const reader = stream.getReader();
|
|
883
|
+
const parts = [];
|
|
884
|
+
for (;; ) {
|
|
885
|
+
const { done, value } = await reader.read();
|
|
886
|
+
if (done)
|
|
887
|
+
break;
|
|
888
|
+
parts.push(decoder.decode(value, { stream: true }));
|
|
889
|
+
}
|
|
890
|
+
parts.push(decoder.decode());
|
|
891
|
+
return parts.join("");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/template-chunk.ts
|
|
895
|
+
function createTemplateChunk(slotId, resolvedHtml, nonce) {
|
|
896
|
+
const tmplId = `v-tmpl-${slotId}`;
|
|
897
|
+
const slotRef = `v-slot-${slotId}`;
|
|
898
|
+
const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
899
|
+
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>";
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/render-to-stream.ts
|
|
903
|
+
function isSuspenseNode(node) {
|
|
904
|
+
return typeof node === "object" && "tag" in node && node.tag === "__suspense" && "_resolve" in node;
|
|
905
|
+
}
|
|
906
|
+
function renderToStream(tree, options) {
|
|
907
|
+
const pendingBoundaries = [];
|
|
908
|
+
function walkAndSerialize(node) {
|
|
909
|
+
if (typeof node === "string") {
|
|
910
|
+
return escapeHtml(node);
|
|
911
|
+
}
|
|
912
|
+
if (isRawHtml(node)) {
|
|
913
|
+
return node.html;
|
|
914
|
+
}
|
|
915
|
+
if (isSuspenseNode(node)) {
|
|
916
|
+
const placeholder = createSlotPlaceholder(node._fallback);
|
|
917
|
+
pendingBoundaries.push({
|
|
918
|
+
slotId: placeholder._slotId,
|
|
919
|
+
resolve: node._resolve
|
|
920
|
+
});
|
|
921
|
+
return serializeToHtml(placeholder);
|
|
922
|
+
}
|
|
923
|
+
const { tag, attrs, children } = node;
|
|
924
|
+
const isRawText = RAW_TEXT_ELEMENTS.has(tag);
|
|
925
|
+
const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${escapeAttr(v)}"`).join("");
|
|
926
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
927
|
+
return `<${tag}${attrStr}>`;
|
|
928
|
+
}
|
|
929
|
+
const childrenHtml = children.map((child) => {
|
|
930
|
+
if (typeof child === "string" && isRawText) {
|
|
931
|
+
return child;
|
|
932
|
+
}
|
|
933
|
+
return walkAndSerialize(child);
|
|
934
|
+
}).join("");
|
|
935
|
+
return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
|
|
936
|
+
}
|
|
937
|
+
return new ReadableStream({
|
|
938
|
+
async start(controller) {
|
|
939
|
+
const mainHtml = walkAndSerialize(tree);
|
|
940
|
+
controller.enqueue(encodeChunk(mainHtml));
|
|
941
|
+
if (pendingBoundaries.length > 0) {
|
|
942
|
+
const nonce = options?.nonce;
|
|
943
|
+
const resolutions = pendingBoundaries.map(async (boundary) => {
|
|
944
|
+
try {
|
|
945
|
+
const resolved = await boundary.resolve;
|
|
946
|
+
const resolvedHtml = serializeToHtml(resolved);
|
|
947
|
+
return createTemplateChunk(boundary.slotId, resolvedHtml, nonce);
|
|
948
|
+
} catch (_err) {
|
|
949
|
+
const errorHtml = `<div data-v-ssr-error="true" id="v-ssr-error-${boundary.slotId}">` + "<!--SSR error--></div>";
|
|
950
|
+
return createTemplateChunk(boundary.slotId, errorHtml, nonce);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
const chunks = await Promise.all(resolutions);
|
|
954
|
+
for (const chunk of chunks) {
|
|
955
|
+
controller.enqueue(encodeChunk(chunk));
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
controller.close();
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/ssr-context.ts
|
|
964
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
965
|
+
var ssrStorage = new AsyncLocalStorage;
|
|
966
|
+
function isInSSR() {
|
|
967
|
+
return ssrStorage.getStore() !== undefined;
|
|
968
|
+
}
|
|
969
|
+
function registerSSRQuery(entry) {
|
|
970
|
+
ssrStorage.getStore()?.queries.push(entry);
|
|
971
|
+
}
|
|
972
|
+
function getSSRQueries() {
|
|
973
|
+
return ssrStorage.getStore()?.queries ?? [];
|
|
974
|
+
}
|
|
975
|
+
function setGlobalSSRTimeout(timeout) {
|
|
976
|
+
const store = ssrStorage.getStore();
|
|
977
|
+
if (store)
|
|
978
|
+
store.globalSSRTimeout = timeout;
|
|
979
|
+
}
|
|
980
|
+
function clearGlobalSSRTimeout() {
|
|
981
|
+
const store = ssrStorage.getStore();
|
|
982
|
+
if (store)
|
|
983
|
+
store.globalSSRTimeout = undefined;
|
|
984
|
+
}
|
|
985
|
+
function getGlobalSSRTimeout() {
|
|
986
|
+
return ssrStorage.getStore()?.globalSSRTimeout;
|
|
987
|
+
}
|
|
988
|
+
globalThis.__VERTZ_IS_SSR__ = isInSSR;
|
|
989
|
+
globalThis.__VERTZ_SSR_REGISTER_QUERY__ = registerSSRQuery;
|
|
990
|
+
globalThis.__VERTZ_GET_GLOBAL_SSR_TIMEOUT__ = getGlobalSSRTimeout;
|
|
991
|
+
|
|
992
|
+
// src/ssr-streaming-runtime.ts
|
|
993
|
+
function safeSerialize(data) {
|
|
994
|
+
return JSON.stringify(data).replace(/</g, "\\u003c");
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/ssr-render.ts
|
|
998
|
+
var renderLock = Promise.resolve();
|
|
999
|
+
function withRenderLock(fn) {
|
|
1000
|
+
const prev = renderLock;
|
|
1001
|
+
let release;
|
|
1002
|
+
renderLock = new Promise((r) => {
|
|
1003
|
+
release = r;
|
|
1004
|
+
});
|
|
1005
|
+
return prev.then(fn).finally(() => release());
|
|
1006
|
+
}
|
|
1007
|
+
function resolveAppFactory(module) {
|
|
1008
|
+
const createApp = module.default || module.App;
|
|
1009
|
+
if (typeof createApp !== "function") {
|
|
1010
|
+
throw new Error("App entry must export a default function or named App function");
|
|
1011
|
+
}
|
|
1012
|
+
return createApp;
|
|
1013
|
+
}
|
|
1014
|
+
function collectCSS(themeCss, module) {
|
|
1015
|
+
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1016
|
+
const globalTags = module.styles ? module.styles.map((s) => `<style data-vertz-css>${s}</style>`).join(`
|
|
1017
|
+
`) : "";
|
|
1018
|
+
const alreadyIncluded = new Set;
|
|
1019
|
+
if (themeCss)
|
|
1020
|
+
alreadyIncluded.add(themeCss);
|
|
1021
|
+
if (module.styles) {
|
|
1022
|
+
for (const s of module.styles)
|
|
1023
|
+
alreadyIncluded.add(s);
|
|
1024
|
+
}
|
|
1025
|
+
let componentCss;
|
|
1026
|
+
if (module.getInjectedCSS) {
|
|
1027
|
+
componentCss = module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s));
|
|
1028
|
+
} else {
|
|
1029
|
+
componentCss = [];
|
|
1030
|
+
const head = globalThis.document?.head;
|
|
1031
|
+
if (head instanceof SSRElement) {
|
|
1032
|
+
for (const child of head.children) {
|
|
1033
|
+
if (child instanceof SSRElement && child.tag === "style" && "data-vertz-css" in child.attrs && child.textContent && !alreadyIncluded.has(child.textContent)) {
|
|
1034
|
+
componentCss.push(child.textContent);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const componentStyles = componentCss.map((s) => `<style data-vertz-css>${s}</style>`).join(`
|
|
1040
|
+
`);
|
|
1041
|
+
return [themeTag, globalTags, componentStyles].filter(Boolean).join(`
|
|
1042
|
+
`);
|
|
1043
|
+
}
|
|
1044
|
+
async function ssrRenderToString(module, url, options) {
|
|
1045
|
+
return withRenderLock(() => ssrRenderToStringUnsafe(module, url, options));
|
|
1046
|
+
}
|
|
1047
|
+
async function ssrRenderToStringUnsafe(module, url, options) {
|
|
1048
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
1049
|
+
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
1050
|
+
return ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
|
|
1051
|
+
globalThis.__SSR_URL__ = normalizedUrl;
|
|
1052
|
+
installDomShim();
|
|
1053
|
+
globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
|
|
1054
|
+
try {
|
|
1055
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
1056
|
+
const createApp = resolveAppFactory(module);
|
|
1057
|
+
let themeCss = "";
|
|
1058
|
+
if (module.theme) {
|
|
1059
|
+
try {
|
|
1060
|
+
themeCss = compileTheme(module.theme).css;
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
|
|
1066
|
+
createApp();
|
|
1067
|
+
const queries = getSSRQueries();
|
|
1068
|
+
const resolvedQueries = [];
|
|
1069
|
+
if (queries.length > 0) {
|
|
1070
|
+
await Promise.allSettled(queries.map(({ promise, timeout, resolve, key }) => Promise.race([
|
|
1071
|
+
promise.then((data) => {
|
|
1072
|
+
resolve(data);
|
|
1073
|
+
resolvedQueries.push({ key, data });
|
|
1074
|
+
return "resolved";
|
|
1075
|
+
}),
|
|
1076
|
+
new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
|
|
1077
|
+
])));
|
|
1078
|
+
const store = ssrStorage.getStore();
|
|
1079
|
+
if (store)
|
|
1080
|
+
store.queries = [];
|
|
1081
|
+
}
|
|
1082
|
+
const app = createApp();
|
|
1083
|
+
const vnode = toVNode(app);
|
|
1084
|
+
const stream = renderToStream(vnode);
|
|
1085
|
+
const html = await streamToString(stream);
|
|
1086
|
+
const css = collectCSS(themeCss, module);
|
|
1087
|
+
const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({
|
|
1088
|
+
key,
|
|
1089
|
+
data: JSON.parse(JSON.stringify(data))
|
|
1090
|
+
})) : [];
|
|
1091
|
+
return { html, css, ssrData };
|
|
1092
|
+
} finally {
|
|
1093
|
+
clearGlobalSSRTimeout();
|
|
1094
|
+
removeDomShim();
|
|
1095
|
+
delete globalThis.__SSR_URL__;
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
async function ssrStreamNavQueries(module, url, options) {
|
|
1100
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
1101
|
+
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
1102
|
+
const navTimeout = options?.navSsrTimeout ?? 5000;
|
|
1103
|
+
const queries = await withRenderLock(() => ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
|
|
1104
|
+
globalThis.__SSR_URL__ = normalizedUrl;
|
|
1105
|
+
installDomShim();
|
|
1106
|
+
globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
|
|
1107
|
+
try {
|
|
1108
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
1109
|
+
const createApp = resolveAppFactory(module);
|
|
1110
|
+
globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
|
|
1111
|
+
createApp();
|
|
1112
|
+
const discovered = getSSRQueries();
|
|
1113
|
+
return discovered.map((q) => ({
|
|
1114
|
+
promise: q.promise,
|
|
1115
|
+
timeout: q.timeout || ssrTimeout,
|
|
1116
|
+
resolve: q.resolve,
|
|
1117
|
+
key: q.key
|
|
1118
|
+
}));
|
|
1119
|
+
} finally {
|
|
1120
|
+
clearGlobalSSRTimeout();
|
|
1121
|
+
removeDomShim();
|
|
1122
|
+
delete globalThis.__SSR_URL__;
|
|
1123
|
+
}
|
|
1124
|
+
}));
|
|
1125
|
+
if (queries.length === 0) {
|
|
1126
|
+
const encoder3 = new TextEncoder;
|
|
1127
|
+
return new ReadableStream({
|
|
1128
|
+
start(controller) {
|
|
1129
|
+
controller.enqueue(encoder3.encode(`event: done
|
|
1130
|
+
data: {}
|
|
1131
|
+
|
|
1132
|
+
`));
|
|
1133
|
+
controller.close();
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
const encoder2 = new TextEncoder;
|
|
1138
|
+
let remaining = queries.length;
|
|
1139
|
+
return new ReadableStream({
|
|
1140
|
+
start(controller) {
|
|
1141
|
+
let closed = false;
|
|
1142
|
+
function safeEnqueue(chunk) {
|
|
1143
|
+
if (closed)
|
|
1144
|
+
return;
|
|
1145
|
+
try {
|
|
1146
|
+
controller.enqueue(chunk);
|
|
1147
|
+
} catch {
|
|
1148
|
+
closed = true;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
function safeClose() {
|
|
1152
|
+
if (closed)
|
|
1153
|
+
return;
|
|
1154
|
+
closed = true;
|
|
1155
|
+
try {
|
|
1156
|
+
controller.close();
|
|
1157
|
+
} catch {}
|
|
1158
|
+
}
|
|
1159
|
+
function checkDone() {
|
|
1160
|
+
if (remaining === 0) {
|
|
1161
|
+
safeEnqueue(encoder2.encode(`event: done
|
|
1162
|
+
data: {}
|
|
1163
|
+
|
|
1164
|
+
`));
|
|
1165
|
+
safeClose();
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
for (const { promise, resolve, key } of queries) {
|
|
1169
|
+
let settled = false;
|
|
1170
|
+
promise.then((data) => {
|
|
1171
|
+
if (settled)
|
|
1172
|
+
return;
|
|
1173
|
+
settled = true;
|
|
1174
|
+
resolve(data);
|
|
1175
|
+
const entry = { key, data: JSON.parse(JSON.stringify(data)) };
|
|
1176
|
+
safeEnqueue(encoder2.encode(`event: data
|
|
1177
|
+
data: ${safeSerialize(entry)}
|
|
1178
|
+
|
|
1179
|
+
`));
|
|
1180
|
+
remaining--;
|
|
1181
|
+
checkDone();
|
|
1182
|
+
}, () => {
|
|
1183
|
+
if (settled)
|
|
1184
|
+
return;
|
|
1185
|
+
settled = true;
|
|
1186
|
+
remaining--;
|
|
1187
|
+
checkDone();
|
|
1188
|
+
});
|
|
1189
|
+
setTimeout(() => {
|
|
1190
|
+
if (settled)
|
|
1191
|
+
return;
|
|
1192
|
+
settled = true;
|
|
1193
|
+
remaining--;
|
|
1194
|
+
checkDone();
|
|
1195
|
+
}, navTimeout);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/bun-dev-server.ts
|
|
1202
|
+
function killStaleProcess(targetPort) {
|
|
1203
|
+
try {
|
|
1204
|
+
const output = execSync(`lsof -ti :${targetPort}`, { encoding: "utf8" }).trim();
|
|
1205
|
+
if (!output)
|
|
1206
|
+
return;
|
|
1207
|
+
const pids = output.split(`
|
|
1208
|
+
`).filter(Boolean);
|
|
1209
|
+
const myPid = String(process.pid);
|
|
1210
|
+
for (const pid of pids) {
|
|
1211
|
+
if (pid === myPid)
|
|
1212
|
+
continue;
|
|
1213
|
+
try {
|
|
1214
|
+
process.kill(Number(pid), "SIGTERM");
|
|
1215
|
+
console.log(`[Server] Killed stale process on port ${targetPort} (PID ${pid})`);
|
|
1216
|
+
} catch {}
|
|
1217
|
+
}
|
|
1218
|
+
} catch {}
|
|
1219
|
+
}
|
|
1220
|
+
function createIndexHtmlStasher(projectRoot) {
|
|
1221
|
+
const indexHtmlPath = resolve(projectRoot, "index.html");
|
|
1222
|
+
const indexHtmlBackupPath = resolve(projectRoot, ".vertz", "dev", "index.html.bak");
|
|
1223
|
+
let stashed = false;
|
|
1224
|
+
return {
|
|
1225
|
+
stash() {
|
|
1226
|
+
if (!existsSync(indexHtmlPath) && existsSync(indexHtmlBackupPath)) {
|
|
1227
|
+
renameSync(indexHtmlBackupPath, indexHtmlPath);
|
|
1228
|
+
}
|
|
1229
|
+
if (existsSync(indexHtmlPath)) {
|
|
1230
|
+
mkdirSync(resolve(projectRoot, ".vertz", "dev"), { recursive: true });
|
|
1231
|
+
renameSync(indexHtmlPath, indexHtmlBackupPath);
|
|
1232
|
+
stashed = true;
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
restore() {
|
|
1236
|
+
if (stashed && existsSync(indexHtmlBackupPath)) {
|
|
1237
|
+
renameSync(indexHtmlBackupPath, indexHtmlPath);
|
|
1238
|
+
stashed = false;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function parseHMRAssets(html) {
|
|
1244
|
+
const srcMatch = html.match(/src="(\/_bun\/client\/[^"]+\.js)"/);
|
|
1245
|
+
const bootstrapMatch = html.match(/<script>(\(\(a\)=>\{document\.addEventListener.*?)<\/script>/);
|
|
1246
|
+
return {
|
|
1247
|
+
scriptUrl: srcMatch?.[1] ?? null,
|
|
1248
|
+
bootstrapScript: bootstrapMatch?.[1] ? `<script>${bootstrapMatch[1]}</script>` : null
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function detectEditor(explicit) {
|
|
1252
|
+
if (explicit)
|
|
1253
|
+
return explicit;
|
|
1254
|
+
const env = process.env.VERTZ_EDITOR || process.env.EDITOR || "";
|
|
1255
|
+
const lower = env.toLowerCase();
|
|
1256
|
+
if (lower.includes("cursor"))
|
|
1257
|
+
return "cursor";
|
|
1258
|
+
if (lower.includes("zed"))
|
|
1259
|
+
return "zed";
|
|
1260
|
+
if (lower.includes("webstorm") || lower.includes("idea"))
|
|
1261
|
+
return "webstorm";
|
|
1262
|
+
return "vscode";
|
|
1263
|
+
}
|
|
1264
|
+
function editorHrefJs(editor) {
|
|
1265
|
+
if (editor === "webstorm" || editor === "idea") {
|
|
1266
|
+
return `V._editorHref=function(f,l){if(!f)return'';return'${editor}://open?file='+encodeURI(f)+(l?'&line='+l:'')};`;
|
|
1267
|
+
}
|
|
1268
|
+
return `V._editorHref=function(f,l){if(!f)return'';return'${editor}://file/'+encodeURI(f)+(l?':'+l:'')};`;
|
|
1269
|
+
}
|
|
1270
|
+
function buildErrorChannelScript(editor) {
|
|
1271
|
+
return [
|
|
1272
|
+
"<style>bun-hmr{display:none!important}</style>",
|
|
1273
|
+
"<script>(function(){",
|
|
1274
|
+
"var V=window.__vertz_overlay={};",
|
|
1275
|
+
editorHrefJs(editor),
|
|
1276
|
+
"V._ws=null;",
|
|
1277
|
+
"V._src=null;",
|
|
1278
|
+
"V._hadClientError=false;",
|
|
1279
|
+
"V._needsReload=false;",
|
|
1280
|
+
'var rts=sessionStorage.getItem("__vertz_recovering");',
|
|
1281
|
+
"V._recovering=rts&&(Date.now()-Number(rts)<10000);",
|
|
1282
|
+
'if(V._recovering)sessionStorage.removeItem("__vertz_recovering");',
|
|
1283
|
+
"var _reload=location.reload.bind(location);",
|
|
1284
|
+
"try{location.reload=function(){if(V._src){V._needsReload=true;return}_reload()}}catch(e){}",
|
|
1285
|
+
"V.esc=function(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')};",
|
|
1286
|
+
"V.formatErrors=function(errs){",
|
|
1287
|
+
`if(!errs||!errs.length)return'<p style="margin:0;color:var(--ve-muted);font-size:12px">Check your terminal for details.</p>';`,
|
|
1288
|
+
"var groups=[],seen={};",
|
|
1289
|
+
"errs.forEach(function(e){",
|
|
1290
|
+
"var k=(e.file||'')+'|'+(e.line||0);",
|
|
1291
|
+
"if(!seen[k]){seen[k]={file:e.file,absFile:e.absFile,line:e.line,lineText:e.lineText,msgs:[]};groups.push(seen[k])}",
|
|
1292
|
+
"seen[k].msgs.push({message:e.message,column:e.column})});",
|
|
1293
|
+
"return groups.map(function(g){var h='';",
|
|
1294
|
+
"if(g.file){",
|
|
1295
|
+
"var loc=V.esc(g.file)+(g.line?':'+g.line:'');",
|
|
1296
|
+
"var href=V._editorHref(g.absFile,g.line);",
|
|
1297
|
+
`h+=href?'<a href="'+href+'" style="color:var(--ve-link);font-size:12px;text-decoration:underline;text-underline-offset:2px">'+loc+'</a>'` + `:'<span style="color:var(--ve-link);font-size:12px">'+loc+'</span>';`,
|
|
1298
|
+
"h+='<br>'}",
|
|
1299
|
+
`g.msgs.forEach(function(m){h+='<div style="color:var(--ve-error);font-size:12px;margin:2px 0">'+V.esc(m.message)+'</div>'});`,
|
|
1300
|
+
`if(g.lineText){h+='<pre style="margin:4px 0 0;color:var(--ve-code);font-size:11px;background:var(--ve-code-bg);border-radius:4px;padding:6px 8px;overflow-x:auto;border:1px solid var(--ve-border)">'+V.esc(g.lineText)+'</pre>'}`,
|
|
1301
|
+
`return'<div style="margin-bottom:10px">'+h+'</div>'}).join('')};`,
|
|
1302
|
+
"V.formatStack=function(frames){",
|
|
1303
|
+
"if(!frames||!frames.length)return'';",
|
|
1304
|
+
`var h='<div style="margin-top:8px;border-top:1px solid var(--ve-border);padding-top:8px">';`,
|
|
1305
|
+
"var visible=frames.slice(0,3);var hidden=frames.slice(3);",
|
|
1306
|
+
"visible.forEach(function(f){h+=V._renderFrame(f)});",
|
|
1307
|
+
`if(hidden.length){h+='<details style="margin-top:2px"><summary style="color:var(--ve-muted);font-size:11px;cursor:pointer;list-style:none">'`,
|
|
1308
|
+
"+hidden.length+' more frame'+(hidden.length>1?'s':'')+'</summary>';",
|
|
1309
|
+
"hidden.forEach(function(f){h+=V._renderFrame(f)});h+='</details>'}",
|
|
1310
|
+
"return h+'</div>'};",
|
|
1311
|
+
"V._renderFrame=function(f){",
|
|
1312
|
+
"var name=f.functionName||'(anonymous)';",
|
|
1313
|
+
"var loc=V.esc(f.file)+(f.line?':'+f.line:'');",
|
|
1314
|
+
"var isSrc=f.file&&f.file.indexOf('src/')!==-1&&f.file.indexOf('node_modules')===-1;",
|
|
1315
|
+
"var color=isSrc?'var(--ve-fg)':'var(--ve-muted)';",
|
|
1316
|
+
"var href=V._editorHref(f.absFile,f.line);",
|
|
1317
|
+
`var link=href?'<a href="'+href+'" style="color:var(--ve-link);text-decoration:underline;text-underline-offset:2px">'+loc+'</a>':'<span>'+loc+'</span>';`,
|
|
1318
|
+
`return'<div style="font-size:11px;color:'+color+';margin:1px 0;font-family:ui-monospace,monospace">'+V.esc(name)+' '+link+'</div>'};`,
|
|
1319
|
+
"V.removeOverlay=function(){V._src=null;var e=document.getElementById('__vertz_error');if(e)e.remove();" + "var d=document.getElementById('__vertz_error_data');if(d)d.remove()};",
|
|
1320
|
+
"V.showOverlay=function(t,body,payload,src){",
|
|
1321
|
+
"V.removeOverlay();",
|
|
1322
|
+
"V._src=src||'ws';",
|
|
1323
|
+
"var d=document,c=d.createElement('div');",
|
|
1324
|
+
"c.id='__vertz_error';",
|
|
1325
|
+
"c.style.cssText='",
|
|
1326
|
+
"--ve-bg:hsl(0 0% 100%);--ve-fg:hsl(0 0% 9%);--ve-muted:hsl(0 0% 45%);",
|
|
1327
|
+
"--ve-error:hsl(0 72% 51%);--ve-link:hsl(221 83% 53%);--ve-border:hsl(0 0% 90%);",
|
|
1328
|
+
"--ve-code:hsl(24 70% 45%);--ve-code-bg:hsl(0 0% 97%);--ve-btn:hsl(0 0% 9%);--ve-btn-fg:hsl(0 0% 100%);",
|
|
1329
|
+
"position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;",
|
|
1330
|
+
"background:var(--ve-bg);color:var(--ve-fg);border-radius:8px;padding:14px 16px;",
|
|
1331
|
+
"max-width:480px;width:calc(100% - 32px);font-family:ui-sans-serif,system-ui,sans-serif;",
|
|
1332
|
+
"box-shadow:0 4px 24px rgba(0,0,0,0.12),0 1px 3px rgba(0,0,0,0.08);border:1px solid var(--ve-border)';",
|
|
1333
|
+
"var st=d.createElement('style');",
|
|
1334
|
+
"st.textContent='@media(prefers-color-scheme:dark){#__vertz_error{",
|
|
1335
|
+
"--ve-bg:hsl(0 0% 7%);--ve-fg:hsl(0 0% 93%);--ve-muted:hsl(0 0% 55%);",
|
|
1336
|
+
"--ve-error:hsl(0 72% 65%);--ve-link:hsl(217 91% 70%);--ve-border:hsl(0 0% 18%);",
|
|
1337
|
+
"--ve-code:hsl(36 80% 65%);--ve-code-bg:hsl(0 0% 11%);--ve-btn:hsl(0 0% 93%);--ve-btn-fg:hsl(0 0% 7%)}}';",
|
|
1338
|
+
"d.head.appendChild(st);",
|
|
1339
|
+
`c.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'`,
|
|
1340
|
+
`+'<span style="font-size:13px;font-weight:600;color:var(--ve-error)">'+V.esc(t)+'</span>'`,
|
|
1341
|
+
`+'<button id="__vertz_retry" style="background:var(--ve-btn);color:var(--ve-btn-fg);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;font-weight:500">Retry</button>'`,
|
|
1342
|
+
"+'</div>'+body;",
|
|
1343
|
+
"(d.body||d.documentElement).appendChild(c);",
|
|
1344
|
+
"d.getElementById('__vertz_retry').onclick=function(){location.reload()};",
|
|
1345
|
+
"if(payload){var s=d.createElement('script');s.type='application/json';s.id='__vertz_error_data';s.textContent=JSON.stringify(payload);(d.body||d.documentElement).appendChild(s)}};",
|
|
1346
|
+
"var delay=1000,maxDelay=30000;",
|
|
1347
|
+
"function connect(){",
|
|
1348
|
+
"var p=location.protocol==='https:'?'wss:':'ws:';",
|
|
1349
|
+
"var ws=new WebSocket(p+'//'+location.host+'/__vertz_errors');",
|
|
1350
|
+
"V._ws=ws;",
|
|
1351
|
+
"ws.onmessage=function(e){",
|
|
1352
|
+
"try{var m=JSON.parse(e.data);",
|
|
1353
|
+
"if(m.type==='error'){",
|
|
1354
|
+
"if(V._recovering)return;",
|
|
1355
|
+
"V.showOverlay(m.category==='build'?'Build failed':m.category==='ssr'?'SSR error':m.category==='resolve'?'Module not found':'Runtime error',V.formatErrors(m.errors)+V.formatStack(m.parsedStack),m,'ws')}",
|
|
1356
|
+
"else if(m.type==='clear'){",
|
|
1357
|
+
"if(V._needsReload){V._needsReload=false;V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
|
|
1358
|
+
"var a=document.getElementById('app');",
|
|
1359
|
+
"if(!a||a.innerHTML.length<50){V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
|
|
1360
|
+
"if(V._hadClientError)return;",
|
|
1361
|
+
"V.removeOverlay()}",
|
|
1362
|
+
"else if(m.type==='connected'){delay=1000}",
|
|
1363
|
+
"}catch(ex){}};",
|
|
1364
|
+
"ws.onclose=function(){V._ws=null;setTimeout(function(){delay=Math.min(delay*2,maxDelay);connect()},delay)};",
|
|
1365
|
+
"ws.onerror=function(){ws.close()}}",
|
|
1366
|
+
"connect();",
|
|
1367
|
+
"V._sendResolveStack=function(stack,msg){",
|
|
1368
|
+
'if(V._ws&&V._ws.readyState===1){try{V._ws.send(JSON.stringify({type:"resolve-stack",stack:stack,message:msg}))}catch(e){}}};',
|
|
1369
|
+
"function showRuntimeError(title,errors,payload){",
|
|
1370
|
+
"var a=document.getElementById('app');",
|
|
1371
|
+
"if(V._recovering&&a&&a.innerHTML.length>50)return;",
|
|
1372
|
+
"if(V._recovering)V._recovering=false;",
|
|
1373
|
+
"V._hadClientError=true;",
|
|
1374
|
+
"V.showOverlay(title,V.formatErrors(errors),payload,'client')}",
|
|
1375
|
+
"if(V._recovering){setTimeout(function(){V._recovering=false},5000)}",
|
|
1376
|
+
"window.addEventListener('error',function(e){",
|
|
1377
|
+
"var msg=e.message||String(e.error);",
|
|
1378
|
+
"var stk=e.error&&e.error.stack;",
|
|
1379
|
+
"if(stk){V._sendResolveStack(stk,msg)}",
|
|
1380
|
+
"var f=e.filename,isBundled=f&&(f.indexOf('/_bun/')!==-1||f.indexOf('blob:')!==-1);",
|
|
1381
|
+
"var errInfo=isBundled?{message:msg}:{message:msg,file:f,line:e.lineno,column:e.colno};",
|
|
1382
|
+
"showRuntimeError('Runtime error',[errInfo],{type:'error',category:'runtime',errors:[errInfo]})});",
|
|
1383
|
+
"window.addEventListener('unhandledrejection',function(e){",
|
|
1384
|
+
"var m=e.reason instanceof Error?e.reason.message:String(e.reason);",
|
|
1385
|
+
"var stk=e.reason&&e.reason.stack;",
|
|
1386
|
+
"if(stk){V._sendResolveStack(stk,m)}",
|
|
1387
|
+
"showRuntimeError('Runtime error',[{message:m}],{type:'error',category:'runtime',errors:[{message:m}]})});",
|
|
1388
|
+
"var hmrErr=false,origCE=console.error,origCL=console.log;",
|
|
1389
|
+
"console.error=function(){",
|
|
1390
|
+
"var t=Array.prototype.join.call(arguments,' ');",
|
|
1391
|
+
"var hmr=t.match(/\\[vertz-hmr\\] Error re-mounting (\\w+): ([\\s\\S]*?)(?:\\n\\s+at |$)/);",
|
|
1392
|
+
"if(hmr){hmrErr=true;V._hadClientError=true;",
|
|
1393
|
+
"V.showOverlay('Runtime error',V.formatErrors([{message:hmr[2].split('\\n')[0]}]),{type:'error',category:'runtime',errors:[{message:hmr[2].split('\\n')[0]}]},'client')}",
|
|
1394
|
+
"origCE.apply(console,arguments)};",
|
|
1395
|
+
"console.log=function(){",
|
|
1396
|
+
"var t=Array.prototype.join.call(arguments,' ');",
|
|
1397
|
+
"if(t.indexOf('[vertz-hmr] Hot updated:')!==-1){",
|
|
1398
|
+
"if(!hmrErr&&V._src==='client'){",
|
|
1399
|
+
"V._hadClientError=false;V.removeOverlay();setTimeout(function(){var a=document.getElementById('app');if(!a||a.innerHTML.length<50){V._needsReload=true}},500)}",
|
|
1400
|
+
"hmrErr=false}",
|
|
1401
|
+
"origCL.apply(console,arguments)};",
|
|
1402
|
+
"})()</script>"
|
|
1403
|
+
].join("");
|
|
1404
|
+
}
|
|
1405
|
+
var RELOAD_GUARD_SCRIPT = `<script>(function(){var K="__vertz_reload_count",T="__vertz_reload_ts",s=sessionStorage,n=parseInt(s.getItem(K)||"0",10),t=parseInt(s.getItem(T)||"0",10),now=Date.now();if(now-t<100){n++}else{n=1}s.setItem(K,String(n));s.setItem(T,String(now));if(n>10){window.stop();s.removeItem(K);s.removeItem(T);var d=document,o=d.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.6)";var c=d.createElement("div");c.style.cssText="background:#fff;color:#1a1a1a;border-radius:12px;padding:32px;max-width:480px;width:90%;font-family:system-ui,sans-serif;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3)";c.innerHTML='<div style="font-size:40px;margin-bottom:16px">⚠️</div><h2 style="margin:0 0 8px;font-size:20px">Dev server connection lost</h2><p style="margin:0 0 20px;color:#666;font-size:14px;line-height:1.5">The page reloaded 10+ times in rapid succession. This usually means the dev server stopped or a build failed.</p><button id="__vertz_retry" style="background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 24px;font-size:14px;cursor:pointer">Retry</button>';o.appendChild(c);(d.body||d.documentElement).appendChild(o);d.getElementById("__vertz_retry").onclick=function(){location.href=location.href}}else{setTimeout(function(){s.removeItem(K);s.removeItem(T)},5e3)}})()</script>`;
|
|
1406
|
+
function generateSSRPageHtml({
|
|
1407
|
+
title,
|
|
1408
|
+
css,
|
|
1409
|
+
bodyHtml,
|
|
1410
|
+
ssrData,
|
|
1411
|
+
scriptTag,
|
|
1412
|
+
editor = "vscode"
|
|
1413
|
+
}) {
|
|
1414
|
+
const ssrDataScript = ssrData.length > 0 ? `<script>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>` : "";
|
|
1415
|
+
return `<!doctype html>
|
|
1416
|
+
<html lang="en">
|
|
1417
|
+
<head>
|
|
1418
|
+
<meta charset="UTF-8" />
|
|
1419
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1420
|
+
<title>${title}</title>
|
|
1421
|
+
${css}
|
|
1422
|
+
${buildErrorChannelScript(editor)}
|
|
1423
|
+
${RELOAD_GUARD_SCRIPT}
|
|
1424
|
+
</head>
|
|
1425
|
+
<body>
|
|
1426
|
+
<div id="app">${bodyHtml}</div>
|
|
1427
|
+
${ssrDataScript}
|
|
1428
|
+
${scriptTag}
|
|
1429
|
+
</body>
|
|
1430
|
+
</html>`;
|
|
1431
|
+
}
|
|
1432
|
+
function createFetchInterceptor({
|
|
1433
|
+
apiHandler,
|
|
1434
|
+
origin,
|
|
1435
|
+
skipSSRPaths,
|
|
1436
|
+
originalFetch
|
|
1437
|
+
}) {
|
|
1438
|
+
const intercepted = (input, init) => {
|
|
1439
|
+
const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1440
|
+
const isRelative = rawUrl.startsWith("/");
|
|
1441
|
+
const fetchPath = isRelative ? rawUrl.split("?")[0] ?? "/" : new URL(rawUrl).pathname;
|
|
1442
|
+
const isLocal = isRelative || new URL(rawUrl).origin === origin;
|
|
1443
|
+
if (isLocal && skipSSRPaths.some((p) => fetchPath.startsWith(p))) {
|
|
1444
|
+
const absoluteUrl = isRelative ? `${origin}${rawUrl}` : rawUrl;
|
|
1445
|
+
const req = new Request(absoluteUrl, init);
|
|
1446
|
+
return apiHandler(req);
|
|
1447
|
+
}
|
|
1448
|
+
return originalFetch(input, init);
|
|
1449
|
+
};
|
|
1450
|
+
intercepted.preconnect = originalFetch.preconnect;
|
|
1451
|
+
return intercepted;
|
|
1452
|
+
}
|
|
1453
|
+
var BUILD_ERROR_LOADER = [
|
|
1454
|
+
"(function(){",
|
|
1455
|
+
"var el=document.querySelector('[data-bun-dev-server-script]');if(!el)return;var src=el.src;",
|
|
1456
|
+
"var V=window.__vertz_overlay||{};",
|
|
1457
|
+
`var formatErrors=V.formatErrors||function(){return'<p style="margin:0;color:#666;font-size:12px">Check your terminal for details.</p>'};`,
|
|
1458
|
+
"var showOverlay=V.showOverlay||function(t,body){",
|
|
1459
|
+
"var e=document.getElementById('__vertz_error');if(e)e.remove();",
|
|
1460
|
+
"var d=document,c=d.createElement('div');c.id='__vertz_error';",
|
|
1461
|
+
"c.style.cssText='position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#fff;color:#1a1a1a;border-radius:8px;padding:14px 16px;max-width:480px;width:calc(100% - 32px);font-family:system-ui,sans-serif;box-shadow:0 4px 24px rgba(0,0,0,0.12);border:1px solid #e5e5e5';",
|
|
1462
|
+
`c.innerHTML='<div style="margin-bottom:10px;font-size:13px;font-weight:600;color:#dc2626">'+t+'</div>'+body+'<button onclick="location.reload()" style="margin-top:8px;background:#1a1a1a;color:#fff;border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer">Retry</button>';`,
|
|
1463
|
+
"(d.body||d.documentElement).appendChild(c)};",
|
|
1464
|
+
"el.remove();",
|
|
1465
|
+
"fetch(src).then(function(r){return r.text()}).then(function(t){",
|
|
1466
|
+
"if(t.trimStart().startsWith('try{location.reload()}')){",
|
|
1467
|
+
"fetch('/__vertz_build_check').then(function(r){return r.json()}).then(function(j){",
|
|
1468
|
+
"if(j.errors&&j.errors.length>0){showOverlay('Build failed',formatErrors(j.errors),j)}",
|
|
1469
|
+
"else{var rk='__vertz_stub_retry',rc=+(sessionStorage.getItem(rk)||0);",
|
|
1470
|
+
"if(rc<3){sessionStorage.setItem(rk,String(rc+1));setTimeout(function(){location.reload()},2000)}",
|
|
1471
|
+
`else{sessionStorage.removeItem(rk);showOverlay('Build failed','<p style="margin:0;color:#666;font-size:12px">Could not load client bundle. Try reloading manually.</p>')}}`,
|
|
1472
|
+
"}).catch(function(){",
|
|
1473
|
+
`showOverlay('Build failed','<p style="margin:0;color:#666;font-size:12px">Check your terminal for details.</p>')})}`,
|
|
1474
|
+
"else{sessionStorage.removeItem('__vertz_stub_retry');var s=document.createElement('script');s.type='module';s.crossOrigin='';s.src=src;document.body.appendChild(s)}",
|
|
1475
|
+
`}).catch(function(){showOverlay('Dev server unreachable','<p style="margin:0;color:#666;font-size:12px">Could not connect. Is it still running?</p>')})`,
|
|
1476
|
+
"})()"
|
|
1477
|
+
].join("");
|
|
1478
|
+
function buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc) {
|
|
1479
|
+
if (bundledScriptUrl) {
|
|
1480
|
+
const placeholder = `<script type="text/plain" crossorigin src="${bundledScriptUrl}" data-bun-dev-server-script></script>`;
|
|
1481
|
+
const bootstrap = hmrBootstrapScript ? `
|
|
1482
|
+
${hmrBootstrapScript}` : "";
|
|
1483
|
+
const loader = `<script>${BUILD_ERROR_LOADER}</script>`;
|
|
1484
|
+
return `${placeholder}${bootstrap}
|
|
1485
|
+
${loader}`;
|
|
1486
|
+
}
|
|
1487
|
+
return `<script type="module" src="${clientSrc}"></script>`;
|
|
1488
|
+
}
|
|
1489
|
+
function createBunDevServer(options) {
|
|
1490
|
+
const {
|
|
1491
|
+
entry,
|
|
1492
|
+
port = 3000,
|
|
1493
|
+
host = "localhost",
|
|
1494
|
+
apiHandler,
|
|
1495
|
+
skipSSRPaths = ["/api/"],
|
|
1496
|
+
openapi,
|
|
1497
|
+
clientEntry: clientEntryOption,
|
|
1498
|
+
title = "Vertz App",
|
|
1499
|
+
projectRoot = process.cwd(),
|
|
1500
|
+
logRequests = true,
|
|
1501
|
+
editor: editorOption
|
|
1502
|
+
} = options;
|
|
1503
|
+
const editor = detectEditor(editorOption);
|
|
1504
|
+
const devDir = resolve(projectRoot, ".vertz", "dev");
|
|
1505
|
+
mkdirSync(devDir, { recursive: true });
|
|
1506
|
+
const logger = createDebugLogger(devDir);
|
|
1507
|
+
const diagnostics = new DiagnosticsCollector;
|
|
1508
|
+
let server = null;
|
|
1509
|
+
let srcWatcherRef = null;
|
|
1510
|
+
let refreshTimeout = null;
|
|
1511
|
+
let stopped = false;
|
|
1512
|
+
const wsClients = new Set;
|
|
1513
|
+
let currentError = null;
|
|
1514
|
+
const sourceMapResolver = createSourceMapResolver(projectRoot);
|
|
1515
|
+
let clearGraceUntil = 0;
|
|
1516
|
+
let runtimeDebounceTimer = null;
|
|
1517
|
+
let pendingRuntimeError = null;
|
|
1518
|
+
function broadcastError(category, errors) {
|
|
1519
|
+
if (currentError?.category === "build" && category !== "build") {
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (category === "runtime" && Date.now() < clearGraceUntil) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (category === "runtime") {
|
|
1526
|
+
if (!pendingRuntimeError || errors.some((e) => e.file)) {
|
|
1527
|
+
pendingRuntimeError = errors;
|
|
1528
|
+
}
|
|
1529
|
+
if (!runtimeDebounceTimer) {
|
|
1530
|
+
runtimeDebounceTimer = setTimeout(() => {
|
|
1531
|
+
runtimeDebounceTimer = null;
|
|
1532
|
+
const errs = pendingRuntimeError;
|
|
1533
|
+
pendingRuntimeError = null;
|
|
1534
|
+
if (errs) {
|
|
1535
|
+
currentError = { category: "runtime", errors: errs };
|
|
1536
|
+
const msg2 = JSON.stringify({ type: "error", category: "runtime", errors: errs });
|
|
1537
|
+
for (const ws of wsClients) {
|
|
1538
|
+
ws.sendText(msg2);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}, 100);
|
|
1542
|
+
}
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
currentError = { category, errors };
|
|
1546
|
+
diagnostics.recordError(category, errors[0]?.message ?? "");
|
|
1547
|
+
logger.log("ws", "broadcast-error", { category, errorCount: errors.length });
|
|
1548
|
+
const msg = JSON.stringify({ type: "error", category, errors });
|
|
1549
|
+
for (const ws of wsClients) {
|
|
1550
|
+
ws.sendText(msg);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
function clearError() {
|
|
1554
|
+
if (currentError === null && !pendingRuntimeError)
|
|
1555
|
+
return;
|
|
1556
|
+
currentError = null;
|
|
1557
|
+
diagnostics.recordErrorClear();
|
|
1558
|
+
if (runtimeDebounceTimer) {
|
|
1559
|
+
clearTimeout(runtimeDebounceTimer);
|
|
1560
|
+
runtimeDebounceTimer = null;
|
|
1561
|
+
pendingRuntimeError = null;
|
|
1562
|
+
}
|
|
1563
|
+
clearGraceUntil = Date.now() + 5000;
|
|
1564
|
+
const msg = JSON.stringify({ type: "clear" });
|
|
1565
|
+
for (const ws of wsClients) {
|
|
1566
|
+
ws.sendText(msg);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function clearErrorForFileChange() {
|
|
1570
|
+
if (currentError === null && !pendingRuntimeError)
|
|
1571
|
+
return;
|
|
1572
|
+
currentError = null;
|
|
1573
|
+
diagnostics.recordErrorClear();
|
|
1574
|
+
if (runtimeDebounceTimer) {
|
|
1575
|
+
clearTimeout(runtimeDebounceTimer);
|
|
1576
|
+
runtimeDebounceTimer = null;
|
|
1577
|
+
pendingRuntimeError = null;
|
|
1578
|
+
}
|
|
1579
|
+
clearGraceUntil = 0;
|
|
1580
|
+
const msg = JSON.stringify({ type: "clear" });
|
|
1581
|
+
for (const ws of wsClients) {
|
|
1582
|
+
ws.sendText(msg);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
let lastBuildError = "";
|
|
1586
|
+
let lastBroadcastedError = "";
|
|
1587
|
+
let lastChangedFile = "";
|
|
1588
|
+
const resolvePatterns = ["Could not resolve", "Module not found", "Cannot find module"];
|
|
1589
|
+
const hmrErrorPattern = /\[vertz-hmr\] Error re-mounting (\w+): ([\s\S]*?)(?:\n\s+at |$)/;
|
|
1590
|
+
const frontendErrorPattern = /\x1b\[31mfrontend\x1b\[0m ([\s\S]*?)(?:\n\s+from browser|$)/;
|
|
1591
|
+
function parseSourceFromStack(text) {
|
|
1592
|
+
const stackLines = text.split(`
|
|
1593
|
+
`);
|
|
1594
|
+
const srcLine = stackLines.find((l) => l.includes("/src/") && !l.includes("node_modules"));
|
|
1595
|
+
if (srcLine) {
|
|
1596
|
+
const locMatch = srcLine.match(/(?:at .+? \(|at )(.+?):(\d+):(\d+)/);
|
|
1597
|
+
if (locMatch) {
|
|
1598
|
+
const absFile = locMatch[1];
|
|
1599
|
+
const line = Number(locMatch[2]);
|
|
1600
|
+
const lineText = absFile ? readLineText(absFile, line) : undefined;
|
|
1601
|
+
return {
|
|
1602
|
+
message: "",
|
|
1603
|
+
file: absFile?.replace(projectRoot, "").replace(/^\//, ""),
|
|
1604
|
+
absFile,
|
|
1605
|
+
line,
|
|
1606
|
+
column: Number(locMatch[3]),
|
|
1607
|
+
lineText
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
return { message: "" };
|
|
1612
|
+
}
|
|
1613
|
+
const origConsoleError = console.error;
|
|
1614
|
+
console.error = (...args) => {
|
|
1615
|
+
const text = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
|
|
1616
|
+
if (!text.startsWith("[Server]")) {
|
|
1617
|
+
lastBuildError = text;
|
|
1618
|
+
if (resolvePatterns.some((p) => text.includes(p)) && text !== lastBroadcastedError) {
|
|
1619
|
+
lastBroadcastedError = text;
|
|
1620
|
+
broadcastError("resolve", [{ message: text }]);
|
|
1621
|
+
} else {
|
|
1622
|
+
const hmrMatch = text.match(hmrErrorPattern);
|
|
1623
|
+
if (hmrMatch && text !== lastBroadcastedError) {
|
|
1624
|
+
lastBroadcastedError = text;
|
|
1625
|
+
const component = hmrMatch[1];
|
|
1626
|
+
const errorMsg = hmrMatch[2]?.trim() ?? "Unknown error";
|
|
1627
|
+
const loc = parseSourceFromStack(text);
|
|
1628
|
+
if (!loc.file && lastChangedFile) {
|
|
1629
|
+
loc.file = lastChangedFile;
|
|
1630
|
+
loc.absFile = resolve(projectRoot, lastChangedFile);
|
|
1631
|
+
}
|
|
1632
|
+
broadcastError("runtime", [
|
|
1633
|
+
{
|
|
1634
|
+
message: `${errorMsg} (in ${component})`,
|
|
1635
|
+
file: loc.file,
|
|
1636
|
+
absFile: loc.absFile,
|
|
1637
|
+
line: loc.line,
|
|
1638
|
+
column: loc.column,
|
|
1639
|
+
lineText: loc.lineText
|
|
1640
|
+
}
|
|
1641
|
+
]);
|
|
1642
|
+
} else {
|
|
1643
|
+
const feMatch = text.match(frontendErrorPattern);
|
|
1644
|
+
if (feMatch && text !== lastBroadcastedError) {
|
|
1645
|
+
lastBroadcastedError = text;
|
|
1646
|
+
const errorMsg = feMatch[1]?.split(`
|
|
1647
|
+
`)[0]?.trim() ?? "Unknown error";
|
|
1648
|
+
const loc = parseSourceFromStack(text);
|
|
1649
|
+
if (!loc.file && lastChangedFile) {
|
|
1650
|
+
loc.file = lastChangedFile;
|
|
1651
|
+
loc.absFile = resolve(projectRoot, lastChangedFile);
|
|
1652
|
+
}
|
|
1653
|
+
broadcastError("runtime", [
|
|
1654
|
+
{
|
|
1655
|
+
message: errorMsg,
|
|
1656
|
+
file: loc.file,
|
|
1657
|
+
absFile: loc.absFile,
|
|
1658
|
+
line: loc.line,
|
|
1659
|
+
column: loc.column,
|
|
1660
|
+
lineText: loc.lineText
|
|
1661
|
+
}
|
|
1662
|
+
]);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
origConsoleError.apply(console, args);
|
|
1668
|
+
};
|
|
1669
|
+
const indexHtmlStasher = createIndexHtmlStasher(projectRoot);
|
|
1670
|
+
let cachedSpec = null;
|
|
1671
|
+
let specWatcher = null;
|
|
1672
|
+
const loadOpenAPISpec = () => {
|
|
1673
|
+
if (!openapi)
|
|
1674
|
+
return null;
|
|
1675
|
+
try {
|
|
1676
|
+
const specContent = readFileSync2(openapi.specPath, "utf-8");
|
|
1677
|
+
return JSON.parse(specContent);
|
|
1678
|
+
} catch (err) {
|
|
1679
|
+
console.error("[Server] Error reading OpenAPI spec:", err);
|
|
1680
|
+
return null;
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
const setupOpenAPIWatcher = () => {
|
|
1684
|
+
if (!openapi || !existsSync(openapi.specPath))
|
|
1685
|
+
return;
|
|
1686
|
+
cachedSpec = loadOpenAPISpec();
|
|
1687
|
+
if (cachedSpec === null)
|
|
1688
|
+
return;
|
|
1689
|
+
try {
|
|
1690
|
+
const specDir = dirname(openapi.specPath);
|
|
1691
|
+
const specFile = openapi.specPath.split("/").pop() || "openapi.json";
|
|
1692
|
+
specWatcher = watch(specDir, { persistent: false }, (eventType, filename) => {
|
|
1693
|
+
if (filename === specFile && (eventType === "change" || eventType === "rename")) {
|
|
1694
|
+
if (logRequests) {
|
|
1695
|
+
console.log("[Server] OpenAPI spec file changed, reloading...");
|
|
1696
|
+
}
|
|
1697
|
+
cachedSpec = loadOpenAPISpec();
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
console.warn("[Server] Could not set up file watcher for OpenAPI spec:", err);
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
const serveOpenAPISpec = () => {
|
|
1705
|
+
if (cachedSpec) {
|
|
1706
|
+
return new Response(JSON.stringify(cachedSpec), {
|
|
1707
|
+
headers: { "Content-Type": "application/json" }
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
if (openapi && existsSync(openapi.specPath)) {
|
|
1711
|
+
cachedSpec = loadOpenAPISpec();
|
|
1712
|
+
if (cachedSpec) {
|
|
1713
|
+
return new Response(JSON.stringify(cachedSpec), {
|
|
1714
|
+
headers: { "Content-Type": "application/json" }
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return new Response("OpenAPI spec not found", { status: 404 });
|
|
1719
|
+
};
|
|
1720
|
+
async function start() {
|
|
1721
|
+
indexHtmlStasher.stash();
|
|
1722
|
+
const { plugin } = await Promise.resolve(globalThis.Bun);
|
|
1723
|
+
const { createVertzBunPlugin } = await import("./bun-plugin/index.js");
|
|
1724
|
+
const entryPath = resolve(projectRoot, entry);
|
|
1725
|
+
const rawClientSrc = clientEntryOption ?? entry;
|
|
1726
|
+
const clientSrc = rawClientSrc.replace(/^\.\//, "/");
|
|
1727
|
+
plugin({
|
|
1728
|
+
name: "vertz-ssr-jsx-swap",
|
|
1729
|
+
setup(build) {
|
|
1730
|
+
build.onResolve({ filter: /^@vertz\/ui\/jsx-runtime$/ }, () => ({
|
|
1731
|
+
path: "@vertz/ui-server/jsx-runtime",
|
|
1732
|
+
external: false
|
|
1733
|
+
}));
|
|
1734
|
+
build.onResolve({ filter: /^@vertz\/ui\/jsx-dev-runtime$/ }, () => ({
|
|
1735
|
+
path: "@vertz/ui-server/jsx-runtime",
|
|
1736
|
+
external: false
|
|
1737
|
+
}));
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
const { plugin: serverPlugin } = createVertzBunPlugin({
|
|
1741
|
+
hmr: false,
|
|
1742
|
+
fastRefresh: false,
|
|
1743
|
+
logger,
|
|
1744
|
+
diagnostics
|
|
1745
|
+
});
|
|
1746
|
+
plugin(serverPlugin);
|
|
1747
|
+
let ssrMod;
|
|
1748
|
+
try {
|
|
1749
|
+
ssrMod = await import(entryPath);
|
|
1750
|
+
if (logRequests) {
|
|
1751
|
+
console.log("[Server] SSR module loaded");
|
|
1752
|
+
}
|
|
1753
|
+
} catch (e) {
|
|
1754
|
+
console.error("[Server] Failed to load SSR module:", e);
|
|
1755
|
+
process.exit(1);
|
|
1756
|
+
}
|
|
1757
|
+
mkdirSync(devDir, { recursive: true });
|
|
1758
|
+
const frInitPath = resolve(devDir, "fast-refresh-init.ts");
|
|
1759
|
+
writeFileSync2(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
|
|
1760
|
+
if (import.meta.hot) import.meta.hot.accept();
|
|
1761
|
+
`);
|
|
1762
|
+
const hmrShellHtml = `<!doctype html>
|
|
1763
|
+
<html lang="en"><head>
|
|
1764
|
+
<meta charset="UTF-8" />
|
|
1765
|
+
<title>HMR Shell</title>
|
|
1766
|
+
</head><body>
|
|
1767
|
+
<script type="module" src="./fast-refresh-init.ts"></script>
|
|
1768
|
+
<script type="module" src="${clientSrc}"></script>
|
|
1769
|
+
</body></html>`;
|
|
1770
|
+
const hmrShellPath = resolve(devDir, "hmr-shell.html");
|
|
1771
|
+
writeFileSync2(hmrShellPath, hmrShellHtml);
|
|
1772
|
+
const hmrShellModule = __require(hmrShellPath);
|
|
1773
|
+
setupOpenAPIWatcher();
|
|
1774
|
+
let bundledScriptUrl = null;
|
|
1775
|
+
let hmrBootstrapScript = null;
|
|
1776
|
+
const routes = {
|
|
1777
|
+
"/__vertz_hmr": hmrShellModule
|
|
1778
|
+
};
|
|
1779
|
+
if (openapi) {
|
|
1780
|
+
routes["/api/openapi.json"] = () => serveOpenAPISpec();
|
|
1781
|
+
}
|
|
1782
|
+
if (apiHandler) {
|
|
1783
|
+
routes["/api/*"] = (req) => apiHandler(req);
|
|
1784
|
+
}
|
|
1785
|
+
killStaleProcess(port);
|
|
1786
|
+
server = Bun.serve({
|
|
1787
|
+
port,
|
|
1788
|
+
hostname: host,
|
|
1789
|
+
routes,
|
|
1790
|
+
async fetch(request) {
|
|
1791
|
+
const url = new URL(request.url);
|
|
1792
|
+
const pathname = url.pathname;
|
|
1793
|
+
if (pathname === "/__vertz_errors") {
|
|
1794
|
+
if (server?.upgrade(request, { data: {} })) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
1798
|
+
}
|
|
1799
|
+
if (pathname.startsWith("/_bun/")) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (pathname === "/__vertz_build_check") {
|
|
1803
|
+
if (currentError) {
|
|
1804
|
+
return Response.json({ errors: currentError.errors });
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
const clientRelative = rawClientSrc.replace(/^\//, "");
|
|
1808
|
+
const result = await Bun.build({
|
|
1809
|
+
entrypoints: [resolve(projectRoot, clientRelative)],
|
|
1810
|
+
root: projectRoot,
|
|
1811
|
+
target: "browser",
|
|
1812
|
+
throw: false
|
|
1813
|
+
});
|
|
1814
|
+
if (!result.success && result.logs.length > 0) {
|
|
1815
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => {
|
|
1816
|
+
const pos = l.position;
|
|
1817
|
+
const file = pos?.file ? pos.file.replace(projectRoot, "").replace(/^\//, "") : undefined;
|
|
1818
|
+
return {
|
|
1819
|
+
message: l.message,
|
|
1820
|
+
file,
|
|
1821
|
+
absFile: pos?.file,
|
|
1822
|
+
line: pos?.line,
|
|
1823
|
+
column: pos?.column,
|
|
1824
|
+
lineText: pos?.lineText
|
|
1825
|
+
};
|
|
1826
|
+
});
|
|
1827
|
+
return Response.json({ errors });
|
|
1828
|
+
}
|
|
1829
|
+
if (lastBuildError) {
|
|
1830
|
+
return Response.json({ errors: [{ message: lastBuildError }] });
|
|
1831
|
+
}
|
|
1832
|
+
return Response.json({ errors: [] });
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1835
|
+
return Response.json({ errors: [{ message: msg }] });
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
if (pathname === "/__vertz_diagnostics") {
|
|
1839
|
+
return Response.json(diagnostics.getSnapshot());
|
|
1840
|
+
}
|
|
1841
|
+
if (openapi && request.method === "GET" && pathname === "/api/openapi.json") {
|
|
1842
|
+
return serveOpenAPISpec();
|
|
1843
|
+
}
|
|
1844
|
+
if (apiHandler && skipSSRPaths.some((p) => pathname.startsWith(p))) {
|
|
1845
|
+
return apiHandler(request);
|
|
1846
|
+
}
|
|
1847
|
+
if (request.headers.get("x-vertz-nav") === "1") {
|
|
1848
|
+
try {
|
|
1849
|
+
const stream = await ssrStreamNavQueries(ssrMod, pathname, { navSsrTimeout: 5000 });
|
|
1850
|
+
return new Response(stream, {
|
|
1851
|
+
status: 200,
|
|
1852
|
+
headers: {
|
|
1853
|
+
"Content-Type": "text/event-stream",
|
|
1854
|
+
"Cache-Control": "no-cache"
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
} catch {
|
|
1858
|
+
return new Response(`event: done
|
|
1859
|
+
data: {}
|
|
1860
|
+
|
|
1861
|
+
`, {
|
|
1862
|
+
status: 200,
|
|
1863
|
+
headers: {
|
|
1864
|
+
"Content-Type": "text/event-stream",
|
|
1865
|
+
"Cache-Control": "no-cache"
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
if (pathname !== "/" && !pathname.endsWith(".html")) {
|
|
1871
|
+
const safePath = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
1872
|
+
const publicDir = resolve(projectRoot, "public");
|
|
1873
|
+
const resolvedPublic = resolve(publicDir, safePath.slice(1));
|
|
1874
|
+
if (resolvedPublic.startsWith(publicDir)) {
|
|
1875
|
+
const publicFile = Bun.file(resolvedPublic);
|
|
1876
|
+
if (await publicFile.exists()) {
|
|
1877
|
+
return new Response(publicFile);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const resolvedRoot = resolve(projectRoot, safePath.slice(1));
|
|
1881
|
+
if (resolvedRoot.startsWith(projectRoot)) {
|
|
1882
|
+
const rootFile = Bun.file(resolvedRoot);
|
|
1883
|
+
if (await rootFile.exists()) {
|
|
1884
|
+
return new Response(rootFile);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
if (!request.headers.get("accept")?.includes("text/html") && !pathname.endsWith(".html") && pathname !== "/") {
|
|
1889
|
+
return new Response("Not Found", { status: 404 });
|
|
1890
|
+
}
|
|
1891
|
+
if (logRequests) {
|
|
1892
|
+
console.log(`[Server] SSR: ${pathname}`);
|
|
1893
|
+
}
|
|
1894
|
+
try {
|
|
1895
|
+
const originalFetch = globalThis.fetch;
|
|
1896
|
+
if (apiHandler) {
|
|
1897
|
+
globalThis.fetch = createFetchInterceptor({
|
|
1898
|
+
apiHandler,
|
|
1899
|
+
origin: `http://${host}:${server?.port}`,
|
|
1900
|
+
skipSSRPaths,
|
|
1901
|
+
originalFetch
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
logger.log("ssr", "render-start", { url: pathname });
|
|
1906
|
+
const ssrStart = performance.now();
|
|
1907
|
+
const result = await ssrRenderToString(ssrMod, pathname, { ssrTimeout: 300 });
|
|
1908
|
+
logger.log("ssr", "render-done", {
|
|
1909
|
+
url: pathname,
|
|
1910
|
+
durationMs: Math.round(performance.now() - ssrStart),
|
|
1911
|
+
htmlBytes: result.html.length
|
|
1912
|
+
});
|
|
1913
|
+
const scriptTag = buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc);
|
|
1914
|
+
const html = generateSSRPageHtml({
|
|
1915
|
+
title,
|
|
1916
|
+
css: result.css,
|
|
1917
|
+
bodyHtml: result.html,
|
|
1918
|
+
ssrData: result.ssrData,
|
|
1919
|
+
scriptTag,
|
|
1920
|
+
editor
|
|
1921
|
+
});
|
|
1922
|
+
return new Response(html, {
|
|
1923
|
+
status: 200,
|
|
1924
|
+
headers: {
|
|
1925
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1926
|
+
"Cache-Control": "no-store"
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
} finally {
|
|
1930
|
+
if (apiHandler) {
|
|
1931
|
+
globalThis.fetch = originalFetch;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
console.error("[Server] SSR error:", err);
|
|
1936
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1937
|
+
const errStack = err instanceof Error ? err.stack : undefined;
|
|
1938
|
+
const { message: _, ...loc } = errStack ? parseSourceFromStack(errStack) : { message: "" };
|
|
1939
|
+
broadcastError("ssr", [{ message: errMsg, ...loc, stack: errStack }]);
|
|
1940
|
+
const scriptTag = buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc);
|
|
1941
|
+
const fallbackHtml = generateSSRPageHtml({
|
|
1942
|
+
title,
|
|
1943
|
+
css: "",
|
|
1944
|
+
bodyHtml: "",
|
|
1945
|
+
ssrData: [],
|
|
1946
|
+
scriptTag,
|
|
1947
|
+
editor
|
|
1948
|
+
});
|
|
1949
|
+
return new Response(fallbackHtml, {
|
|
1950
|
+
status: 200,
|
|
1951
|
+
headers: {
|
|
1952
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1953
|
+
"Cache-Control": "no-store"
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
websocket: {
|
|
1959
|
+
open(ws) {
|
|
1960
|
+
wsClients.add(ws);
|
|
1961
|
+
diagnostics.recordWebSocketChange(wsClients.size);
|
|
1962
|
+
logger.log("ws", "client-connected", { total: wsClients.size });
|
|
1963
|
+
ws.sendText(JSON.stringify({ type: "connected" }));
|
|
1964
|
+
if (currentError) {
|
|
1965
|
+
ws.sendText(JSON.stringify({
|
|
1966
|
+
type: "error",
|
|
1967
|
+
category: currentError.category,
|
|
1968
|
+
errors: currentError.errors
|
|
1969
|
+
}));
|
|
1970
|
+
}
|
|
1971
|
+
},
|
|
1972
|
+
message(ws, msg) {
|
|
1973
|
+
try {
|
|
1974
|
+
const data = JSON.parse(typeof msg === "string" ? msg : new TextDecoder().decode(msg));
|
|
1975
|
+
if (data.type === "ping") {
|
|
1976
|
+
ws.sendText(JSON.stringify({ type: "pong" }));
|
|
1977
|
+
} else if (data.type === "resolve-stack" && data.stack) {
|
|
1978
|
+
const selfFetch = async (url) => {
|
|
1979
|
+
const absUrl = url.startsWith("http") ? url : `http://${host}:${server?.port}${url}`;
|
|
1980
|
+
return fetch(absUrl);
|
|
1981
|
+
};
|
|
1982
|
+
sourceMapResolver.resolveStack(data.stack, data.message ?? "", selfFetch).then((result) => {
|
|
1983
|
+
const hasFileInfo = result.errors.some((e) => e.file);
|
|
1984
|
+
if (!hasFileInfo) {
|
|
1985
|
+
const msg2 = result.errors[0]?.message ?? "";
|
|
1986
|
+
let enrichedErrors = null;
|
|
1987
|
+
if (currentError?.errors.some((e) => e.file)) {
|
|
1988
|
+
enrichedErrors = currentError.errors.map((e) => ({
|
|
1989
|
+
...e,
|
|
1990
|
+
message: msg2 || e.message
|
|
1991
|
+
}));
|
|
1992
|
+
} else if (lastChangedFile) {
|
|
1993
|
+
const absFile = resolve(projectRoot, lastChangedFile);
|
|
1994
|
+
enrichedErrors = [{ message: msg2, file: lastChangedFile, absFile }];
|
|
1995
|
+
}
|
|
1996
|
+
if (enrichedErrors) {
|
|
1997
|
+
const payload2 = {
|
|
1998
|
+
type: "error",
|
|
1999
|
+
category: "runtime",
|
|
2000
|
+
errors: enrichedErrors,
|
|
2001
|
+
parsedStack: result.parsedStack
|
|
2002
|
+
};
|
|
2003
|
+
currentError = { category: "runtime", errors: enrichedErrors };
|
|
2004
|
+
const text2 = JSON.stringify(payload2);
|
|
2005
|
+
for (const client of wsClients) {
|
|
2006
|
+
client.sendText(text2);
|
|
2007
|
+
}
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
const payload = {
|
|
2012
|
+
type: "error",
|
|
2013
|
+
category: "runtime",
|
|
2014
|
+
errors: result.errors,
|
|
2015
|
+
parsedStack: result.parsedStack
|
|
2016
|
+
};
|
|
2017
|
+
currentError = { category: "runtime", errors: result.errors };
|
|
2018
|
+
const text = JSON.stringify(payload);
|
|
2019
|
+
for (const client of wsClients) {
|
|
2020
|
+
client.sendText(text);
|
|
2021
|
+
}
|
|
2022
|
+
}).catch(() => {
|
|
2023
|
+
let errors;
|
|
2024
|
+
if (currentError?.errors.some((e) => e.file)) {
|
|
2025
|
+
errors = currentError.errors;
|
|
2026
|
+
} else if (lastChangedFile) {
|
|
2027
|
+
const absFile = resolve(projectRoot, lastChangedFile);
|
|
2028
|
+
errors = [
|
|
2029
|
+
{ message: data.message ?? "Unknown error", file: lastChangedFile, absFile }
|
|
2030
|
+
];
|
|
2031
|
+
}
|
|
2032
|
+
if (errors) {
|
|
2033
|
+
const payload = {
|
|
2034
|
+
type: "error",
|
|
2035
|
+
category: "runtime",
|
|
2036
|
+
errors
|
|
2037
|
+
};
|
|
2038
|
+
currentError = { category: "runtime", errors };
|
|
2039
|
+
const text = JSON.stringify(payload);
|
|
2040
|
+
for (const client of wsClients) {
|
|
2041
|
+
client.sendText(text);
|
|
2042
|
+
}
|
|
2043
|
+
} else {
|
|
2044
|
+
broadcastError("runtime", [{ message: data.message ?? "Unknown error" }]);
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
} catch {}
|
|
2049
|
+
},
|
|
2050
|
+
close(ws) {
|
|
2051
|
+
wsClients.delete(ws);
|
|
2052
|
+
diagnostics.recordWebSocketChange(wsClients.size);
|
|
2053
|
+
}
|
|
2054
|
+
},
|
|
2055
|
+
development: {
|
|
2056
|
+
hmr: true,
|
|
2057
|
+
console: true
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
if (logRequests) {
|
|
2061
|
+
console.log(`[Server] SSR+HMR dev server running at http://${host}:${server.port}`);
|
|
2062
|
+
}
|
|
2063
|
+
await discoverHMRAssets();
|
|
2064
|
+
async function discoverHMRAssets() {
|
|
2065
|
+
try {
|
|
2066
|
+
const res = await fetch(`http://${host}:${server?.port}/__vertz_hmr`);
|
|
2067
|
+
const html = await res.text();
|
|
2068
|
+
const assets = parseHMRAssets(html);
|
|
2069
|
+
if (assets.scriptUrl) {
|
|
2070
|
+
bundledScriptUrl = assets.scriptUrl;
|
|
2071
|
+
if (logRequests) {
|
|
2072
|
+
console.log("[Server] Discovered bundled script URL:", bundledScriptUrl);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (assets.bootstrapScript) {
|
|
2076
|
+
hmrBootstrapScript = assets.bootstrapScript;
|
|
2077
|
+
if (logRequests) {
|
|
2078
|
+
console.log("[Server] Extracted HMR bootstrap script");
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
diagnostics.recordHMRAssets(bundledScriptUrl, hmrBootstrapScript !== null);
|
|
2082
|
+
} catch (e) {
|
|
2083
|
+
console.warn("[Server] Could not discover HMR bundled URL:", e);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
const srcDir = resolve(projectRoot, "src");
|
|
2087
|
+
stopped = false;
|
|
2088
|
+
if (existsSync(srcDir)) {
|
|
2089
|
+
srcWatcherRef = watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
2090
|
+
if (!filename)
|
|
2091
|
+
return;
|
|
2092
|
+
if (refreshTimeout)
|
|
2093
|
+
clearTimeout(refreshTimeout);
|
|
2094
|
+
refreshTimeout = setTimeout(async () => {
|
|
2095
|
+
lastChangedFile = `src/${filename}`;
|
|
2096
|
+
diagnostics.recordFileChange(lastChangedFile);
|
|
2097
|
+
logger.log("watcher", "file-changed", { file: lastChangedFile });
|
|
2098
|
+
lastBroadcastedError = "";
|
|
2099
|
+
sourceMapResolver.invalidate();
|
|
2100
|
+
if (logRequests) {
|
|
2101
|
+
console.log(`[Server] File changed: ${filename}`);
|
|
2102
|
+
}
|
|
2103
|
+
if (stopped)
|
|
2104
|
+
return;
|
|
2105
|
+
await discoverHMRAssets();
|
|
2106
|
+
if (stopped)
|
|
2107
|
+
return;
|
|
2108
|
+
try {
|
|
2109
|
+
const clientRelative = rawClientSrc.replace(/^\//, "");
|
|
2110
|
+
const result = await Bun.build({
|
|
2111
|
+
entrypoints: [resolve(projectRoot, clientRelative)],
|
|
2112
|
+
root: projectRoot,
|
|
2113
|
+
target: "browser",
|
|
2114
|
+
throw: false
|
|
2115
|
+
});
|
|
2116
|
+
if (!result.success && result.logs.length > 0) {
|
|
2117
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => {
|
|
2118
|
+
const pos = l.position;
|
|
2119
|
+
const file = pos?.file ? pos.file.replace(projectRoot, "").replace(/^\//, "") : undefined;
|
|
2120
|
+
return {
|
|
2121
|
+
message: l.message,
|
|
2122
|
+
file,
|
|
2123
|
+
absFile: pos?.file,
|
|
2124
|
+
line: pos?.line,
|
|
2125
|
+
column: pos?.column,
|
|
2126
|
+
lineText: pos?.lineText
|
|
2127
|
+
};
|
|
2128
|
+
});
|
|
2129
|
+
broadcastError("build", errors);
|
|
2130
|
+
} else {
|
|
2131
|
+
const prevUrl = bundledScriptUrl;
|
|
2132
|
+
for (let attempt = 0;attempt < 5; attempt++) {
|
|
2133
|
+
if (stopped)
|
|
2134
|
+
return;
|
|
2135
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2136
|
+
if (stopped)
|
|
2137
|
+
return;
|
|
2138
|
+
await discoverHMRAssets();
|
|
2139
|
+
if (bundledScriptUrl !== prevUrl)
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
clearErrorForFileChange();
|
|
2143
|
+
}
|
|
2144
|
+
} catch {}
|
|
2145
|
+
if (stopped)
|
|
2146
|
+
return;
|
|
2147
|
+
const cacheKeys = Object.keys(__require.cache);
|
|
2148
|
+
let cacheCleared = 0;
|
|
2149
|
+
for (const key of cacheKeys) {
|
|
2150
|
+
if (key.startsWith(srcDir) || key.startsWith(entryPath)) {
|
|
2151
|
+
delete __require.cache[key];
|
|
2152
|
+
cacheCleared++;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
logger.log("watcher", "cache-cleared", { entries: cacheCleared });
|
|
2156
|
+
const ssrWrapperPath = resolve(devDir, "ssr-reload-entry.ts");
|
|
2157
|
+
mkdirSync(devDir, { recursive: true });
|
|
2158
|
+
writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
|
|
2159
|
+
`);
|
|
2160
|
+
const ssrReloadStart = performance.now();
|
|
2161
|
+
try {
|
|
2162
|
+
const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
|
|
2163
|
+
ssrMod = freshMod;
|
|
2164
|
+
const durationMs = Math.round(performance.now() - ssrReloadStart);
|
|
2165
|
+
diagnostics.recordSSRReload(true, durationMs);
|
|
2166
|
+
logger.log("watcher", "ssr-reload", { status: "ok", durationMs });
|
|
2167
|
+
if (logRequests) {
|
|
2168
|
+
console.log("[Server] SSR module refreshed");
|
|
2169
|
+
}
|
|
2170
|
+
} catch {
|
|
2171
|
+
logger.log("watcher", "ssr-reload", { status: "retry" });
|
|
2172
|
+
if (stopped)
|
|
2173
|
+
return;
|
|
2174
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2175
|
+
if (stopped)
|
|
2176
|
+
return;
|
|
2177
|
+
const retryKeys = Object.keys(__require.cache);
|
|
2178
|
+
for (const key of retryKeys) {
|
|
2179
|
+
if (key.startsWith(srcDir) || key.startsWith(entryPath)) {
|
|
2180
|
+
delete __require.cache[key];
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
mkdirSync(devDir, { recursive: true });
|
|
2184
|
+
writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
|
|
2185
|
+
`);
|
|
2186
|
+
try {
|
|
2187
|
+
const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
|
|
2188
|
+
ssrMod = freshMod;
|
|
2189
|
+
const durationMs = Math.round(performance.now() - ssrReloadStart);
|
|
2190
|
+
diagnostics.recordSSRReload(true, durationMs);
|
|
2191
|
+
logger.log("watcher", "ssr-reload", { status: "ok", durationMs, retry: true });
|
|
2192
|
+
if (logRequests) {
|
|
2193
|
+
console.log("[Server] SSR module refreshed (retry)");
|
|
2194
|
+
}
|
|
2195
|
+
} catch (e2) {
|
|
2196
|
+
console.error("[Server] Failed to refresh SSR module:", e2);
|
|
2197
|
+
const errMsg = e2 instanceof Error ? e2.message : String(e2);
|
|
2198
|
+
const errStack = e2 instanceof Error ? e2.stack : undefined;
|
|
2199
|
+
const durationMs = Math.round(performance.now() - ssrReloadStart);
|
|
2200
|
+
diagnostics.recordSSRReload(false, durationMs, errMsg);
|
|
2201
|
+
logger.log("watcher", "ssr-reload", { status: "failed", error: errMsg });
|
|
2202
|
+
const { message: _m, ...loc2 } = errStack ? parseSourceFromStack(errStack) : { message: "" };
|
|
2203
|
+
broadcastError("ssr", [{ message: errMsg, ...loc2, stack: errStack }]);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}, 100);
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return {
|
|
2211
|
+
start,
|
|
2212
|
+
broadcastError,
|
|
2213
|
+
clearError,
|
|
2214
|
+
clearErrorForFileChange,
|
|
2215
|
+
setLastChangedFile(file) {
|
|
2216
|
+
lastChangedFile = file;
|
|
2217
|
+
},
|
|
2218
|
+
async stop() {
|
|
2219
|
+
stopped = true;
|
|
2220
|
+
if (refreshTimeout) {
|
|
2221
|
+
clearTimeout(refreshTimeout);
|
|
2222
|
+
refreshTimeout = null;
|
|
2223
|
+
}
|
|
2224
|
+
if (specWatcher) {
|
|
2225
|
+
specWatcher.close();
|
|
2226
|
+
specWatcher = null;
|
|
2227
|
+
}
|
|
2228
|
+
if (srcWatcherRef) {
|
|
2229
|
+
srcWatcherRef.close();
|
|
2230
|
+
srcWatcherRef = null;
|
|
2231
|
+
}
|
|
2232
|
+
if (server) {
|
|
2233
|
+
server.stop(true);
|
|
2234
|
+
server = null;
|
|
2235
|
+
}
|
|
2236
|
+
indexHtmlStasher.restore();
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
export {
|
|
2241
|
+
parseHMRAssets,
|
|
2242
|
+
generateSSRPageHtml,
|
|
2243
|
+
createIndexHtmlStasher,
|
|
2244
|
+
createFetchInterceptor,
|
|
2245
|
+
createBunDevServer,
|
|
2246
|
+
buildScriptTag
|
|
2247
|
+
};
|