veryfront 0.1.831 → 0.1.833
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 +26 -7
- package/esm/src/modules/server/module-server.d.ts.map +1 -1
- package/esm/src/modules/server/module-server.js +45 -9
- package/esm/src/proxy/main.js +3 -2
- package/esm/src/proxy/response-headers.d.ts +2 -0
- package/esm/src/proxy/response-headers.d.ts.map +1 -0
- package/esm/src/proxy/response-headers.js +74 -0
- package/esm/src/release-assets/constants.d.ts +4 -0
- package/esm/src/release-assets/constants.d.ts.map +1 -1
- package/esm/src/release-assets/constants.js +4 -0
- package/esm/src/release-assets/manifest-cache.d.ts.map +1 -1
- package/esm/src/release-assets/manifest-cache.js +40 -10
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +25 -23
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-shell-generator.d.ts","sourceRoot":"","sources":["../../../src/src/html/html-shell-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"html-shell-generator.d.ts","sourceRoot":"","sources":["../../../src/src/html/html-shell-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAmCxE,OAAO,EAGL,aAAa,EACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgDxD,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC;AA0FzE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC1C,KAAK,CAAC,EAAE,cAAc,EACtB,kBAAkB,CAAC,EAAE,MAAM,EAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAC5C,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBzC;AAqRD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC1C,KAAK,CAAC,EAAE,cAAc,EACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAC5C,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
|
|
@@ -5,16 +5,18 @@ import { profilePhase } from "../observability/request-profiler.js";
|
|
|
5
5
|
import { serverLogger } from "../utils/logger/logger.js";
|
|
6
6
|
import { isMarkdownPreview as checkMarkdownPreview } from "../transforms/md/utils.js";
|
|
7
7
|
import { generateModulePreloadHintsFromManifest, getRouteManifest, } from "../modules/manifest/route-module-manifest.js";
|
|
8
|
-
import { getReadyManifestForRenderAsync } from "../release-assets/manifest-cache.js";
|
|
8
|
+
import { getReadyManifestForRenderAsync, isReleaseAssetManifestEnabled, } from "../release-assets/manifest-cache.js";
|
|
9
|
+
import { RELEASE_MODULE_RUNTIME_VERSION_PARAM, RELEASE_MODULE_VERSION_PARAM, } from "../release-assets/constants.js";
|
|
9
10
|
import { resolveManifestModuleUrl, resolveManifestRoutePreloadUrls, } from "../release-assets/html-consumption.js";
|
|
10
11
|
import { routeForPage } from "../release-assets/route-path.js";
|
|
12
|
+
import { VERSION } from "../utils/version.js";
|
|
11
13
|
import { buildNonceAttribute, escapeHTML } from "./html-escape.js";
|
|
12
14
|
import { generateHydrationData, getDevScripts, getProdHydrationModulePath, getProdScripts, } from "./hydration-script-builder/index.js";
|
|
13
15
|
import { getPreviewStylesheetLink, getStudioScripts } from "./dev-scripts.js";
|
|
14
16
|
import { processMetadata } from "./metadata-builder.js";
|
|
15
17
|
import { extractCandidates, getDevStyles as getErrorOverlayStyles, getProjectCSS, } from "./styles-builder/index.js";
|
|
16
18
|
import { buildImportMap, buildRootAttributes, shouldDisableLayout } from "./utils.js";
|
|
17
|
-
function pathToModuleUrl(path, studioEmbed, manifest) {
|
|
19
|
+
function pathToModuleUrl(path, studioEmbed, manifest, fallbackReleaseId) {
|
|
18
20
|
if (!path)
|
|
19
21
|
return "";
|
|
20
22
|
// Manifest hit → content-addressed asset URL (production only; never in
|
|
@@ -28,7 +30,17 @@ function pathToModuleUrl(path, studioEmbed, manifest) {
|
|
|
28
30
|
const urlBase = withExtReplaced === path && !path.endsWith(".js")
|
|
29
31
|
? `/_vf_modules/${path}.js`
|
|
30
32
|
: `/_vf_modules/${withExtReplaced}`;
|
|
31
|
-
|
|
33
|
+
if (studioEmbed)
|
|
34
|
+
return `${urlBase}?studio_embed=true`;
|
|
35
|
+
return fallbackReleaseId ? appendReleaseModuleVersion(urlBase, fallbackReleaseId) : urlBase;
|
|
36
|
+
}
|
|
37
|
+
function appendReleaseModuleVersion(url, releaseId) {
|
|
38
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
39
|
+
const params = new URLSearchParams({
|
|
40
|
+
[RELEASE_MODULE_VERSION_PARAM]: releaseId,
|
|
41
|
+
[RELEASE_MODULE_RUNTIME_VERSION_PARAM]: VERSION,
|
|
42
|
+
});
|
|
43
|
+
return `${url}${separator}${params.toString()}`;
|
|
32
44
|
}
|
|
33
45
|
function getRelativePagePath(fullPath, projectDir) {
|
|
34
46
|
if (!fullPath)
|
|
@@ -46,6 +58,10 @@ function generateModulePreloadHints(options, releaseManifest) {
|
|
|
46
58
|
const addedUrls = new Set();
|
|
47
59
|
const projectDir = options.projectDir ?? "";
|
|
48
60
|
const studioEmbed = options.studioEmbed;
|
|
61
|
+
const fallbackReleaseId = !studioEmbed && !releaseManifest && options.releaseId &&
|
|
62
|
+
isReleaseAssetManifestEnabled()
|
|
63
|
+
? options.releaseId
|
|
64
|
+
: undefined;
|
|
49
65
|
function addHint(moduleUrl) {
|
|
50
66
|
if (!moduleUrl || addedUrls.has(moduleUrl))
|
|
51
67
|
return;
|
|
@@ -54,14 +70,14 @@ function generateModulePreloadHints(options, releaseManifest) {
|
|
|
54
70
|
}
|
|
55
71
|
if (options.pagePath) {
|
|
56
72
|
const relativePath = getRelativePagePath(options.pagePath, projectDir);
|
|
57
|
-
addHint(pathToModuleUrl(relativePath, studioEmbed, releaseManifest));
|
|
73
|
+
addHint(pathToModuleUrl(relativePath, studioEmbed, releaseManifest, fallbackReleaseId));
|
|
58
74
|
}
|
|
59
75
|
for (const layout of options.nestedLayouts ?? []) {
|
|
60
76
|
const layoutPath = layout.path ?? layout.componentPath ?? "";
|
|
61
77
|
if (!layoutPath)
|
|
62
78
|
continue;
|
|
63
79
|
const relativePath = getRelativePagePath(layoutPath, projectDir);
|
|
64
|
-
addHint(pathToModuleUrl(relativePath, studioEmbed, releaseManifest));
|
|
80
|
+
addHint(pathToModuleUrl(relativePath, studioEmbed, releaseManifest, fallbackReleaseId));
|
|
65
81
|
}
|
|
66
82
|
// Skip manifest-based preloads in preview/studio-embed mode:
|
|
67
83
|
// HMR reloads modules with ?t=timestamp, so preloaded versions won't match
|
|
@@ -90,10 +106,13 @@ function generateModulePreloadHints(options, releaseManifest) {
|
|
|
90
106
|
if (!manifest || manifest.renderCount <= 0)
|
|
91
107
|
return hints.join("\n ");
|
|
92
108
|
for (const hint of generateModulePreloadHintsFromManifest(projectSlug, legacyModuleManifestRoute, 50)) {
|
|
93
|
-
const
|
|
109
|
+
const rawHref = hint.match(/href="([^"]+)"/)?.[1];
|
|
110
|
+
const href = rawHref && fallbackReleaseId && rawHref.startsWith("/_vf_modules/")
|
|
111
|
+
? appendReleaseModuleVersion(rawHref, fallbackReleaseId)
|
|
112
|
+
: rawHref;
|
|
94
113
|
if (!href || addedUrls.has(href))
|
|
95
114
|
continue;
|
|
96
|
-
hints.push(hint);
|
|
115
|
+
hints.push(href === rawHref ? hint : hint.replace(/href="([^"]+)"/, `href="${href}"`));
|
|
97
116
|
addedUrls.add(href);
|
|
98
117
|
}
|
|
99
118
|
return hints.join("\n ");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-server.d.ts","sourceRoot":"","sources":["../../../../src/src/modules/server/module-server.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAG5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"module-server.d.ts","sourceRoot":"","sources":["../../../../src/src/modules/server/module-server.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAG5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AA6CtE;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwDrD,CAAC;AA4DF,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+XzF;AAwWD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGrD"}
|
|
@@ -15,11 +15,13 @@ import { injectNodePositions } from "../../transforms/plugins/babel-node-positio
|
|
|
15
15
|
import { parseProjectDomain } from "../../server/utils/domain-parser.js";
|
|
16
16
|
import { applySSRImportRewritesAsync, resolveSSRImportTargetModulePath, stripSSRModuleJsExtension, } from "./ssr-import-rewriter.js";
|
|
17
17
|
import { addHMRTimestamps } from "../../transforms/esm/import-rewriter.js";
|
|
18
|
+
import { replaceSpecifiers } from "../../transforms/esm/lexer.js";
|
|
18
19
|
import { FRAMEWORK_ROOT, resolveFrameworkSourcePath, } from "../../platform/compat/framework-source-resolver.js";
|
|
19
20
|
import { getReactUrls, REACT_DEFAULT_VERSION } from "../../utils/constants/cdn.js";
|
|
20
21
|
import { readLimitedCrossProjectSource } from "./cross-project-source-limit.js";
|
|
21
22
|
import { sha256Short } from "../../cache/hash.js";
|
|
22
23
|
import { rewriteReleaseDependencyImportsForModule } from "../../release-assets/module-consumption.js";
|
|
24
|
+
import { RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS, RELEASE_MODULE_RUNTIME_VERSION_PARAM, RELEASE_MODULE_VERSION_PARAM, } from "../../release-assets/constants.js";
|
|
23
25
|
import { buildSourceMissCacheKey, hasSourceMiss, rememberSourceMiss, } from "./module-source-resolution-cache.js";
|
|
24
26
|
import { ensureFilenameDefaultExport } from "../loader-shared/filename-default-export.js";
|
|
25
27
|
const logger = serverLogger.component("module-server");
|
|
@@ -96,6 +98,37 @@ const SNIPPET_MODULE_PREFIX = /^\/_vf_modules\/_snippets\/([a-f0-9]+)\.js/;
|
|
|
96
98
|
// Cross-project import patterns: /_vf_modules/_cross/<slug>[@<version>]/@/<path>
|
|
97
99
|
const CROSS_PROJECT_VERSIONED_PREFIX = /^\/_vf_modules\/_cross\/([a-z0-9-]+)@([\d^~x][\d.x^~-]*)\/\@\/(.+)$/;
|
|
98
100
|
const CROSS_PROJECT_LATEST_PREFIX = /^\/_vf_modules\/_cross\/([a-z0-9-]+)\/\@\/(.+)$/;
|
|
101
|
+
function appendReleaseModuleVersion(url, releaseId) {
|
|
102
|
+
if (url.includes(`${RELEASE_MODULE_VERSION_PARAM}=`) ||
|
|
103
|
+
url.includes(`${RELEASE_MODULE_RUNTIME_VERSION_PARAM}=`)) {
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
106
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
107
|
+
const params = new URLSearchParams({
|
|
108
|
+
[RELEASE_MODULE_VERSION_PARAM]: releaseId,
|
|
109
|
+
[RELEASE_MODULE_RUNTIME_VERSION_PARAM]: VERSION,
|
|
110
|
+
});
|
|
111
|
+
return `${url}${separator}${params.toString()}`;
|
|
112
|
+
}
|
|
113
|
+
function shouldCacheReleaseVersionedModule(url, options, isSSR) {
|
|
114
|
+
if (options.dev || isSSR || !options.releaseId)
|
|
115
|
+
return false;
|
|
116
|
+
return url.searchParams.get(RELEASE_MODULE_VERSION_PARAM) === options.releaseId &&
|
|
117
|
+
url.searchParams.get(RELEASE_MODULE_RUNTIME_VERSION_PARAM) === VERSION;
|
|
118
|
+
}
|
|
119
|
+
function isSSRModuleRequest(req, url) {
|
|
120
|
+
const userAgent = req.headers.get("user-agent") ?? "";
|
|
121
|
+
return url.searchParams.get("ssr") === "true" || userAgent.startsWith("Deno/");
|
|
122
|
+
}
|
|
123
|
+
async function addReleaseVersionToFallbackImports(code, releaseId) {
|
|
124
|
+
if (!releaseId || !code.includes("/_vf_modules/"))
|
|
125
|
+
return code;
|
|
126
|
+
return await replaceSpecifiers(code, (specifier) => {
|
|
127
|
+
if (!specifier.startsWith("/_vf_modules/"))
|
|
128
|
+
return null;
|
|
129
|
+
return appendReleaseModuleVersion(specifier, releaseId);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
99
132
|
/** Serve transformed module at /_vf_modules/* path */
|
|
100
133
|
export function serveModule(req, options) {
|
|
101
134
|
const url = new URL(req.url);
|
|
@@ -152,8 +185,7 @@ export function serveModule(req, options) {
|
|
|
152
185
|
});
|
|
153
186
|
}
|
|
154
187
|
const { slug: snippetProjectSlug, branch: snippetBranch } = parseProjectDomain(url.host);
|
|
155
|
-
const
|
|
156
|
-
const isSSR = url.searchParams.get("ssr") === "true" || userAgent.startsWith("Deno/");
|
|
188
|
+
const isSSR = isSSRModuleRequest(req, url);
|
|
157
189
|
logger.debug("Transforming snippet", {
|
|
158
190
|
hash,
|
|
159
191
|
isSSR,
|
|
@@ -231,8 +263,7 @@ export function serveModule(req, options) {
|
|
|
231
263
|
"Cache-Control": "no-cache",
|
|
232
264
|
});
|
|
233
265
|
}
|
|
234
|
-
const
|
|
235
|
-
const isSSR = url.searchParams.get("ssr") === "true" || userAgent.startsWith("Deno/");
|
|
266
|
+
const isSSR = isSSRModuleRequest(req, url);
|
|
236
267
|
let code = await profileModuleTransform(() => transformToESM(source, crossPath, projectDir, adapter, {
|
|
237
268
|
projectId: effectiveProjectId,
|
|
238
269
|
dev,
|
|
@@ -325,7 +356,7 @@ export function serveModule(req, options) {
|
|
|
325
356
|
: await secureFs.readFile(sourceFile);
|
|
326
357
|
}
|
|
327
358
|
const userAgent = req.headers.get("user-agent") ?? "";
|
|
328
|
-
const isSSR =
|
|
359
|
+
const isSSR = isSSRModuleRequest(req, url);
|
|
329
360
|
const studioEmbed = url.searchParams.get("studio_embed") === "true";
|
|
330
361
|
const shouldInjectPositions = dev || options.mode === "preview";
|
|
331
362
|
const isJsxFile = /\.(tsx|jsx)$/i.test(sourceFile);
|
|
@@ -379,9 +410,12 @@ export function serveModule(req, options) {
|
|
|
379
410
|
releaseId: options.releaseId,
|
|
380
411
|
readDependencySource: (path) => platformFs.readTextFile(path),
|
|
381
412
|
});
|
|
413
|
+
code = await addReleaseVersionToFallbackImports(code, options.releaseId);
|
|
382
414
|
}
|
|
383
415
|
}
|
|
384
|
-
const headers =
|
|
416
|
+
const headers = getModuleHeaders(modulePath, {
|
|
417
|
+
cacheable: shouldCacheReleaseVersionedModule(url, options, isSSRModuleRequest(req, url)),
|
|
418
|
+
});
|
|
385
419
|
logger.debug("Request complete", {
|
|
386
420
|
path: modulePath,
|
|
387
421
|
durationMs: (performance.now() - startTime).toFixed(1),
|
|
@@ -391,7 +425,7 @@ export function serveModule(req, options) {
|
|
|
391
425
|
catch (error) {
|
|
392
426
|
const errorMsg = getErrorMessage(error);
|
|
393
427
|
logger.error("Module transform error", { modulePath, error: errorMsg });
|
|
394
|
-
const headers =
|
|
428
|
+
const headers = getModuleHeaders(modulePath);
|
|
395
429
|
const errorBody = createDevModuleErrorBody(modulePath, errorMsg);
|
|
396
430
|
return createModuleResponse(method, errorBody, HTTP_SERVER_ERROR, headers);
|
|
397
431
|
}
|
|
@@ -671,10 +705,12 @@ export function isModuleRequest(req) {
|
|
|
671
705
|
const url = new URL(req.url);
|
|
672
706
|
return DEV_MODULE_PREFIX.test(url.pathname);
|
|
673
707
|
}
|
|
674
|
-
function
|
|
708
|
+
function getModuleHeaders(modulePath, options = {}) {
|
|
675
709
|
return {
|
|
676
710
|
"Content-Type": getDevModuleContentType(modulePath),
|
|
677
|
-
"Cache-Control":
|
|
711
|
+
"Cache-Control": options.cacheable
|
|
712
|
+
? `public, max-age=${RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS}, immutable`
|
|
713
|
+
: "no-cache",
|
|
678
714
|
};
|
|
679
715
|
}
|
|
680
716
|
function getDevModuleContentType(modulePath) {
|
package/esm/src/proxy/main.js
CHANGED
|
@@ -38,6 +38,7 @@ import { handleReleaseAssetRequest, isReleaseAssetPath } from "./asset-handler.j
|
|
|
38
38
|
import { runProxyRequestLifecycle } from "./request-lifecycle.js";
|
|
39
39
|
import { createUpstreamFailureResponse, createUpstreamTimeoutResponse, UPSTREAM_FAILURE_STATUS, UPSTREAM_TIMEOUT_STATUS, } from "./upstream-error-response.js";
|
|
40
40
|
import { createProxyServerTiming, markProxyServerTimingPhase, profileProxyServerTimingPhase, withProxyServerTimingHeader, } from "./server-timing.js";
|
|
41
|
+
import { removeStickyCookieFromPublicCacheableResponse } from "./response-headers.js";
|
|
41
42
|
function getLocalProjects() {
|
|
42
43
|
const raw = getEnv("LOCAL_PROJECTS");
|
|
43
44
|
return raw ? JSON.parse(raw) : {};
|
|
@@ -335,11 +336,11 @@ function forwardToServer(req, url) {
|
|
|
335
336
|
else {
|
|
336
337
|
reqLogger.info(`${response.status} ${req.method} ${url.pathname}`, { ms });
|
|
337
338
|
}
|
|
338
|
-
return withProxyTiming(new Response(response.body, {
|
|
339
|
+
return withProxyTiming(removeStickyCookieFromPublicCacheableResponse(new Response(response.body, {
|
|
339
340
|
status: response.status,
|
|
340
341
|
statusText: response.statusText,
|
|
341
342
|
headers: response.headers,
|
|
342
|
-
}));
|
|
343
|
+
})));
|
|
343
344
|
}
|
|
344
345
|
catch (error) {
|
|
345
346
|
clearTimeout(timeoutId);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-headers.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/response-headers.ts"],"names":[],"mappings":"AA+DA,wBAAgB,6CAA6C,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAkB1F"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const STICKY_COOKIE_NAME = "lb";
|
|
2
|
+
function parseCacheControl(cacheControl) {
|
|
3
|
+
const directives = new Map();
|
|
4
|
+
if (!cacheControl)
|
|
5
|
+
return directives;
|
|
6
|
+
for (const part of cacheControl.split(",")) {
|
|
7
|
+
const trimmed = part.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
continue;
|
|
10
|
+
const [rawName, rawValue] = trimmed.split("=", 2);
|
|
11
|
+
const name = rawName?.trim().toLowerCase();
|
|
12
|
+
if (!name)
|
|
13
|
+
continue;
|
|
14
|
+
directives.set(name, rawValue === undefined ? true : rawValue.trim().replace(/^"|"$/g, ""));
|
|
15
|
+
}
|
|
16
|
+
return directives;
|
|
17
|
+
}
|
|
18
|
+
function readPositiveDirectiveSeconds(directives, name) {
|
|
19
|
+
const value = directives.get(name);
|
|
20
|
+
if (typeof value !== "string")
|
|
21
|
+
return 0;
|
|
22
|
+
const seconds = Number(value);
|
|
23
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
|
|
24
|
+
}
|
|
25
|
+
function isPublicCacheable(headers) {
|
|
26
|
+
const directives = parseCacheControl(headers.get("cache-control"));
|
|
27
|
+
if (!directives.has("public"))
|
|
28
|
+
return false;
|
|
29
|
+
if (directives.has("private") || directives.has("no-store") || directives.has("no-cache")) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return directives.has("immutable") ||
|
|
33
|
+
readPositiveDirectiveSeconds(directives, "s-maxage") > 0 ||
|
|
34
|
+
readPositiveDirectiveSeconds(directives, "max-age") > 0;
|
|
35
|
+
}
|
|
36
|
+
function readSetCookies(headers) {
|
|
37
|
+
const getSetCookie = headers.getSetCookie;
|
|
38
|
+
if (typeof getSetCookie === "function") {
|
|
39
|
+
const values = getSetCookie.call(headers);
|
|
40
|
+
if (values.length > 0)
|
|
41
|
+
return values;
|
|
42
|
+
}
|
|
43
|
+
const values = [];
|
|
44
|
+
for (const [key, value] of headers) {
|
|
45
|
+
if (key.toLowerCase() === "set-cookie")
|
|
46
|
+
values.push(value);
|
|
47
|
+
}
|
|
48
|
+
const directValue = headers.get("set-cookie");
|
|
49
|
+
if (values.length === 0 && directValue)
|
|
50
|
+
values.push(directValue);
|
|
51
|
+
return values;
|
|
52
|
+
}
|
|
53
|
+
function isStickyCookie(setCookie) {
|
|
54
|
+
const [name] = setCookie.split("=", 1);
|
|
55
|
+
return name?.trim().toLowerCase() === STICKY_COOKIE_NAME;
|
|
56
|
+
}
|
|
57
|
+
export function removeStickyCookieFromPublicCacheableResponse(response) {
|
|
58
|
+
if (!isPublicCacheable(response.headers))
|
|
59
|
+
return response;
|
|
60
|
+
const setCookies = readSetCookies(response.headers);
|
|
61
|
+
if (setCookies.length === 0 || !setCookies.some(isStickyCookie))
|
|
62
|
+
return response;
|
|
63
|
+
const headers = new Headers(response.headers);
|
|
64
|
+
headers.delete("set-cookie");
|
|
65
|
+
for (const setCookie of setCookies) {
|
|
66
|
+
if (!isStickyCookie(setCookie))
|
|
67
|
+
headers.append("Set-Cookie", setCookie);
|
|
68
|
+
}
|
|
69
|
+
return new Response(response.body, {
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
headers,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -20,6 +20,10 @@ export declare const RELEASE_ASSET_CONTENT_TYPE_ALLOWLIST: readonly ReleaseAsset
|
|
|
20
20
|
export declare const RELEASE_ASSET_MAX_SIZE_BYTES: number;
|
|
21
21
|
/** Immutable cache max-age in seconds (1 year). */
|
|
22
22
|
export declare const RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS = 31536000;
|
|
23
|
+
/** Query param that scopes fallback module URLs to an immutable release. */
|
|
24
|
+
export declare const RELEASE_MODULE_VERSION_PARAM: "vf_release";
|
|
25
|
+
/** Query param that scopes fallback module URLs to the Veryfront runtime build. */
|
|
26
|
+
export declare const RELEASE_MODULE_RUNTIME_VERSION_PARAM: "vf_runtime";
|
|
23
27
|
/** Bounded upload concurrency when posting assets during a build. */
|
|
24
28
|
export declare const RELEASE_ASSET_UPLOAD_CONCURRENCY = 8;
|
|
25
29
|
/** Env flag that enables HTML manifest consumption in production (default OFF). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,qCAAqC,EAAG,CAAU,CAAC;AAEhE,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,EAAG,aAAsB,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,2BAA2B;;;CAG9B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,MAAM,OAAO,2BAA2B,CAAC;AAC7E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,qBAAqB,CAAC,CAAC;AAElG,0EAA0E;AAC1E,eAAO,MAAM,oCAAoC,EAAE,SAAS,uBAAuB,EAGlF,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAE7D,mDAAmD;AACnD,eAAO,MAAM,uCAAuC,WAAa,CAAC;AAElE,qEAAqE;AACrE,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,mFAAmF;AACnF,eAAO,MAAM,+BAA+B,qCAAqC,CAAC;AAElF,mFAAmF;AACnF,eAAO,MAAM,4CAA4C,kDACR,CAAC;AAElD,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,qBAAqB,GAAG,MAAM,CAE7F;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB,uBAAuB,GAAG,IAAI,CAIhC;AAED,6EAA6E;AAC7E,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,KAAK,IAAI,uBAAuB,CAIlC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,qCAAqC,EAAG,CAAU,CAAC;AAEhE,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,EAAG,aAAsB,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,2BAA2B;;;CAG9B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,MAAM,OAAO,2BAA2B,CAAC;AAC7E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,qBAAqB,CAAC,CAAC;AAElG,0EAA0E;AAC1E,eAAO,MAAM,oCAAoC,EAAE,SAAS,uBAAuB,EAGlF,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAE7D,mDAAmD;AACnD,eAAO,MAAM,uCAAuC,WAAa,CAAC;AAElE,4EAA4E;AAC5E,eAAO,MAAM,4BAA4B,EAAG,YAAqB,CAAC;AAElE,mFAAmF;AACnF,eAAO,MAAM,oCAAoC,EAAG,YAAqB,CAAC;AAE1E,qEAAqE;AACrE,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,mFAAmF;AACnF,eAAO,MAAM,+BAA+B,qCAAqC,CAAC;AAElF,mFAAmF;AACnF,eAAO,MAAM,4CAA4C,kDACR,CAAC;AAElD,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,qBAAqB,GAAG,MAAM,CAE7F;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB,uBAAuB,GAAG,IAAI,CAIhC;AAED,6EAA6E;AAC7E,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,KAAK,IAAI,uBAAuB,CAIlC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -21,6 +21,10 @@ export const RELEASE_ASSET_CONTENT_TYPE_ALLOWLIST = [
|
|
|
21
21
|
export const RELEASE_ASSET_MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
22
22
|
/** Immutable cache max-age in seconds (1 year). */
|
|
23
23
|
export const RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS = 31_536_000;
|
|
24
|
+
/** Query param that scopes fallback module URLs to an immutable release. */
|
|
25
|
+
export const RELEASE_MODULE_VERSION_PARAM = "vf_release";
|
|
26
|
+
/** Query param that scopes fallback module URLs to the Veryfront runtime build. */
|
|
27
|
+
export const RELEASE_MODULE_RUNTIME_VERSION_PARAM = "vf_runtime";
|
|
24
28
|
/** Bounded upload concurrency when posting assets during a build. */
|
|
25
29
|
export const RELEASE_ASSET_UPLOAD_CONCURRENCY = 8;
|
|
26
30
|
/** Env flag that enables HTML manifest consumption in production (default OFF). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/manifest-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;
|
|
1
|
+
{"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/manifest-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAWH,OAAO,EAA6B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAmC5F;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC1B;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAChE,CAAC;CACH;AAKD;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,2BAA2B,GAAG,SAAS,GAC/C,IAAI,CAMN;AASD,yEAAyE;AACzE,wBAAgB,6BAA6B,IAAI,OAAO,CAKvD;AAiCD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,oBAAoB,GAAG,IAAI,CAgC7B;AAED;;;;;;;GAOG;AACH,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA4BtC;AA+ED,6EAA6E;AAC7E,wBAAgB,gCAAgC,IAAI,IAAI,CAIvD;AAED,uEAAuE;AACvE,wBAAgB,8BAA8B,IAAI,IAAI,CAGrD"}
|
|
@@ -26,6 +26,7 @@ import { serverLogger } from "../utils/index.js";
|
|
|
26
26
|
import { LRUCache } from "../utils/lru-wrapper.js";
|
|
27
27
|
import { registerLRUCache } from "../cache/index.js";
|
|
28
28
|
import { getHostEnv } from "../platform/compat/process.js";
|
|
29
|
+
import { markRequestProfilePhase, profilePhase, } from "../observability/request-profiler.js";
|
|
29
30
|
import { RELEASE_ASSET_MANIFEST_ENV_FLAG } from "./constants.js";
|
|
30
31
|
import { parseReleaseAssetManifest } from "./manifest-schema.js";
|
|
31
32
|
const logger = serverLogger.component("release-asset-manifest");
|
|
@@ -92,6 +93,9 @@ export function isReleaseAssetManifestEnabled() {
|
|
|
92
93
|
function cacheKey(releaseId, manifestVersion) {
|
|
93
94
|
return manifestVersion !== undefined ? `${releaseId}:${manifestVersion}` : releaseId;
|
|
94
95
|
}
|
|
96
|
+
function markManifestDecision(decision) {
|
|
97
|
+
markRequestProfilePhase(`release_manifest.${decision}`);
|
|
98
|
+
}
|
|
95
99
|
function findCachedReadyEntry(releaseId) {
|
|
96
100
|
let best = null;
|
|
97
101
|
for (const [k, v] of manifestCache.entries()) {
|
|
@@ -121,14 +125,21 @@ function maybeScheduleReadyRefresh(releaseId, entry) {
|
|
|
121
125
|
* the flag is off or no fetcher is registered for this releaseId.
|
|
122
126
|
*/
|
|
123
127
|
export function getReadyManifestForRender(releaseId) {
|
|
124
|
-
if (!releaseId)
|
|
128
|
+
if (!releaseId) {
|
|
129
|
+
markManifestDecision("no_release_id");
|
|
125
130
|
return null;
|
|
126
|
-
|
|
131
|
+
}
|
|
132
|
+
if (!isReleaseAssetManifestEnabled()) {
|
|
133
|
+
markManifestDecision("disabled");
|
|
127
134
|
return null;
|
|
128
|
-
|
|
135
|
+
}
|
|
136
|
+
if (!resolveFetcher(releaseId)) {
|
|
137
|
+
markManifestDecision("no_fetcher");
|
|
129
138
|
return null;
|
|
139
|
+
}
|
|
130
140
|
const best = findCachedReadyEntry(releaseId);
|
|
131
141
|
if (best) {
|
|
142
|
+
markManifestDecision("cached_ready");
|
|
132
143
|
maybeScheduleReadyRefresh(releaseId, best);
|
|
133
144
|
return best.manifest;
|
|
134
145
|
}
|
|
@@ -136,8 +147,10 @@ export function getReadyManifestForRender(releaseId) {
|
|
|
136
147
|
const plain = manifestCache.get(releaseId);
|
|
137
148
|
if (plain && plain.expiresAt > Date.now()) {
|
|
138
149
|
// Non-ready entry still warm — return null without scheduling another fetch.
|
|
150
|
+
markManifestDecision("cached_null");
|
|
139
151
|
return null;
|
|
140
152
|
}
|
|
153
|
+
markManifestDecision("cache_miss");
|
|
141
154
|
scheduleFetch(releaseId);
|
|
142
155
|
return null;
|
|
143
156
|
}
|
|
@@ -150,20 +163,29 @@ export function getReadyManifestForRender(releaseId) {
|
|
|
150
163
|
* manifest is unavailable, or the fetch fails.
|
|
151
164
|
*/
|
|
152
165
|
export async function getReadyManifestForRenderAsync(releaseId) {
|
|
153
|
-
if (!releaseId)
|
|
166
|
+
if (!releaseId) {
|
|
167
|
+
markManifestDecision("no_release_id");
|
|
154
168
|
return null;
|
|
155
|
-
|
|
169
|
+
}
|
|
170
|
+
if (!isReleaseAssetManifestEnabled()) {
|
|
171
|
+
markManifestDecision("disabled");
|
|
156
172
|
return null;
|
|
157
|
-
|
|
173
|
+
}
|
|
174
|
+
if (!resolveFetcher(releaseId)) {
|
|
175
|
+
markManifestDecision("no_fetcher");
|
|
158
176
|
return null;
|
|
177
|
+
}
|
|
159
178
|
const best = findCachedReadyEntry(releaseId);
|
|
160
179
|
if (best) {
|
|
180
|
+
markManifestDecision("cached_ready");
|
|
161
181
|
maybeScheduleReadyRefresh(releaseId, best);
|
|
162
182
|
return best.manifest;
|
|
163
183
|
}
|
|
164
184
|
const plain = manifestCache.get(releaseId);
|
|
165
|
-
if (plain && plain.expiresAt > Date.now())
|
|
185
|
+
if (plain && plain.expiresAt > Date.now()) {
|
|
186
|
+
markManifestDecision("cached_null");
|
|
166
187
|
return null;
|
|
188
|
+
}
|
|
167
189
|
return await fetchManifest(releaseId);
|
|
168
190
|
}
|
|
169
191
|
function scheduleFetch(releaseId) {
|
|
@@ -171,19 +193,24 @@ function scheduleFetch(releaseId) {
|
|
|
171
193
|
}
|
|
172
194
|
function fetchManifest(releaseId) {
|
|
173
195
|
const existing = inFlight.get(releaseId);
|
|
174
|
-
if (existing)
|
|
196
|
+
if (existing) {
|
|
197
|
+
markManifestDecision("await_inflight");
|
|
175
198
|
return existing.promise;
|
|
199
|
+
}
|
|
176
200
|
const active = resolveFetcher(releaseId);
|
|
177
|
-
if (!active)
|
|
201
|
+
if (!active) {
|
|
202
|
+
markManifestDecision("no_fetcher");
|
|
178
203
|
return Promise.resolve(null);
|
|
204
|
+
}
|
|
179
205
|
const fetchGeneration = cacheGeneration;
|
|
180
206
|
const token = Symbol(releaseId);
|
|
181
|
-
const promise =
|
|
207
|
+
const promise = profilePhase("release_manifest.fetch", async () => {
|
|
182
208
|
try {
|
|
183
209
|
const result = await active(releaseId);
|
|
184
210
|
if (fetchGeneration !== cacheGeneration)
|
|
185
211
|
return null;
|
|
186
212
|
if (!result) {
|
|
213
|
+
markManifestDecision("fetch_missing");
|
|
187
214
|
manifestCache.set(releaseId, { manifest: null, expiresAt: Date.now() + NON_READY_TTL_MS });
|
|
188
215
|
return null;
|
|
189
216
|
}
|
|
@@ -191,6 +218,7 @@ function fetchManifest(releaseId) {
|
|
|
191
218
|
? parseReleaseAssetManifest(result.manifest)
|
|
192
219
|
: null;
|
|
193
220
|
if (manifest) {
|
|
221
|
+
markManifestDecision("fetch_ready");
|
|
194
222
|
const key = cacheKey(releaseId, manifest.manifestVersion);
|
|
195
223
|
manifestCache.set(key, {
|
|
196
224
|
manifest,
|
|
@@ -206,6 +234,7 @@ function fetchManifest(releaseId) {
|
|
|
206
234
|
return manifest;
|
|
207
235
|
}
|
|
208
236
|
else {
|
|
237
|
+
markManifestDecision("fetch_not_ready");
|
|
209
238
|
manifestCache.set(releaseId, {
|
|
210
239
|
manifest: null,
|
|
211
240
|
expiresAt: Date.now() + NON_READY_TTL_MS,
|
|
@@ -220,6 +249,7 @@ function fetchManifest(releaseId) {
|
|
|
220
249
|
});
|
|
221
250
|
if (fetchGeneration !== cacheGeneration)
|
|
222
251
|
return null;
|
|
252
|
+
markManifestDecision("fetch_failed");
|
|
223
253
|
manifestCache.set(releaseId, { manifest: null, expiresAt: Date.now() + NON_READY_TTL_MS });
|
|
224
254
|
return null;
|
|
225
255
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA+ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AA0BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;YAIJ,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IA8GjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAsS9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAiGzE,kBAAkB;YA2ClB,cAAc;IAW5B,OAAO,CAAC,uBAAuB;YAYjB,kBAAkB;IAyFhC,OAAO,CAAC,kBAAkB;YAQZ,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAStB"}
|
|
@@ -17,6 +17,7 @@ import { rendererLogger as logger } from "../../utils/index.js";
|
|
|
17
17
|
import { getExtensionName } from "../../utils/path-utils.js";
|
|
18
18
|
import { createBuildVersion } from "../../utils/version.js";
|
|
19
19
|
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
20
|
+
import { profilePhase } from "../../observability/request-profiler.js";
|
|
20
21
|
import { SpanNames } from "../../observability/tracing/span-names.js";
|
|
21
22
|
import { VeryfrontError } from "../../errors/index.js";
|
|
22
23
|
import { FILE_NOT_FOUND, RENDER_ERROR } from "../../errors/error-registry.js";
|
|
@@ -178,12 +179,12 @@ export class RenderPipeline {
|
|
|
178
179
|
if (modulesToLoad.length === 0) {
|
|
179
180
|
return { params, pageProps, layoutProps };
|
|
180
181
|
}
|
|
181
|
-
const loadedModules = await withSpan(SpanNames.RENDER_LOAD_MODULES, () => withTimeoutThrow(this.loadModulesInParallel(modulesToLoad), MODULE_LOAD_TIMEOUT_MS, `Module loading for ${slug}`), { "render.module_count": modulesToLoad.length });
|
|
182
|
+
const loadedModules = await profilePhase("render.load_modules", () => withSpan(SpanNames.RENDER_LOAD_MODULES, () => withTimeoutThrow(this.loadModulesInParallel(modulesToLoad), MODULE_LOAD_TIMEOUT_MS, `Module loading for ${slug}`), { "render.module_count": modulesToLoad.length }));
|
|
182
183
|
const dataJobs = loadedModules.filter((m) => hasDataFetchingFunction(m.mod));
|
|
183
184
|
if (dataJobs.length === 0) {
|
|
184
185
|
return { params, pageProps, layoutProps };
|
|
185
186
|
}
|
|
186
|
-
const dataResults = await withSpan(SpanNames.RENDER_FETCH_DATA, () => withTimeoutThrow(Promise.all(dataJobs.map(async (job) => {
|
|
187
|
+
const dataResults = await profilePhase("render.fetch_data", () => withSpan(SpanNames.RENDER_FETCH_DATA, () => withTimeoutThrow(Promise.all(dataJobs.map(async (job) => {
|
|
187
188
|
try {
|
|
188
189
|
const jobPath = job.path;
|
|
189
190
|
const fetchOptions = {
|
|
@@ -197,7 +198,7 @@ export class RenderPipeline {
|
|
|
197
198
|
catch (error) {
|
|
198
199
|
return { ...job, result: null, error: error };
|
|
199
200
|
}
|
|
200
|
-
})), DATA_FETCH_TIMEOUT_MS, `Data fetch for ${slug}`), { "render.data_job_count": dataJobs.length });
|
|
201
|
+
})), DATA_FETCH_TIMEOUT_MS, `Data fetch for ${slug}`), { "render.data_job_count": dataJobs.length }));
|
|
201
202
|
this.applyFetchedDataResults(slug, dataResults, pageProps, layoutProps);
|
|
202
203
|
return { params, pageProps, layoutProps };
|
|
203
204
|
}
|
|
@@ -255,13 +256,13 @@ export class RenderPipeline {
|
|
|
255
256
|
const renderOnce = () => withSpan("render.page", async () => {
|
|
256
257
|
const { result } = await runWithCSSCollector(async () => {
|
|
257
258
|
const pageResolveStart = performance.now();
|
|
258
|
-
const pageInfo = await withSpan("render.resolve_page", () => this.config.pageResolver.resolvePage(slug), { "render.slug": slug });
|
|
259
|
+
const pageInfo = await profilePhase("render.resolve_page", () => withSpan("render.resolve_page", () => this.config.pageResolver.resolvePage(slug), { "render.slug": slug }));
|
|
259
260
|
timing.pageResolve = Math.round(performance.now() - pageResolveStart);
|
|
260
261
|
const sourceFile = extractRelativePathShared(pageInfo.entity.path, this.config.projectDir);
|
|
261
262
|
try {
|
|
262
263
|
const skipLayouts = isDotPath(slug, pageInfo.entity.path);
|
|
263
264
|
const layoutCollectStart = performance.now();
|
|
264
|
-
const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await withSpan("render.collect_layouts", () => this.config.layoutOrchestrator.collectLayouts(pageInfo), { "render.slug": slug });
|
|
265
|
+
const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await profilePhase("render.collect_layouts", () => withSpan("render.collect_layouts", () => this.config.layoutOrchestrator.collectLayouts(pageInfo), { "render.slug": slug }));
|
|
265
266
|
timing.layoutCollect = Math.round(performance.now() - layoutCollectStart);
|
|
266
267
|
const layoutPreloadPromise = !skipLayouts && layoutResult.nestedLayouts.length > 0
|
|
267
268
|
? this.config.layoutOrchestrator.preloadLayoutModules(layoutResult.nestedLayouts)
|
|
@@ -273,7 +274,7 @@ export class RenderPipeline {
|
|
|
273
274
|
let layoutDataMap = new Map();
|
|
274
275
|
const dataFetchStart = performance.now();
|
|
275
276
|
if (options?.request && options?.url) {
|
|
276
|
-
await withSpan("render.data_fetching", async () => {
|
|
277
|
+
await profilePhase("render.data_fetching", () => withSpan("render.data_fetching", async () => {
|
|
277
278
|
try {
|
|
278
279
|
const dataResolution = await this.resolveDataFetching(slug, pageInfo.entity.path, layoutResult.nestedLayouts, options);
|
|
279
280
|
resolvedParams = dataResolution.params;
|
|
@@ -291,7 +292,7 @@ export class RenderPipeline {
|
|
|
291
292
|
});
|
|
292
293
|
throw error;
|
|
293
294
|
}
|
|
294
|
-
}, { "render.slug": slug });
|
|
295
|
+
}, { "render.slug": slug }));
|
|
295
296
|
}
|
|
296
297
|
timing.dataFetch = Math.round(performance.now() - dataFetchStart);
|
|
297
298
|
const hasResolvedParams = Object.keys(resolvedParams).length > 0;
|
|
@@ -305,7 +306,7 @@ export class RenderPipeline {
|
|
|
305
306
|
}
|
|
306
307
|
: options;
|
|
307
308
|
const bundlePrepStart = performance.now();
|
|
308
|
-
const pageBundleResult = await withSpan("render.prepare_bundles", () => this.config.pageRenderer.preparePageBundles(pageInfo, slug, cacheResult?.cachedModule, mergedOptions), { "render.slug": slug });
|
|
309
|
+
const pageBundleResult = await profilePhase("render.prepare_bundles", () => withSpan("render.prepare_bundles", () => this.config.pageRenderer.preparePageBundles(pageInfo, slug, cacheResult?.cachedModule, mergedOptions), { "render.slug": slug }));
|
|
309
310
|
timing.bundlePrep = Math.round(performance.now() - bundlePrepStart);
|
|
310
311
|
if (pageBundleResult.scriptResult)
|
|
311
312
|
return pageBundleResult.scriptResult;
|
|
@@ -323,13 +324,16 @@ export class RenderPipeline {
|
|
|
323
324
|
const headings = pageBundle.headings || [];
|
|
324
325
|
await layoutPreloadPromise;
|
|
325
326
|
const layoutApplyStart = performance.now();
|
|
326
|
-
const wrappedElement = await withSpan("render.apply_layouts", () => this.config.layoutOrchestrator.applyLayoutsAndWrappers(pageElement, pageInfo, layoutResult.layoutBundle, layoutResult.nestedLayouts, layoutDataMap, options?.url, resolvedParams, mergedFrontmatter, headings, options?.projectSlug), {
|
|
327
|
+
const wrappedElement = await profilePhase("render.apply_layouts", () => withSpan("render.apply_layouts", () => this.config.layoutOrchestrator.applyLayoutsAndWrappers(pageElement, pageInfo, layoutResult.layoutBundle, layoutResult.nestedLayouts, layoutDataMap, options?.url, resolvedParams, mergedFrontmatter, headings, options?.projectSlug), {
|
|
328
|
+
"render.slug": slug,
|
|
329
|
+
"render.layout_count": layoutResult.nestedLayouts.length,
|
|
330
|
+
}));
|
|
327
331
|
timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
|
|
328
332
|
// Snapshot CSS imports collected during module loading (before SSR rendering).
|
|
329
333
|
// These are passed to the HTML generator to be included in the output.
|
|
330
334
|
const collectedCSSImports = getCSSImports();
|
|
331
335
|
const ssrStart = performance.now();
|
|
332
|
-
const ssrResult = await withSpan("render.ssr", () => withTimeoutThrow(this.config.ssrOrchestrator.performSSRRendering(wrappedElement, {
|
|
336
|
+
const ssrResult = await profilePhase("render.ssr", () => withSpan("render.ssr", () => withTimeoutThrow(this.config.ssrOrchestrator.performSSRRendering(wrappedElement, {
|
|
333
337
|
pageInfo,
|
|
334
338
|
pageBundle,
|
|
335
339
|
layoutBundle: layoutResult.layoutBundle,
|
|
@@ -337,7 +341,7 @@ export class RenderPipeline {
|
|
|
337
341
|
collectedMetadata: pageBundleResult.collectedMetadata,
|
|
338
342
|
slug,
|
|
339
343
|
cssImports: collectedCSSImports,
|
|
340
|
-
}, mergedOptions), SSR_RENDER_TIMEOUT_MS, `SSR rendering for ${slug}`), { "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" });
|
|
344
|
+
}, mergedOptions), SSR_RENDER_TIMEOUT_MS, `SSR rendering for ${slug}`), { "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" }));
|
|
341
345
|
timing.ssr = Math.round(performance.now() - ssrStart);
|
|
342
346
|
if (collectedCSSImports.length > 0) {
|
|
343
347
|
renderPipelineLog.debug("CSS imports collected for HTML generation", {
|
|
@@ -411,24 +415,22 @@ export class RenderPipeline {
|
|
|
411
415
|
if (this.config.mode === "development") {
|
|
412
416
|
clearSSRModuleCacheForProject(projectId);
|
|
413
417
|
}
|
|
414
|
-
const pageInfo = await this.config.pageResolver.resolvePage(slug);
|
|
418
|
+
const pageInfo = await profilePhase("page_data.resolve_page", () => this.config.pageResolver.resolvePage(slug));
|
|
415
419
|
const skipLayouts = isDotPath(slug, pageInfo.entity.path);
|
|
416
|
-
const layoutResult = skipLayouts
|
|
417
|
-
? EMPTY_LAYOUT_RESULT
|
|
418
|
-
: await this.config.layoutOrchestrator.collectLayouts(pageInfo);
|
|
420
|
+
const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await profilePhase("page_data.collect_layouts", () => this.config.layoutOrchestrator.collectLayouts(pageInfo));
|
|
419
421
|
const pagePath = extractRelativePathShared(pageInfo.entity.path, this.config.projectDir);
|
|
420
422
|
const fileExtension = getExtensionName(pageInfo.entity.path);
|
|
421
423
|
const pageType = fileExtension;
|
|
422
|
-
const dataResolution = await this.resolveDataFetching(slug, pageInfo.entity.path, layoutResult.nestedLayouts, options);
|
|
424
|
+
const dataResolution = await profilePhase("page_data.resolve_data", () => this.resolveDataFetching(slug, pageInfo.entity.path, layoutResult.nestedLayouts, options));
|
|
423
425
|
const pageProps = dataResolution.pageProps;
|
|
424
426
|
const params = dataResolution.params;
|
|
425
427
|
const layoutProps = serializeLayoutProps(dataResolution.layoutProps);
|
|
426
|
-
const { frontmatter, headings } = await this.extractMdxMetadata(pageType, pageInfo, slug, options, params);
|
|
428
|
+
const { frontmatter, headings } = await profilePhase("page_data.extract_mdx_metadata", () => this.extractMdxMetadata(pageType, pageInfo, slug, options, params));
|
|
427
429
|
const layouts = serializeLayouts(layoutResult.nestedLayouts, this.config.projectDir);
|
|
428
430
|
const providers = [];
|
|
429
431
|
const projectUpdatedAt = this.resolveProjectUpdatedAt();
|
|
430
|
-
const appPath = await this.resolveAppPath();
|
|
431
|
-
const { css, cssAction, cssError } = await this.resolvePageDataCss(slug, options, projectUpdatedAt);
|
|
432
|
+
const appPath = await profilePhase("page_data.resolve_app_path", () => this.resolveAppPath());
|
|
433
|
+
const { css, cssAction, cssError } = await profilePhase("page_data.resolve_css", () => this.resolvePageDataCss(slug, options, projectUpdatedAt));
|
|
432
434
|
resolvePageDataLog.debug("Resolved page data", {
|
|
433
435
|
slug,
|
|
434
436
|
pagePath,
|
|
@@ -515,17 +517,17 @@ export class RenderPipeline {
|
|
|
515
517
|
return { css: cachedCss, cssAction: undefined, cssError: undefined };
|
|
516
518
|
}
|
|
517
519
|
try {
|
|
518
|
-
const renderResult = await withTimeout(this.renderPage(slug, {
|
|
520
|
+
const renderResult = await profilePhase("page_data.css.render_html", () => withTimeout(this.renderPage(slug, {
|
|
519
521
|
...options,
|
|
520
522
|
delivery: "string",
|
|
521
523
|
skipCacheCheck: true,
|
|
522
524
|
skipCachePersist: true,
|
|
523
|
-
}), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`);
|
|
525
|
+
}), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`));
|
|
524
526
|
if (!renderResult?.html) {
|
|
525
527
|
return { css: undefined, cssAction: undefined, cssError: undefined };
|
|
526
528
|
}
|
|
527
529
|
let cssAction;
|
|
528
|
-
let css = await this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId);
|
|
530
|
+
let css = await profilePhase("page_data.css.extract_from_html", () => this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId));
|
|
529
531
|
if (css) {
|
|
530
532
|
resolvePageDataLog.debug("Reused SSR CSS for page data", {
|
|
531
533
|
slug,
|
|
@@ -540,7 +542,7 @@ export class RenderPipeline {
|
|
|
540
542
|
});
|
|
541
543
|
}
|
|
542
544
|
else {
|
|
543
|
-
css = await this.generatePageCssFromHtml(slug, renderResult.html, options);
|
|
545
|
+
css = await profilePhase("page_data.css.generate_from_html", () => this.generatePageCssFromHtml(slug, renderResult.html, options));
|
|
544
546
|
}
|
|
545
547
|
if (css)
|
|
546
548
|
cachePageCss(cssCacheKey, css);
|