veryfront 0.1.72 → 0.1.73
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/esm/deno.js +1 -1
- package/esm/src/html/html-shell-generator.d.ts.map +1 -1
- package/esm/src/html/html-shell-generator.js +6 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +116 -105
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +4 -0
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/error-formatter.js +15 -0
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/html-template.js +131 -8
- package/esm/src/server/dev-server/error-overlay/index.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/index.js +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +2 -2
- package/esm/src/server/dev-server/request-handler.d.ts.map +1 -1
- package/esm/src/server/dev-server/request-handler.js +6 -2
- package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
- package/esm/src/server/services/rendering/ssr.service.js +9 -2
- package/esm/src/server/utils/error-html.d.ts.map +1 -1
- package/esm/src/server/utils/error-html.js +26 -6
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/html/html-shell-generator.ts +9 -0
- package/src/src/rendering/orchestrator/pipeline.ts +186 -172
- package/src/src/server/dev-server/error-overlay/error-formatter.ts +21 -0
- package/src/src/server/dev-server/error-overlay/html-template.ts +139 -8
- package/src/src/server/dev-server/error-overlay/index.ts +1 -0
- package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +2 -1
- package/src/src/server/dev-server/request-handler.ts +6 -2
- package/src/src/server/services/rendering/ssr.service.ts +9 -2
- package/src/src/server/utils/error-html.ts +29 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.service.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/services/rendering/ssr.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,kCAAkC,CAAC;AAmB1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,CAAC;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA0B;gBAEzC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;KAAE;IAI7D,mBAAmB,IAAI,YAAY;IAW7B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAI1D,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiG1F,OAAO,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"ssr.service.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/services/rendering/ssr.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,kCAAkC,CAAC;AAmB1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,CAAC;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA0B;gBAEzC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;KAAE;IAI7D,mBAAmB,IAAI,YAAY;IAW7B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAI1D,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiG1F,OAAO,CAAC,iBAAiB;IA4FzB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;CAS1D"}
|
|
@@ -6,7 +6,7 @@ import { VeryfrontError } from "../../../errors/index.js";
|
|
|
6
6
|
import { getColorSchemeFromRequest } from "../../../security/http/client-hints.js";
|
|
7
7
|
import { endRenderSession, startRenderSession, } from "../../../transforms/mdx/esm-module-loader/module-fetcher/index.js";
|
|
8
8
|
import { getErrorCollector } from "../../../observability/error-collector.js";
|
|
9
|
-
import { ErrorOverlay } from "../../dev-server/error-overlay/index.js";
|
|
9
|
+
import { ErrorOverlay, parseErrorLocation } from "../../dev-server/error-overlay/index.js";
|
|
10
10
|
import { ErrorPages } from "../../utils/error-html.js";
|
|
11
11
|
import { HTTP_INTERNAL_SERVER_ERROR, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAVAILABLE, } from "../../../utils/constants/index.js";
|
|
12
12
|
const logger = serverLogger.component("ssr-service");
|
|
@@ -150,9 +150,16 @@ export class SSRService {
|
|
|
150
150
|
url: request.url,
|
|
151
151
|
slug,
|
|
152
152
|
});
|
|
153
|
+
const sourceFile = errorObj.sourceFile;
|
|
154
|
+
const location = sourceFile ? parseErrorLocation(errorObj, sourceFile) : {};
|
|
153
155
|
return {
|
|
154
156
|
status: HTTP_INTERNAL_SERVER_ERROR,
|
|
155
|
-
html: ErrorOverlay.createHTML({
|
|
157
|
+
html: ErrorOverlay.createHTML({
|
|
158
|
+
error: errorObj,
|
|
159
|
+
type: "runtime",
|
|
160
|
+
...(sourceFile ? { file: sourceFile } : {}),
|
|
161
|
+
...location,
|
|
162
|
+
}, ctx.projectSlug),
|
|
156
163
|
isStreaming: false,
|
|
157
164
|
cacheStrategy: "no-cache",
|
|
158
165
|
error: errorObj,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-html.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/error-html.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"error-html.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/error-html.ts"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAQnE;AA4GD,eAAO,MAAM,UAAU;wBACD,MAAM,GAAG,MAAM;0BAUb,MAAM,GAAG,MAAM;kBAQvB,MAAM;sBAQF,MAAM;CAOzB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeHTML } from "../../html/html-escape.js";
|
|
1
2
|
export function generateErrorHtml(options) {
|
|
2
3
|
const { statusCode, title, message, pathname, minimal } = options;
|
|
3
4
|
if (minimal) {
|
|
@@ -6,13 +7,16 @@ export function generateErrorHtml(options) {
|
|
|
6
7
|
return generateStyledErrorHtml(statusCode, title, message);
|
|
7
8
|
}
|
|
8
9
|
function generateStyledErrorHtml(statusCode, title, message) {
|
|
10
|
+
const errorMessage = title === "Not Found" ? `Page not found: ${message}` : message;
|
|
11
|
+
// 4xx = warning (routing/config issue), 5xx = error (something broke)
|
|
12
|
+
const errorType = statusCode >= 500 ? "error" : "warning";
|
|
9
13
|
return `<!DOCTYPE html>
|
|
10
14
|
<html lang="en">
|
|
11
15
|
<head>
|
|
12
16
|
<meta charset="utf-8">
|
|
13
17
|
<meta name="viewport" content="width=device-width">
|
|
14
18
|
<link rel="icon" type="image/png" href="https://cdn.veryfront.com/images/veryfront-favicon.png">
|
|
15
|
-
<title>${statusCode} ${title} — Veryfront</title>
|
|
19
|
+
<title>${statusCode} ${escapeHTML(title)} — Veryfront</title>
|
|
16
20
|
<style>
|
|
17
21
|
:root {
|
|
18
22
|
--bg: #ffffff;
|
|
@@ -61,9 +65,25 @@ function generateStyledErrorHtml(statusCode, title, message) {
|
|
|
61
65
|
</head>
|
|
62
66
|
<body>
|
|
63
67
|
<div class="container">
|
|
64
|
-
<h1 class="title">${title}</h1>
|
|
65
|
-
<p class="message">${message}</p>
|
|
68
|
+
<h1 class="title">${escapeHTML(title)}</h1>
|
|
69
|
+
<p class="message">${escapeHTML(message)}</p>
|
|
66
70
|
</div>
|
|
71
|
+
<script>
|
|
72
|
+
if (window.parent !== window) {
|
|
73
|
+
try {
|
|
74
|
+
window.parent.postMessage({
|
|
75
|
+
action: 'appUpdated',
|
|
76
|
+
isInitialLoad: true,
|
|
77
|
+
hasError: true,
|
|
78
|
+
url: window.location.href,
|
|
79
|
+
errors: [{
|
|
80
|
+
type: '${errorType}',
|
|
81
|
+
message: ${JSON.stringify(errorMessage).replace(/</g, "\\u003c")}
|
|
82
|
+
}]
|
|
83
|
+
}, '*');
|
|
84
|
+
} catch (e) { /* postMessage may fail in cross-origin iframes */ }
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
67
87
|
</body>
|
|
68
88
|
</html>`;
|
|
69
89
|
}
|
|
@@ -74,11 +94,11 @@ function generateMinimalErrorHtml(statusCode, title, message, pathname) {
|
|
|
74
94
|
<head>
|
|
75
95
|
<meta charset="utf-8"/>
|
|
76
96
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
77
|
-
<title>${statusCode} ${title}</title>
|
|
97
|
+
<title>${statusCode} ${escapeHTML(title)}</title>
|
|
78
98
|
</head>
|
|
79
99
|
<body>
|
|
80
|
-
<h1>${statusCode} ${title}</h1>
|
|
81
|
-
<p>${fullMessage}</p>
|
|
100
|
+
<h1>${statusCode} ${escapeHTML(title)}</h1>
|
|
101
|
+
<p>${escapeHTML(fullMessage)}</p>
|
|
82
102
|
</body>
|
|
83
103
|
</html>`;
|
|
84
104
|
}
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -208,6 +208,14 @@ async function generateHTMLShellPartsImpl(
|
|
|
208
208
|
|
|
209
209
|
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
210
210
|
|
|
211
|
+
// Expose project slug for runtime error overlay "Fix in Veryfront" button
|
|
212
|
+
const overlaySlug = options.projectId || meta.slug;
|
|
213
|
+
const slugForOverlay = useDevScripts && overlaySlug
|
|
214
|
+
? `<script${nonceAttr}>window.__VF_PROJECT_SLUG__=${
|
|
215
|
+
JSON.stringify(overlaySlug).replace(/</g, "\\u003c")
|
|
216
|
+
};</script>`
|
|
217
|
+
: "";
|
|
218
|
+
|
|
211
219
|
const hydrationErrorSuppression = useDevScripts ? "" : `<script${nonceAttr}>
|
|
212
220
|
(function(){
|
|
213
221
|
var origError = console.error;
|
|
@@ -303,6 +311,7 @@ async function generateHTMLShellPartsImpl(
|
|
|
303
311
|
${linkTags}
|
|
304
312
|
${styleTags}
|
|
305
313
|
${modeStyles}
|
|
314
|
+
${slugForOverlay}
|
|
306
315
|
</head>
|
|
307
316
|
<body${bodyClass ? ` class="${bodyClass}"` : ""} suppressHydrationWarning>
|
|
308
317
|
<div ${rootAttributes}>`;
|
|
@@ -391,191 +391,205 @@ export class RenderPipeline {
|
|
|
391
391
|
);
|
|
392
392
|
timing.pageResolve = Math.round(performance.now() - pageResolveStart);
|
|
393
393
|
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await withSpan(
|
|
398
|
-
"render.collect_layouts",
|
|
399
|
-
() => this.config.layoutOrchestrator.collectLayouts(pageInfo),
|
|
400
|
-
{ "render.slug": slug },
|
|
394
|
+
const sourceFile = extractRelativePathShared(
|
|
395
|
+
pageInfo.entity.path,
|
|
396
|
+
this.config.projectDir,
|
|
401
397
|
);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
? { ...options.params }
|
|
411
|
-
: {};
|
|
412
|
-
let layoutDataMap = new Map<string, Record<string, unknown>>();
|
|
413
|
-
|
|
414
|
-
const dataFetchStart = performance.now();
|
|
415
|
-
if (options?.request && options?.url) {
|
|
416
|
-
await withSpan(
|
|
417
|
-
"render.data_fetching",
|
|
418
|
-
async () => {
|
|
419
|
-
try {
|
|
420
|
-
const dataResolution = await this.resolveDataFetching(
|
|
421
|
-
slug,
|
|
422
|
-
pageInfo.entity.path,
|
|
423
|
-
layoutResult.nestedLayouts,
|
|
424
|
-
options,
|
|
425
|
-
);
|
|
426
|
-
resolvedParams = dataResolution.params;
|
|
427
|
-
dataFetchingProps = Object.keys(dataResolution.pageProps).length > 0
|
|
428
|
-
? dataResolution.pageProps
|
|
429
|
-
: undefined;
|
|
430
|
-
layoutDataMap = dataResolution.layoutProps;
|
|
431
|
-
} catch (error) {
|
|
432
|
-
if (error instanceof VeryfrontError) throw error;
|
|
433
|
-
|
|
434
|
-
renderPageLog.error("Data fetching error", {
|
|
435
|
-
slug,
|
|
436
|
-
error: error instanceof Error ? error.message : String(error),
|
|
437
|
-
});
|
|
438
|
-
throw error;
|
|
439
|
-
}
|
|
440
|
-
},
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const skipLayouts = isDotPath(slug, pageInfo.entity.path);
|
|
401
|
+
|
|
402
|
+
const layoutCollectStart = performance.now();
|
|
403
|
+
const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await withSpan(
|
|
404
|
+
"render.collect_layouts",
|
|
405
|
+
() => this.config.layoutOrchestrator.collectLayouts(pageInfo),
|
|
441
406
|
{ "render.slug": slug },
|
|
442
407
|
);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
408
|
+
timing.layoutCollect = Math.round(performance.now() - layoutCollectStart);
|
|
409
|
+
|
|
410
|
+
const layoutPreloadPromise = !skipLayouts && layoutResult.nestedLayouts.length > 0
|
|
411
|
+
? this.config.layoutOrchestrator.preloadLayoutModules(layoutResult.nestedLayouts)
|
|
412
|
+
: Promise.resolve();
|
|
413
|
+
|
|
414
|
+
let dataFetchingProps: Record<string, unknown> | undefined;
|
|
415
|
+
let resolvedParams: Record<string, string | string[]> = options?.params
|
|
416
|
+
? { ...options.params }
|
|
417
|
+
: {};
|
|
418
|
+
let layoutDataMap = new Map<string, Record<string, unknown>>();
|
|
419
|
+
|
|
420
|
+
const dataFetchStart = performance.now();
|
|
421
|
+
if (options?.request && options?.url) {
|
|
422
|
+
await withSpan(
|
|
423
|
+
"render.data_fetching",
|
|
424
|
+
async () => {
|
|
425
|
+
try {
|
|
426
|
+
const dataResolution = await this.resolveDataFetching(
|
|
427
|
+
slug,
|
|
428
|
+
pageInfo.entity.path,
|
|
429
|
+
layoutResult.nestedLayouts,
|
|
430
|
+
options,
|
|
431
|
+
);
|
|
432
|
+
resolvedParams = dataResolution.params;
|
|
433
|
+
dataFetchingProps = Object.keys(dataResolution.pageProps).length > 0
|
|
434
|
+
? dataResolution.pageProps
|
|
435
|
+
: undefined;
|
|
436
|
+
layoutDataMap = dataResolution.layoutProps;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (error instanceof VeryfrontError) throw error;
|
|
439
|
+
|
|
440
|
+
renderPageLog.error("Data fetching error", {
|
|
441
|
+
slug,
|
|
442
|
+
error: error instanceof Error ? error.message : String(error),
|
|
443
|
+
});
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
{ "render.slug": slug },
|
|
448
|
+
);
|
|
452
449
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
450
|
+
timing.dataFetch = Math.round(performance.now() - dataFetchStart);
|
|
451
|
+
|
|
452
|
+
const hasResolvedParams = Object.keys(resolvedParams).length > 0;
|
|
453
|
+
const mergedOptions = (dataFetchingProps || hasResolvedParams)
|
|
454
|
+
? {
|
|
455
|
+
...options,
|
|
456
|
+
...(hasResolvedParams ? { params: resolvedParams } : {}),
|
|
457
|
+
...(dataFetchingProps
|
|
458
|
+
? { props: { ...options?.props, ...dataFetchingProps } }
|
|
459
|
+
: {}),
|
|
460
|
+
}
|
|
461
|
+
: options;
|
|
462
|
+
|
|
463
|
+
const bundlePrepStart = performance.now();
|
|
464
|
+
const pageBundleResult = await withSpan(
|
|
465
|
+
"render.prepare_bundles",
|
|
466
|
+
() =>
|
|
467
|
+
this.config.pageRenderer.preparePageBundles(
|
|
468
|
+
pageInfo,
|
|
469
|
+
slug,
|
|
470
|
+
cacheResult?.cachedModule,
|
|
471
|
+
mergedOptions,
|
|
472
|
+
),
|
|
473
|
+
{ "render.slug": slug },
|
|
474
|
+
);
|
|
475
|
+
timing.bundlePrep = Math.round(performance.now() - bundlePrepStart);
|
|
468
476
|
|
|
469
|
-
|
|
477
|
+
if (pageBundleResult.scriptResult) return pageBundleResult.scriptResult;
|
|
470
478
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
479
|
+
if (!pageBundleResult.pageElement || !pageBundleResult.pageBundle) {
|
|
480
|
+
throw RENDER_ERROR.create({
|
|
481
|
+
detail: "Failed to prepare page bundle",
|
|
482
|
+
context: { slug },
|
|
483
|
+
});
|
|
484
|
+
}
|
|
477
485
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
),
|
|
504
|
-
{ "render.slug": slug, "render.layout_count": layoutResult.nestedLayouts.length },
|
|
505
|
-
);
|
|
506
|
-
timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
|
|
507
|
-
|
|
508
|
-
// Snapshot CSS imports collected during module loading (before SSR rendering).
|
|
509
|
-
// These are passed to the HTML generator to be included in the output.
|
|
510
|
-
const collectedCSSImports = getCSSImports();
|
|
511
|
-
|
|
512
|
-
const ssrStart = performance.now();
|
|
513
|
-
const ssrResult = await withSpan(
|
|
514
|
-
"render.ssr",
|
|
515
|
-
() =>
|
|
516
|
-
withTimeoutThrow(
|
|
517
|
-
this.config.ssrOrchestrator.performSSRRendering(
|
|
518
|
-
wrappedElement,
|
|
519
|
-
{
|
|
520
|
-
pageInfo,
|
|
521
|
-
pageBundle,
|
|
522
|
-
layoutBundle: layoutResult.layoutBundle,
|
|
523
|
-
nestedLayouts: layoutResult.nestedLayouts,
|
|
524
|
-
collectedMetadata: pageBundleResult.collectedMetadata,
|
|
525
|
-
slug,
|
|
526
|
-
cssImports: collectedCSSImports,
|
|
527
|
-
},
|
|
528
|
-
mergedOptions,
|
|
486
|
+
const { pageElement, pageBundle } = pageBundleResult;
|
|
487
|
+
|
|
488
|
+
const mergedFrontmatter = {
|
|
489
|
+
...pageInfo.entity.frontmatter,
|
|
490
|
+
...(pageBundle as MdxBundle).frontmatter,
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const headings = (pageBundle as PageBundle).headings || [];
|
|
494
|
+
|
|
495
|
+
await layoutPreloadPromise;
|
|
496
|
+
|
|
497
|
+
const layoutApplyStart = performance.now();
|
|
498
|
+
const wrappedElement = await withSpan(
|
|
499
|
+
"render.apply_layouts",
|
|
500
|
+
() =>
|
|
501
|
+
this.config.layoutOrchestrator.applyLayoutsAndWrappers(
|
|
502
|
+
pageElement,
|
|
503
|
+
pageInfo,
|
|
504
|
+
layoutResult.layoutBundle,
|
|
505
|
+
layoutResult.nestedLayouts,
|
|
506
|
+
layoutDataMap,
|
|
507
|
+
options?.url,
|
|
508
|
+
mergedFrontmatter,
|
|
509
|
+
headings,
|
|
510
|
+
options?.projectSlug,
|
|
529
511
|
),
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
512
|
+
{ "render.slug": slug, "render.layout_count": layoutResult.nestedLayouts.length },
|
|
513
|
+
);
|
|
514
|
+
timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
|
|
515
|
+
|
|
516
|
+
// Snapshot CSS imports collected during module loading (before SSR rendering).
|
|
517
|
+
// These are passed to the HTML generator to be included in the output.
|
|
518
|
+
const collectedCSSImports = getCSSImports();
|
|
519
|
+
|
|
520
|
+
const ssrStart = performance.now();
|
|
521
|
+
const ssrResult = await withSpan(
|
|
522
|
+
"render.ssr",
|
|
523
|
+
() =>
|
|
524
|
+
withTimeoutThrow(
|
|
525
|
+
this.config.ssrOrchestrator.performSSRRendering(
|
|
526
|
+
wrappedElement,
|
|
527
|
+
{
|
|
528
|
+
pageInfo,
|
|
529
|
+
pageBundle,
|
|
530
|
+
layoutBundle: layoutResult.layoutBundle,
|
|
531
|
+
nestedLayouts: layoutResult.nestedLayouts,
|
|
532
|
+
collectedMetadata: pageBundleResult.collectedMetadata,
|
|
533
|
+
slug,
|
|
534
|
+
cssImports: collectedCSSImports,
|
|
535
|
+
},
|
|
536
|
+
mergedOptions,
|
|
537
|
+
),
|
|
538
|
+
SSR_RENDER_TIMEOUT_MS,
|
|
539
|
+
`SSR rendering for ${slug}`,
|
|
540
|
+
),
|
|
541
|
+
{ "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" },
|
|
542
|
+
);
|
|
543
|
+
timing.ssr = Math.round(performance.now() - ssrStart);
|
|
536
544
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
545
|
+
if (collectedCSSImports.length > 0) {
|
|
546
|
+
renderPipelineLog.debug("CSS imports collected for HTML generation", {
|
|
547
|
+
slug,
|
|
548
|
+
count: collectedCSSImports.length,
|
|
549
|
+
paths: collectedCSSImports.map((p) => p.split("/").pop()),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
544
552
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
553
|
+
const pageModule = pageBundleResult.clientModuleCode && pageBundleResult.pageModuleType
|
|
554
|
+
? {
|
|
555
|
+
slug,
|
|
556
|
+
code: pageBundleResult.clientModuleCode,
|
|
557
|
+
type: pageBundleResult.pageModuleType,
|
|
558
|
+
}
|
|
559
|
+
: undefined;
|
|
560
|
+
|
|
561
|
+
const result: RenderResult = {
|
|
562
|
+
html: ssrResult.fullHtml,
|
|
563
|
+
frontmatter: (pageBundleResult.pageBundle as MdxBundle).frontmatter || {},
|
|
564
|
+
headings: pageBundleResult.pageBundle.headings || [],
|
|
565
|
+
nodeMap: pageBundleResult.pageBundle.nodeMap,
|
|
566
|
+
stream: ssrResult.finalStream,
|
|
567
|
+
ssrHash: ssrResult.ssrHash,
|
|
568
|
+
...(pageModule ? { pageModule } : {}),
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
if (shouldCache && !options?.skipCachePersist) {
|
|
572
|
+
void this.config.cacheCoordinator.persistResult(result, slug, cacheKey).catch(
|
|
573
|
+
(error) => {
|
|
574
|
+
renderPipelineLog.error("Cache persist failed", {
|
|
575
|
+
slug,
|
|
576
|
+
error: error instanceof Error ? error.message : String(error),
|
|
577
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
578
|
+
});
|
|
579
|
+
},
|
|
580
|
+
);
|
|
550
581
|
}
|
|
551
|
-
: undefined;
|
|
552
|
-
|
|
553
|
-
const result: RenderResult = {
|
|
554
|
-
html: ssrResult.fullHtml,
|
|
555
|
-
frontmatter: (pageBundleResult.pageBundle as MdxBundle).frontmatter || {},
|
|
556
|
-
headings: pageBundleResult.pageBundle.headings || [],
|
|
557
|
-
nodeMap: pageBundleResult.pageBundle.nodeMap,
|
|
558
|
-
stream: ssrResult.finalStream,
|
|
559
|
-
ssrHash: ssrResult.ssrHash,
|
|
560
|
-
...(pageModule ? { pageModule } : {}),
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
if (shouldCache && !options?.skipCachePersist) {
|
|
564
|
-
void this.config.cacheCoordinator.persistResult(result, slug, cacheKey).catch(
|
|
565
|
-
(error) => {
|
|
566
|
-
renderPipelineLog.error("Cache persist failed", {
|
|
567
|
-
slug,
|
|
568
|
-
error: error instanceof Error ? error.message : String(error),
|
|
569
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
570
|
-
});
|
|
571
|
-
},
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
582
|
|
|
575
|
-
|
|
576
|
-
|
|
583
|
+
timing.total = Math.round(performance.now() - pipelineStartTime);
|
|
584
|
+
renderPipelineLog.debug("Complete", { slug, timing });
|
|
577
585
|
|
|
578
|
-
|
|
586
|
+
return result;
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (error instanceof Error) {
|
|
589
|
+
(error as Error & { sourceFile?: string }).sourceFile = sourceFile;
|
|
590
|
+
}
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
579
593
|
});
|
|
580
594
|
return result;
|
|
581
595
|
},
|
|
@@ -53,6 +53,27 @@ export function getSuggestion(error: Error): string | undefined {
|
|
|
53
53
|
return undefined;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
export function parseErrorLocation(
|
|
57
|
+
error: Error,
|
|
58
|
+
file: string,
|
|
59
|
+
): { line?: number; column?: number } {
|
|
60
|
+
if (!error.stack || !file) return {};
|
|
61
|
+
|
|
62
|
+
const lines = error.stack.split("\n");
|
|
63
|
+
|
|
64
|
+
// First try to match the known source file
|
|
65
|
+
for (const stackLine of lines) {
|
|
66
|
+
if (!stackLine.includes(file)) continue;
|
|
67
|
+
|
|
68
|
+
const match = stackLine.match(/:(\d+):(\d+)\)?$/);
|
|
69
|
+
if (match) {
|
|
70
|
+
return { line: Number(match[1]), column: Number(match[2]) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
export function formatErrorType(type: ErrorType): string {
|
|
57
78
|
const firstChar = type[0];
|
|
58
79
|
if (!firstChar) return "";
|