vinext 0.0.53 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/inline-css.d.ts +7 -0
- package/dist/build/inline-css.js +50 -0
- package/dist/build/inline-css.js.map +1 -0
- package/dist/build/prerender.js +2 -1
- package/dist/build/prerender.js.map +1 -1
- package/dist/check.js +4 -0
- package/dist/check.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +2 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/client/window-next.d.ts +7 -0
- package/dist/client/window-next.js.map +1 -1
- package/dist/config/next-config.d.ts +83 -1
- package/dist/config/next-config.js +131 -2
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +13 -0
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.d.ts +11 -1
- package/dist/entries/app-browser-entry.js +16 -6
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +8 -1
- package/dist/entries/app-rsc-entry.js +18 -5
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +21 -1
- package/dist/entries/app-rsc-manifest.js +6 -4
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.d.ts +4 -1
- package/dist/entries/pages-client-entry.js +18 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +82 -4
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +1 -10
- package/dist/entries/runtime-entry-module.js +2 -12
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +63 -5
- package/dist/index.js.map +1 -1
- package/dist/plugins/remove-console.d.ts +16 -0
- package/dist/plugins/remove-console.js +176 -0
- package/dist/plugins/remove-console.js.map +1 -0
- package/dist/routing/app-route-graph.d.ts +24 -1
- package/dist/routing/app-route-graph.js +52 -4
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -2
- package/dist/routing/app-router.js +2 -2
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +21 -1
- package/dist/routing/file-matcher.js +39 -1
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +1 -1
- package/dist/routing/pages-router.js +10 -3
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/server/api-handler.js +1 -1
- package/dist/server/app-browser-entry.js +25 -16
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +4 -0
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +13 -4
- package/dist/server/app-elements-wire.js +10 -1
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +2 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +15 -5
- package/dist/server/app-fallback-renderer.js +10 -4
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-inline-css-client.d.ts +7 -0
- package/dist/server/app-inline-css-client.js +37 -0
- package/dist/server/app-inline-css-client.js.map +1 -0
- package/dist/server/app-page-boundary.d.ts +21 -1
- package/dist/server/app-page-boundary.js +28 -3
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +7 -3
- package/dist/server/app-page-cache.js +7 -7
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -1
- package/dist/server/app-page-dispatch.js +126 -79
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.js +12 -28
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-render-identity.d.ts +22 -0
- package/dist/server/app-page-render-identity.js +42 -0
- package/dist/server/app-page-render-identity.js.map +1 -0
- package/dist/server/app-page-render.d.ts +8 -1
- package/dist/server/app-page-render.js +4 -1
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +6 -3
- package/dist/server/app-page-request.js +5 -2
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.js +2 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +15 -0
- package/dist/server/app-page-route-wiring.js +7 -5
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +11 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-response.js +37 -5
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +14 -3
- package/dist/server/app-rsc-handler.js +45 -5
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +2 -1
- package/dist/server/app-rsc-request-normalization.js +3 -2
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +21 -3
- package/dist/server/app-server-action-execution.js +42 -7
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +22 -7
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-error-meta.js +3 -3
- package/dist/server/app-ssr-error-meta.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +2 -1
- package/dist/server/app-ssr-stream.js +176 -31
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/client-trace-metadata.d.ts +31 -0
- package/dist/server/client-trace-metadata.js +83 -0
- package/dist/server/client-trace-metadata.js.map +1 -0
- package/dist/server/cookie-utils.d.ts +13 -0
- package/dist/server/cookie-utils.js +20 -0
- package/dist/server/cookie-utils.js.map +1 -0
- package/dist/server/dev-server.d.ts +8 -1
- package/dist/server/dev-server.js +34 -5
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/html.d.ts +2 -1
- package/dist/server/html.js +6 -1
- package/dist/server/html.js.map +1 -1
- package/dist/server/isr-cache.d.ts +7 -5
- package/dist/server/isr-cache.js +17 -6
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/middleware-runtime.js +1 -2
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/pages-document-initial-props.d.ts +7 -0
- package/dist/server/pages-document-initial-props.js +14 -0
- package/dist/server/pages-document-initial-props.js.map +1 -0
- package/dist/server/pages-page-data.js +3 -0
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-method.d.ts +48 -0
- package/dist/server/pages-page-method.js +19 -0
- package/dist/server/pages-page-method.js.map +1 -0
- package/dist/server/pages-page-response.d.ts +6 -0
- package/dist/server/pages-page-response.js +10 -3
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/pages-serializable-props.d.ts +25 -0
- package/dist/server/pages-serializable-props.js +69 -0
- package/dist/server/pages-serializable-props.js.map +1 -0
- package/dist/server/prod-server.js +3 -0
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/server-action-not-found.js +3 -2
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/static-file-cache.js +2 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/shims/app-router-scroll-state.d.ts +4 -2
- package/dist/shims/app-router-scroll-state.js +16 -3
- package/dist/shims/app-router-scroll-state.js.map +1 -1
- package/dist/shims/app-router-scroll.d.ts +16 -2
- package/dist/shims/app-router-scroll.js +18 -3
- package/dist/shims/app-router-scroll.js.map +1 -1
- package/dist/shims/cache.d.ts +6 -0
- package/dist/shims/cache.js +7 -0
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/error.js +3 -0
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/headers.d.ts +7 -0
- package/dist/shims/headers.js +9 -1
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/internal/app-route-detection.d.ts +37 -0
- package/dist/shims/internal/app-route-detection.js +69 -0
- package/dist/shims/internal/app-route-detection.js.map +1 -0
- package/dist/shims/link.d.ts +18 -2
- package/dist/shims/link.js +70 -6
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +7 -6
- package/dist/shims/metadata.js +9 -5
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +1 -2
- package/dist/shims/navigation.js +63 -12
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.d.ts +5 -0
- package/dist/shims/router.js +14 -4
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +11 -1
- package/dist/shims/script.js +75 -6
- package/dist/shims/script.js.map +1 -1
- package/dist/utils/path.d.ts +13 -0
- package/dist/utils/path.js +16 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
|
|
2
|
-
import { createInlineScriptTag, safeJsonStringify } from "./html.js";
|
|
2
|
+
import { createInlineScriptTag, escapeHtmlAttr, htmlTokenListContains, safeJsonStringify } from "./html.js";
|
|
3
3
|
import { bytesToBase64, concatUint8Arrays } from "./app-rsc-embedded-chunks.js";
|
|
4
4
|
//#region src/server/app-ssr-stream.ts
|
|
5
5
|
const NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION)})]`;
|
|
@@ -86,6 +86,130 @@ function createRscEmbedTransform(embedStream, scriptNonce) {
|
|
|
86
86
|
function fixPreloadAs(html) {
|
|
87
87
|
return html.replace(/<link(?=[^>]*\srel="preload")[^>]*>/g, (tag) => tag.replace(" as=\"stylesheet\"", " as=\"style\""));
|
|
88
88
|
}
|
|
89
|
+
const LINK_TAG_RE = /<link\b[^>]*>/gi;
|
|
90
|
+
const HTML_REWRITE_EXCLUDED_REGION_RE = /<!--[\s\S]*?-->|<(script|style|textarea|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
|
91
|
+
const HTML_REWRITE_EXCLUDED_REGION_START_RE = /<!--|<(script|style|textarea|title)\b[^>]*>/gi;
|
|
92
|
+
function getHtmlAttribute(tag, name) {
|
|
93
|
+
const attrRe = /\s([^\s"'=<>`]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
94
|
+
let match;
|
|
95
|
+
while ((match = attrRe.exec(tag)) !== null) {
|
|
96
|
+
if (match[1]?.toLowerCase() !== name.toLowerCase()) continue;
|
|
97
|
+
return match[2] ?? match[3] ?? match[4] ?? "";
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function htmlAttributeHasToken(tag, name, token) {
|
|
102
|
+
return htmlTokenListContains(getHtmlAttribute(tag, name), token);
|
|
103
|
+
}
|
|
104
|
+
function getInlineCss(manifest, href) {
|
|
105
|
+
if (Object.prototype.hasOwnProperty.call(manifest, href)) return manifest[href] ?? "";
|
|
106
|
+
try {
|
|
107
|
+
const pathname = new URL(href).pathname;
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(manifest, pathname)) return manifest[pathname] ?? "";
|
|
109
|
+
} catch {}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const TRAILING_LINK_OPEN_RE = /<link/gi;
|
|
113
|
+
function splitTrailingIncompleteLinkTag(html) {
|
|
114
|
+
TRAILING_LINK_OPEN_RE.lastIndex = 0;
|
|
115
|
+
let lastIndex = -1;
|
|
116
|
+
let match;
|
|
117
|
+
while ((match = TRAILING_LINK_OPEN_RE.exec(html)) !== null) lastIndex = match.index;
|
|
118
|
+
if (lastIndex === -1) return {
|
|
119
|
+
complete: html,
|
|
120
|
+
trailing: ""
|
|
121
|
+
};
|
|
122
|
+
if (html.indexOf(">", lastIndex) !== -1) return {
|
|
123
|
+
complete: html,
|
|
124
|
+
trailing: ""
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
complete: html.slice(0, lastIndex),
|
|
128
|
+
trailing: html.slice(lastIndex)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function findTrailingOpenHtmlRewriteExcludedRegionStart(html) {
|
|
132
|
+
let match;
|
|
133
|
+
HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex = 0;
|
|
134
|
+
while ((match = HTML_REWRITE_EXCLUDED_REGION_START_RE.exec(html)) !== null) {
|
|
135
|
+
const start = match.index;
|
|
136
|
+
if (match[0] === "<!--") {
|
|
137
|
+
const close = html.indexOf("-->", HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex);
|
|
138
|
+
if (close === -1) return start;
|
|
139
|
+
HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex = close + 3;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const tagName = match[1]?.toLowerCase();
|
|
143
|
+
if (!tagName) continue;
|
|
144
|
+
const close = new RegExp(`</${tagName}\\s*>`, "i").exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));
|
|
145
|
+
if (!close) return start;
|
|
146
|
+
HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex += close.index + close[0].length;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function splitTrailingInlineCssRewriteBoundary(html) {
|
|
151
|
+
const linkSplit = splitTrailingIncompleteLinkTag(html);
|
|
152
|
+
const incompleteLinkStart = linkSplit.trailing ? linkSplit.complete.length : null;
|
|
153
|
+
const openRegionStart = findTrailingOpenHtmlRewriteExcludedRegionStart(html);
|
|
154
|
+
const trailingStart = incompleteLinkStart === null ? openRegionStart : openRegionStart === null ? incompleteLinkStart : Math.min(incompleteLinkStart, openRegionStart);
|
|
155
|
+
if (trailingStart === null) return {
|
|
156
|
+
complete: html,
|
|
157
|
+
trailing: ""
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
complete: html.slice(0, trailingStart),
|
|
161
|
+
trailing: html.slice(trailingStart)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function escapeStyleText(css) {
|
|
165
|
+
return css.replace(/<\/style/gi, "<\\/style");
|
|
166
|
+
}
|
|
167
|
+
const CSS_PREPEND_UNSAFE_PREAMBLE_RE = /^\uFEFF?(?:\s|\/\*[\s\S]*?\*\/)*@(charset|import|layer|namespace)\b/i;
|
|
168
|
+
function canPrependCss(css) {
|
|
169
|
+
return !CSS_PREPEND_UNSAFE_PREAMBLE_RE.test(css);
|
|
170
|
+
}
|
|
171
|
+
function replaceLinkTags(html, replaceLinkTag) {
|
|
172
|
+
LINK_TAG_RE.lastIndex = 0;
|
|
173
|
+
return html.replace(LINK_TAG_RE, replaceLinkTag);
|
|
174
|
+
}
|
|
175
|
+
function replaceLinkTagsOutsideRawText(html, replaceLinkTag) {
|
|
176
|
+
let rewritten = "";
|
|
177
|
+
let cursor = 0;
|
|
178
|
+
let match;
|
|
179
|
+
HTML_REWRITE_EXCLUDED_REGION_RE.lastIndex = 0;
|
|
180
|
+
while ((match = HTML_REWRITE_EXCLUDED_REGION_RE.exec(html)) !== null) {
|
|
181
|
+
rewritten += replaceLinkTags(html.slice(cursor, match.index), replaceLinkTag);
|
|
182
|
+
rewritten += match[0];
|
|
183
|
+
cursor = match.index + match[0].length;
|
|
184
|
+
}
|
|
185
|
+
const tail = html.slice(cursor);
|
|
186
|
+
const openRegionStart = findTrailingOpenHtmlRewriteExcludedRegionStart(tail);
|
|
187
|
+
if (openRegionStart === null) return rewritten + replaceLinkTags(tail, replaceLinkTag);
|
|
188
|
+
return rewritten + replaceLinkTags(tail.slice(0, openRegionStart), replaceLinkTag) + tail.slice(openRegionStart);
|
|
189
|
+
}
|
|
190
|
+
function rewriteInlineCssStylesheetLinks(html, inlineCssManifest, prependCss, ssrScriptNonce) {
|
|
191
|
+
if (!inlineCssManifest || Object.keys(inlineCssManifest).length === 0) return {
|
|
192
|
+
html,
|
|
193
|
+
consumedPrependCss: false
|
|
194
|
+
};
|
|
195
|
+
let consumedPrependCss = false;
|
|
196
|
+
return {
|
|
197
|
+
html: replaceLinkTagsOutsideRawText(html, (tag) => {
|
|
198
|
+
if (!htmlAttributeHasToken(tag, "rel", "stylesheet")) return tag;
|
|
199
|
+
const href = getHtmlAttribute(tag, "href");
|
|
200
|
+
const precedence = getHtmlAttribute(tag, "data-precedence") ?? getHtmlAttribute(tag, "precedence");
|
|
201
|
+
if (!href || !precedence) return tag;
|
|
202
|
+
const css = getInlineCss(inlineCssManifest, href);
|
|
203
|
+
if (css === null) return tag;
|
|
204
|
+
const effectiveNonce = getHtmlAttribute(tag, "nonce") ?? ssrScriptNonce;
|
|
205
|
+
const nonceAttr = effectiveNonce ? ` nonce="${escapeHtmlAttr(effectiveNonce)}"` : "";
|
|
206
|
+
const cssPrefix = !consumedPrependCss && prependCss.length > 0 && canPrependCss(css) ? `${prependCss}\n` : "";
|
|
207
|
+
consumedPrependCss ||= cssPrefix.length > 0;
|
|
208
|
+
return `<style data-vinext-inline-css${nonceAttr} data-precedence="${escapeHtmlAttr(precedence)}" data-href="${escapeHtmlAttr(href)}">${escapeStyleText(cssPrefix + css)}</style>`;
|
|
209
|
+
}),
|
|
210
|
+
consumedPrependCss
|
|
211
|
+
};
|
|
212
|
+
}
|
|
89
213
|
/**
|
|
90
214
|
* Match the `<head ...>` opening tag in a chunk. Matches both bare `<head>`
|
|
91
215
|
* and `<head class="foo">` shapes. Used to splice HTML immediately after the
|
|
@@ -120,18 +244,25 @@ const HEAD_OPEN_RE = /<head\b[^>]*>/;
|
|
|
120
244
|
* to no-op and let the user-rendered Script (in its source-order
|
|
121
245
|
* position) ship as-is.
|
|
122
246
|
*/
|
|
123
|
-
function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadOpenHTML = "") {
|
|
247
|
+
function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadOpenHTML = "", inlineCssManifest, inlineCssPrependCss = "", inlineCssPrependFallbackHTML = "", inlineCssScriptNonce) {
|
|
124
248
|
const decoder = new TextDecoder();
|
|
125
249
|
const encoder = new TextEncoder();
|
|
126
250
|
const insertsPerFlush = typeof injectHTML === "function";
|
|
127
251
|
let injected = false;
|
|
128
252
|
let preHeadInjected = false;
|
|
129
253
|
let buffered = [];
|
|
254
|
+
let pendingHtml = "";
|
|
130
255
|
let timeoutId = null;
|
|
256
|
+
const hasInlineCssManifest = inlineCssManifest !== void 0 && Object.keys(inlineCssManifest).length > 0;
|
|
131
257
|
const readInsertion = () => typeof injectHTML === "function" ? injectHTML() : injectHTML;
|
|
132
258
|
const readPreHeadInsertion = () => typeof injectAfterHeadOpenHTML === "function" ? injectAfterHeadOpenHTML() : injectAfterHeadOpenHTML;
|
|
259
|
+
const readInlineCssPrependFallback = () => {
|
|
260
|
+
if (!inlineCssPrependCss || !inlineCssPrependFallbackHTML) return "";
|
|
261
|
+
inlineCssPrependCss = "";
|
|
262
|
+
return inlineCssPrependFallbackHTML;
|
|
263
|
+
};
|
|
133
264
|
const emitInsertion = (controller) => {
|
|
134
|
-
const insertion = readInsertion();
|
|
265
|
+
const insertion = readInlineCssPrependFallback() + readInsertion();
|
|
135
266
|
if (insertion) controller.enqueue(encoder.encode(insertion));
|
|
136
267
|
};
|
|
137
268
|
/**
|
|
@@ -140,11 +271,11 @@ function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadO
|
|
|
140
271
|
* rewritten chunk and a flag indicating whether the splice happened, so the
|
|
141
272
|
* caller can mark `preHeadInjected` and stop scanning further chunks.
|
|
142
273
|
*
|
|
143
|
-
* NOTE: This is called only when `<head ...>` lies fully inside
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
274
|
+
* NOTE: This is called only when `<head ...>` lies fully inside the current
|
|
275
|
+
* tick-buffered batch. We deliberately avoid retaining arbitrary output until
|
|
276
|
+
* a future chunk completes `<head ...>`, which would delay TTFB and complicate
|
|
277
|
+
* the existing `</head>` injection path. In practice React Fizz emits the
|
|
278
|
+
* opening shell as a single batch.
|
|
148
279
|
*/
|
|
149
280
|
const spliceAfterHeadOpen = (chunk) => {
|
|
150
281
|
if (preHeadInjected) return {
|
|
@@ -167,35 +298,47 @@ function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadO
|
|
|
167
298
|
spliced: true
|
|
168
299
|
};
|
|
169
300
|
};
|
|
170
|
-
const flushBuffered = (controller) => {
|
|
171
|
-
if (buffered.length === 0) return;
|
|
301
|
+
const flushBuffered = (controller, final = false) => {
|
|
302
|
+
if (buffered.length === 0 && !pendingHtml) return;
|
|
303
|
+
const rawHtml = pendingHtml + buffered.join("");
|
|
304
|
+
buffered = [];
|
|
305
|
+
pendingHtml = "";
|
|
306
|
+
const split = final || !hasInlineCssManifest ? {
|
|
307
|
+
complete: rawHtml,
|
|
308
|
+
trailing: ""
|
|
309
|
+
} : splitTrailingInlineCssRewriteBoundary(rawHtml);
|
|
310
|
+
if (split.trailing) pendingHtml = split.trailing;
|
|
311
|
+
if (!split.complete) return;
|
|
172
312
|
if (injected && insertsPerFlush) emitInsertion(controller);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
313
|
+
const preparedHtml = fixPreloadAs(split.complete);
|
|
314
|
+
const inlineCssResult = hasInlineCssManifest ? rewriteInlineCssStylesheetLinks(preparedHtml, inlineCssManifest, inlineCssPrependCss, inlineCssScriptNonce) : {
|
|
315
|
+
html: preparedHtml,
|
|
316
|
+
consumedPrependCss: false
|
|
317
|
+
};
|
|
318
|
+
if (inlineCssResult.consumedPrependCss) inlineCssPrependCss = "";
|
|
319
|
+
let working = inlineCssResult.html;
|
|
320
|
+
if (!preHeadInjected) {
|
|
321
|
+
const result = spliceAfterHeadOpen(working);
|
|
322
|
+
if (result.spliced) {
|
|
323
|
+
working = result.chunk;
|
|
324
|
+
preHeadInjected = true;
|
|
181
325
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
326
|
+
}
|
|
327
|
+
if (!injected) {
|
|
328
|
+
const headEnd = working.indexOf("</head>");
|
|
329
|
+
if (headEnd !== -1) {
|
|
330
|
+
const before = working.slice(0, headEnd);
|
|
331
|
+
const after = working.slice(headEnd);
|
|
332
|
+
controller.enqueue(encoder.encode(before + readInlineCssPrependFallback() + readInsertion() + after));
|
|
333
|
+
injected = true;
|
|
334
|
+
return;
|
|
191
335
|
}
|
|
192
|
-
controller.enqueue(encoder.encode(working));
|
|
193
336
|
}
|
|
194
|
-
|
|
337
|
+
controller.enqueue(encoder.encode(working));
|
|
195
338
|
};
|
|
196
339
|
return new TransformStream({
|
|
197
340
|
transform(chunk, controller) {
|
|
198
|
-
buffered.push(
|
|
341
|
+
buffered.push(decoder.decode(chunk, { stream: true }));
|
|
199
342
|
if (timeoutId !== null) return;
|
|
200
343
|
timeoutId = setTimeout(() => {
|
|
201
344
|
try {
|
|
@@ -211,7 +354,9 @@ function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadO
|
|
|
211
354
|
clearTimeout(timeoutId);
|
|
212
355
|
timeoutId = null;
|
|
213
356
|
}
|
|
214
|
-
|
|
357
|
+
const remainder = decoder.decode();
|
|
358
|
+
if (remainder) buffered.push(remainder);
|
|
359
|
+
flushBuffered(controller, true);
|
|
215
360
|
if (!injected) {
|
|
216
361
|
emitInsertion(controller);
|
|
217
362
|
injected = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import { createInlineScriptTag, safeJsonStringify } from \"./html.js\";\nimport {\n bytesToBase64,\n concatUint8Arrays,\n RSC_EMBEDDED_BINARY_CHUNK,\n type RscEmbeddedChunk,\n} from \"./app-rsc-embedded-chunks.js\";\nimport { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from \"../client/navigation-runtime.js\";\n\ntype RscEmbedTransform = {\n flush(): string;\n finalize(): Promise<string>;\n /** Resolves when all raw bytes from the embed stream have been read. */\n getRawBuffer(): Promise<ArrayBuffer>;\n};\n\ntype HtmlInsertion = string | (() => string);\n\nconst NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(\n NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION,\n)})]`;\n\nexport function navigationRuntimeRscBootstrapExpression(): string {\n return `((${NAVIGATION_RUNTIME_REFERENCE}??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]})`;\n}\n\nexport function createNavigationRuntimeRscMetadataScript(\n params: Record<string, string | string[]>,\n nav: { pathname: string; searchParams: [string, string][] },\n): string {\n return (\n \"Object.assign(\" +\n navigationRuntimeRscBootstrapExpression() +\n \",{params:\" +\n safeJsonStringify(params) +\n \",nav:\" +\n safeJsonStringify(nav) +\n \"})\"\n );\n}\n\nfunction createNavigationRuntimeRscChunkScript(chunk: RscEmbeddedChunk): string {\n return navigationRuntimeRscBootstrapExpression() + \".rsc.push(\" + safeJsonStringify(chunk) + \")\";\n}\n\nfunction createNavigationRuntimeRscDoneScript(): string {\n return navigationRuntimeRscBootstrapExpression() + \".done=true\";\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d*:HL\\[.*?),\"stylesheet\"(\\]|,)/g, '$1,\"style\"$2');\n}\n\n/**\n * Create a helper that progressively embeds RSC chunks as inline <script> tags.\n * The browser entry turns the embedded chunks back into Uint8Array data.\n */\nexport function createRscEmbedTransform(\n embedStream: ReadableStream<Uint8Array>,\n scriptNonce?: string,\n): RscEmbedTransform {\n const reader = embedStream.getReader();\n let pendingChunks: RscEmbeddedChunk[] = [];\n const rawChunks: Uint8Array[] = [];\n let reading = false;\n\n async function pumpReader(): Promise<void> {\n if (reading) return;\n reading = true;\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n // Accumulate raw bytes BEFORE fixFlightHints so the cache stores\n // unmodified RSC data. The embed script path below applies fixes.\n rawChunks.push(result.value);\n try {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const text = decoder.decode(result.value);\n // The RSC entry already fixes HL hints at the source. Keep this second\n // pass as defense in depth for any embed stream that bypasses that\n // wrapper; the rewrite is idempotent, so double-application is safe.\n pendingChunks.push(fixFlightHints(text));\n } catch {\n pendingChunks.push([RSC_EMBEDDED_BINARY_CHUNK, bytesToBase64(result.value)]);\n }\n }\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[vinext] RSC embed stream read error:\", error);\n }\n throw error;\n } finally {\n reading = false;\n }\n }\n\n const pumpPromise = pumpReader();\n\n return {\n flush(): string {\n if (pendingChunks.length === 0) return \"\";\n\n const chunks = pendingChunks;\n pendingChunks = [];\n\n let scripts = \"\";\n for (const chunk of chunks) {\n scripts += createInlineScriptTag(createNavigationRuntimeRscChunkScript(chunk), scriptNonce);\n }\n return scripts;\n },\n\n async finalize(): Promise<string> {\n await pumpPromise;\n let scripts = this.flush();\n scripts += createInlineScriptTag(createNavigationRuntimeRscDoneScript(), scriptNonce);\n return scripts;\n },\n\n async getRawBuffer(): Promise<ArrayBuffer> {\n await pumpPromise;\n const buffer = concatUint8Arrays(rawChunks);\n rawChunks.length = 0;\n return buffer.buffer;\n },\n };\n}\n\n/**\n * Fix invalid preload \"as\" values in server-rendered HTML.\n * React Fizz emits <link rel=\"preload\" as=\"stylesheet\"> for CSS, but the\n * HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixPreloadAs(html: string): string {\n return html.replace(/<link(?=[^>]*\\srel=\"preload\")[^>]*>/g, (tag) =>\n tag.replace(' as=\"stylesheet\"', ' as=\"style\"'),\n );\n}\n\n/**\n * Match the `<head ...>` opening tag in a chunk. Matches both bare `<head>`\n * and `<head class=\"foo\">` shapes. Used to splice HTML immediately after the\n * opening tag so injected content runs before any React-emitted resource\n * hints (stylesheets, modulepreloads) that React Float hoists into `<head>`.\n */\nconst HEAD_OPEN_RE = /<head\\b[^>]*>/;\n\n/**\n * Create the tick-buffered HTML transform that injects RSC scripts between\n * React Fizz flush cycles without corrupting split HTML chunks.\n *\n * Two insertion points are supported in tandem:\n *\n * - `injectHTML` is emitted immediately before `</head>`. This is where the\n * bulk of vinext's head additions live (RSC navigation runtime metadata,\n * bootstrap modulepreload, server-inserted HTML, font preloads, etc.).\n * - `injectAfterHeadOpenHTML` is emitted immediately after the `<head ...>`\n * opening tag so the content runs before any React-emitted resource\n * hints. This is where inline `<Script strategy=\"beforeInteractive\">`\n * captures land so the no-flash dark-mode pattern works.\n *\n * Fallback behaviour differs by insertion point:\n *\n * - `injectHTML` is emitted at end-of-stream by the `flush` handler when no\n * chunk ever contained `</head>` — callers still see the payload on\n * highly fragmented streams (just at the end of the body rather than in\n * the head).\n * - `injectAfterHeadOpenHTML` is silently dropped when `<head ...>` is not\n * found in a discoverable chunk. Emitting it at end-of-stream would put\n * it after the document body, defeating the point — the splice has to\n * happen before resource hints to be useful, so the safer behaviour is\n * to no-op and let the user-rendered Script (in its source-order\n * position) ship as-is.\n */\nexport function createTickBufferedTransform(\n rscEmbed: RscEmbedTransform,\n injectHTML: HtmlInsertion = \"\",\n injectAfterHeadOpenHTML: HtmlInsertion = \"\",\n): TransformStream<Uint8Array, Uint8Array> {\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n const insertsPerFlush = typeof injectHTML === \"function\";\n let injected = false;\n let preHeadInjected = false;\n let buffered: string[] = [];\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n const readInsertion = (): string =>\n typeof injectHTML === \"function\" ? injectHTML() : injectHTML;\n const readPreHeadInsertion = (): string =>\n typeof injectAfterHeadOpenHTML === \"function\"\n ? injectAfterHeadOpenHTML()\n : injectAfterHeadOpenHTML;\n const emitInsertion = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n const insertion = readInsertion();\n if (insertion) {\n controller.enqueue(encoder.encode(insertion));\n }\n };\n\n /**\n * Splice the pre-head insertion (typically captured beforeInteractive inline\n * scripts) immediately after the `<head ...>` opening tag. Returns the\n * rewritten chunk and a flag indicating whether the splice happened, so the\n * caller can mark `preHeadInjected` and stop scanning further chunks.\n *\n * NOTE: This is called only when `<head ...>` lies fully inside `chunk` —\n * we deliberately avoid stitching across chunk boundaries because doing so\n * would force the transform to hold output until it had seen `<head ...>`,\n * which both delays TTFB and complicates the existing `</head>` injection\n * path. In practice React Fizz emits the opening shell as a single chunk.\n */\n const spliceAfterHeadOpen = (chunk: string): { chunk: string; spliced: boolean } => {\n if (preHeadInjected) return { chunk, spliced: false };\n const insertion = readPreHeadInsertion();\n if (!insertion) return { chunk, spliced: false };\n const match = HEAD_OPEN_RE.exec(chunk);\n if (!match) return { chunk, spliced: false };\n const insertAt = match.index + match[0].length;\n return {\n chunk: chunk.slice(0, insertAt) + insertion + chunk.slice(insertAt),\n spliced: true,\n };\n };\n\n const flushBuffered = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n if (buffered.length === 0) return;\n\n if (injected && insertsPerFlush) {\n // Emit newly collected server-inserted HTML before the next Fizz HTML\n // batch so CSS-in-JS styles precede the elements they style.\n emitInsertion(controller);\n }\n\n for (const chunk of buffered) {\n let working = chunk;\n if (!preHeadInjected) {\n const result = spliceAfterHeadOpen(working);\n if (result.spliced) {\n working = result.chunk;\n preHeadInjected = true;\n }\n }\n if (!injected) {\n const headEnd = working.indexOf(\"</head>\");\n if (headEnd !== -1) {\n const before = working.slice(0, headEnd);\n const after = working.slice(headEnd);\n controller.enqueue(encoder.encode(before + readInsertion() + after));\n injected = true;\n continue;\n }\n }\n controller.enqueue(encoder.encode(working));\n }\n buffered = [];\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n buffered.push(fixPreloadAs(decoder.decode(chunk, { stream: true })));\n\n if (timeoutId !== null) return;\n\n timeoutId = setTimeout(() => {\n try {\n flushBuffered(controller);\n\n const rscScripts = rscEmbed.flush();\n if (rscScripts) {\n controller.enqueue(encoder.encode(rscScripts));\n }\n } catch {\n // Stream was cancelled between when the timeout was registered and\n // when it fired (e.g. client disconnected, health-check cancelled\n // the response body). Ignore — the stream is already closed.\n }\n\n timeoutId = null;\n }, 0);\n },\n\n async flush(controller) {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n flushBuffered(controller);\n\n if (!injected) {\n emitInsertion(controller);\n injected = true;\n } else if (insertsPerFlush) {\n emitInsertion(controller);\n }\n\n const finalScripts = await rscEmbed.finalize();\n if (finalScripts) {\n controller.enqueue(encoder.encode(finalScripts));\n }\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import {\n createInlineScriptTag,\n escapeHtmlAttr,\n htmlTokenListContains,\n safeJsonStringify,\n} from \"./html.js\";\nimport {\n bytesToBase64,\n concatUint8Arrays,\n RSC_EMBEDDED_BINARY_CHUNK,\n type RscEmbeddedChunk,\n} from \"./app-rsc-embedded-chunks.js\";\nimport { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from \"../client/navigation-runtime.js\";\n\ntype RscEmbedTransform = {\n flush(): string;\n finalize(): Promise<string>;\n /** Resolves when all raw bytes from the embed stream have been read. */\n getRawBuffer(): Promise<ArrayBuffer>;\n};\n\ntype HtmlInsertion = string | (() => string);\ntype InlineCssManifest = Record<string, string>;\ntype InlineCssRewriteResult = {\n html: string;\n consumedPrependCss: boolean;\n};\n\nconst NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(\n NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION,\n)})]`;\n\nexport function navigationRuntimeRscBootstrapExpression(): string {\n return `((${NAVIGATION_RUNTIME_REFERENCE}??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]})`;\n}\n\nexport function createNavigationRuntimeRscMetadataScript(\n params: Record<string, string | string[]>,\n nav: { pathname: string; searchParams: [string, string][] },\n): string {\n return (\n \"Object.assign(\" +\n navigationRuntimeRscBootstrapExpression() +\n \",{params:\" +\n safeJsonStringify(params) +\n \",nav:\" +\n safeJsonStringify(nav) +\n \"})\"\n );\n}\n\nfunction createNavigationRuntimeRscChunkScript(chunk: RscEmbeddedChunk): string {\n return navigationRuntimeRscBootstrapExpression() + \".rsc.push(\" + safeJsonStringify(chunk) + \")\";\n}\n\nfunction createNavigationRuntimeRscDoneScript(): string {\n return navigationRuntimeRscBootstrapExpression() + \".done=true\";\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d*:HL\\[.*?),\"stylesheet\"(\\]|,)/g, '$1,\"style\"$2');\n}\n\n/**\n * Create a helper that progressively embeds RSC chunks as inline <script> tags.\n * The browser entry turns the embedded chunks back into Uint8Array data.\n */\nexport function createRscEmbedTransform(\n embedStream: ReadableStream<Uint8Array>,\n scriptNonce?: string,\n): RscEmbedTransform {\n const reader = embedStream.getReader();\n let pendingChunks: RscEmbeddedChunk[] = [];\n const rawChunks: Uint8Array[] = [];\n let reading = false;\n\n async function pumpReader(): Promise<void> {\n if (reading) return;\n reading = true;\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n // Accumulate raw bytes BEFORE fixFlightHints so the cache stores\n // unmodified RSC data. The embed script path below applies fixes.\n rawChunks.push(result.value);\n try {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const text = decoder.decode(result.value);\n // The RSC entry already fixes HL hints at the source. Keep this second\n // pass as defense in depth for any embed stream that bypasses that\n // wrapper; the rewrite is idempotent, so double-application is safe.\n pendingChunks.push(fixFlightHints(text));\n } catch {\n pendingChunks.push([RSC_EMBEDDED_BINARY_CHUNK, bytesToBase64(result.value)]);\n }\n }\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[vinext] RSC embed stream read error:\", error);\n }\n throw error;\n } finally {\n reading = false;\n }\n }\n\n const pumpPromise = pumpReader();\n\n return {\n flush(): string {\n if (pendingChunks.length === 0) return \"\";\n\n const chunks = pendingChunks;\n pendingChunks = [];\n\n let scripts = \"\";\n for (const chunk of chunks) {\n scripts += createInlineScriptTag(createNavigationRuntimeRscChunkScript(chunk), scriptNonce);\n }\n return scripts;\n },\n\n async finalize(): Promise<string> {\n await pumpPromise;\n let scripts = this.flush();\n scripts += createInlineScriptTag(createNavigationRuntimeRscDoneScript(), scriptNonce);\n return scripts;\n },\n\n async getRawBuffer(): Promise<ArrayBuffer> {\n await pumpPromise;\n const buffer = concatUint8Arrays(rawChunks);\n rawChunks.length = 0;\n return buffer.buffer;\n },\n };\n}\n\n/**\n * Fix invalid preload \"as\" values in server-rendered HTML.\n * React Fizz emits <link rel=\"preload\" as=\"stylesheet\"> for CSS, but the\n * HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixPreloadAs(html: string): string {\n return html.replace(/<link(?=[^>]*\\srel=\"preload\")[^>]*>/g, (tag) =>\n tag.replace(' as=\"stylesheet\"', ' as=\"style\"'),\n );\n}\n\n// These `g`-flag regexes carry mutable `lastIndex` state. Every consumer below\n// resets `lastIndex` before use, which is safe only because they run to\n// completion synchronously within a single call. They must not be shared across\n// concurrent/interleaved call paths.\nconst LINK_TAG_RE = /<link\\b[^>]*>/gi;\nconst HTML_REWRITE_EXCLUDED_REGION_RE =\n /<!--[\\s\\S]*?-->|<(script|style|textarea|title)\\b[^>]*>[\\s\\S]*?<\\/\\1\\s*>/gi;\nconst HTML_REWRITE_EXCLUDED_REGION_START_RE = /<!--|<(script|style|textarea|title)\\b[^>]*>/gi;\n\nfunction getHtmlAttribute(tag: string, name: string): string | null {\n const attrRe = /\\s([^\\s\"'=<>`]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n let match: RegExpExecArray | null;\n\n while ((match = attrRe.exec(tag)) !== null) {\n if (match[1]?.toLowerCase() !== name.toLowerCase()) continue;\n return match[2] ?? match[3] ?? match[4] ?? \"\";\n }\n\n return null;\n}\n\nfunction htmlAttributeHasToken(tag: string, name: string, token: string): boolean {\n return htmlTokenListContains(getHtmlAttribute(tag, name), token);\n}\n\nfunction getInlineCss(manifest: InlineCssManifest, href: string): string | null {\n if (Object.prototype.hasOwnProperty.call(manifest, href)) {\n return manifest[href] ?? \"\";\n }\n\n try {\n const pathname = new URL(href).pathname;\n if (Object.prototype.hasOwnProperty.call(manifest, pathname)) {\n return manifest[pathname] ?? \"\";\n }\n } catch {\n // Relative asset URLs are looked up by their emitted href.\n }\n\n return null;\n}\n\n// Module-level regex; consumers reset `lastIndex` before each scan. Same\n// shared-state constraint as the other `g`-flag regexes above.\nconst TRAILING_LINK_OPEN_RE = /<link/gi;\n\nfunction splitTrailingIncompleteLinkTag(html: string): { complete: string; trailing: string } {\n // Scan forward to find the last `<link` opening without allocating a\n // lowercased copy of `html` — this runs on every flush of the streaming\n // hot path, and `html` can be tens of KB.\n TRAILING_LINK_OPEN_RE.lastIndex = 0;\n let lastIndex = -1;\n let match: RegExpExecArray | null;\n while ((match = TRAILING_LINK_OPEN_RE.exec(html)) !== null) {\n lastIndex = match.index;\n }\n if (lastIndex === -1) return { complete: html, trailing: \"\" };\n const close = html.indexOf(\">\", lastIndex);\n if (close !== -1) return { complete: html, trailing: \"\" };\n return {\n complete: html.slice(0, lastIndex),\n trailing: html.slice(lastIndex),\n };\n}\n\nfunction findTrailingOpenHtmlRewriteExcludedRegionStart(html: string): number | null {\n let match: RegExpExecArray | null;\n\n HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex = 0;\n while ((match = HTML_REWRITE_EXCLUDED_REGION_START_RE.exec(html)) !== null) {\n const start = match.index;\n if (match[0] === \"<!--\") {\n const close = html.indexOf(\"-->\", HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex);\n if (close === -1) return start;\n HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex = close + 3;\n continue;\n }\n\n const tagName = match[1]?.toLowerCase();\n if (!tagName) continue;\n\n const closeTagRe = new RegExp(`</${tagName}\\\\s*>`, \"i\");\n const close = closeTagRe.exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));\n if (!close) return start;\n HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex += close.index + close[0].length;\n }\n\n return null;\n}\n\nfunction splitTrailingInlineCssRewriteBoundary(html: string): {\n complete: string;\n trailing: string;\n} {\n const linkSplit = splitTrailingIncompleteLinkTag(html);\n const incompleteLinkStart = linkSplit.trailing ? linkSplit.complete.length : null;\n const openRegionStart = findTrailingOpenHtmlRewriteExcludedRegionStart(html);\n const trailingStart =\n incompleteLinkStart === null\n ? openRegionStart\n : openRegionStart === null\n ? incompleteLinkStart\n : Math.min(incompleteLinkStart, openRegionStart);\n\n if (trailingStart === null) return { complete: html, trailing: \"\" };\n\n return {\n complete: html.slice(0, trailingStart),\n trailing: html.slice(trailingStart),\n };\n}\n\nfunction escapeStyleText(css: string): string {\n return css.replace(/<\\/style/gi, \"<\\\\/style\");\n}\n\nconst CSS_PREPEND_UNSAFE_PREAMBLE_RE =\n /^\\uFEFF?(?:\\s|\\/\\*[\\s\\S]*?\\*\\/)*@(charset|import|layer|namespace)\\b/i;\n\nfunction canPrependCss(css: string): boolean {\n return !CSS_PREPEND_UNSAFE_PREAMBLE_RE.test(css);\n}\n\nfunction replaceLinkTags(html: string, replaceLinkTag: (tag: string) => string): string {\n LINK_TAG_RE.lastIndex = 0;\n return html.replace(LINK_TAG_RE, replaceLinkTag);\n}\n\nfunction replaceLinkTagsOutsideRawText(\n html: string,\n replaceLinkTag: (tag: string) => string,\n): string {\n let rewritten = \"\";\n let cursor = 0;\n let match: RegExpExecArray | null;\n\n HTML_REWRITE_EXCLUDED_REGION_RE.lastIndex = 0;\n while ((match = HTML_REWRITE_EXCLUDED_REGION_RE.exec(html)) !== null) {\n rewritten += replaceLinkTags(html.slice(cursor, match.index), replaceLinkTag);\n rewritten += match[0];\n cursor = match.index + match[0].length;\n }\n\n const tail = html.slice(cursor);\n const openRegionStart = findTrailingOpenHtmlRewriteExcludedRegionStart(tail);\n if (openRegionStart === null) {\n return rewritten + replaceLinkTags(tail, replaceLinkTag);\n }\n\n return (\n rewritten +\n replaceLinkTags(tail.slice(0, openRegionStart), replaceLinkTag) +\n tail.slice(openRegionStart)\n );\n}\n\nfunction rewriteInlineCssStylesheetLinks(\n html: string,\n inlineCssManifest: InlineCssManifest | undefined,\n prependCss: string,\n ssrScriptNonce: string | undefined,\n): InlineCssRewriteResult {\n if (!inlineCssManifest || Object.keys(inlineCssManifest).length === 0) {\n return { html, consumedPrependCss: false };\n }\n let consumedPrependCss = false;\n\n const rewritten = replaceLinkTagsOutsideRawText(html, (tag) => {\n if (!htmlAttributeHasToken(tag, \"rel\", \"stylesheet\")) return tag;\n\n const href = getHtmlAttribute(tag, \"href\");\n const precedence =\n getHtmlAttribute(tag, \"data-precedence\") ?? getHtmlAttribute(tag, \"precedence\");\n if (!href || !precedence) return tag;\n\n const css = getInlineCss(inlineCssManifest, href);\n if (css === null) return tag;\n\n // Prefer the link's own nonce if Fizz emitted one; otherwise fall back to\n // the SSR-time script/style nonce so sites with CSP `style-src 'nonce-…'`\n // policies don't block the inlined `<style>` block. The `<link>` tag this\n // replaces wasn't subject to inline-style CSP, but the new `<style>` is.\n const linkNonce = getHtmlAttribute(tag, \"nonce\");\n const effectiveNonce = linkNonce ?? ssrScriptNonce;\n const nonceAttr = effectiveNonce ? ` nonce=\"${escapeHtmlAttr(effectiveNonce)}\"` : \"\";\n const shouldPrependCss = !consumedPrependCss && prependCss.length > 0 && canPrependCss(css);\n const cssPrefix = shouldPrependCss ? `${prependCss}\\n` : \"\";\n consumedPrependCss ||= cssPrefix.length > 0;\n\n return (\n `<style data-vinext-inline-css${nonceAttr}` +\n ` data-precedence=\"${escapeHtmlAttr(precedence)}\"` +\n ` data-href=\"${escapeHtmlAttr(href)}\">` +\n `${escapeStyleText(cssPrefix + css)}</style>`\n );\n });\n\n return { html: rewritten, consumedPrependCss };\n}\n\n/**\n * Match the `<head ...>` opening tag in a chunk. Matches both bare `<head>`\n * and `<head class=\"foo\">` shapes. Used to splice HTML immediately after the\n * opening tag so injected content runs before any React-emitted resource\n * hints (stylesheets, modulepreloads) that React Float hoists into `<head>`.\n */\nconst HEAD_OPEN_RE = /<head\\b[^>]*>/;\n\n/**\n * Create the tick-buffered HTML transform that injects RSC scripts between\n * React Fizz flush cycles without corrupting split HTML chunks.\n *\n * Two insertion points are supported in tandem:\n *\n * - `injectHTML` is emitted immediately before `</head>`. This is where the\n * bulk of vinext's head additions live (RSC navigation runtime metadata,\n * bootstrap modulepreload, server-inserted HTML, font preloads, etc.).\n * - `injectAfterHeadOpenHTML` is emitted immediately after the `<head ...>`\n * opening tag so the content runs before any React-emitted resource\n * hints. This is where inline `<Script strategy=\"beforeInteractive\">`\n * captures land so the no-flash dark-mode pattern works.\n *\n * Fallback behaviour differs by insertion point:\n *\n * - `injectHTML` is emitted at end-of-stream by the `flush` handler when no\n * chunk ever contained `</head>` — callers still see the payload on\n * highly fragmented streams (just at the end of the body rather than in\n * the head).\n * - `injectAfterHeadOpenHTML` is silently dropped when `<head ...>` is not\n * found in a discoverable chunk. Emitting it at end-of-stream would put\n * it after the document body, defeating the point — the splice has to\n * happen before resource hints to be useful, so the safer behaviour is\n * to no-op and let the user-rendered Script (in its source-order\n * position) ship as-is.\n */\nexport function createTickBufferedTransform(\n rscEmbed: RscEmbedTransform,\n injectHTML: HtmlInsertion = \"\",\n injectAfterHeadOpenHTML: HtmlInsertion = \"\",\n inlineCssManifest?: InlineCssManifest,\n inlineCssPrependCss = \"\",\n inlineCssPrependFallbackHTML = \"\",\n inlineCssScriptNonce?: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n const insertsPerFlush = typeof injectHTML === \"function\";\n let injected = false;\n let preHeadInjected = false;\n let buffered: string[] = [];\n let pendingHtml = \"\";\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n // Computed once at transform creation: every flush is a hot path, so we\n // avoid re-running Object.keys() on the manifest per chunk. Gates both the\n // split-link boundary buffering and the inline-css link rewrite below.\n const hasInlineCssManifest =\n inlineCssManifest !== undefined && Object.keys(inlineCssManifest).length > 0;\n const readInsertion = (): string =>\n typeof injectHTML === \"function\" ? injectHTML() : injectHTML;\n const readPreHeadInsertion = (): string =>\n typeof injectAfterHeadOpenHTML === \"function\"\n ? injectAfterHeadOpenHTML()\n : injectAfterHeadOpenHTML;\n const readInlineCssPrependFallback = (): string => {\n if (!inlineCssPrependCss || !inlineCssPrependFallbackHTML) return \"\";\n inlineCssPrependCss = \"\";\n return inlineCssPrependFallbackHTML;\n };\n const emitInsertion = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n const insertion = readInlineCssPrependFallback() + readInsertion();\n if (insertion) {\n controller.enqueue(encoder.encode(insertion));\n }\n };\n\n /**\n * Splice the pre-head insertion (typically captured beforeInteractive inline\n * scripts) immediately after the `<head ...>` opening tag. Returns the\n * rewritten chunk and a flag indicating whether the splice happened, so the\n * caller can mark `preHeadInjected` and stop scanning further chunks.\n *\n * NOTE: This is called only when `<head ...>` lies fully inside the current\n * tick-buffered batch. We deliberately avoid retaining arbitrary output until\n * a future chunk completes `<head ...>`, which would delay TTFB and complicate\n * the existing `</head>` injection path. In practice React Fizz emits the\n * opening shell as a single batch.\n */\n const spliceAfterHeadOpen = (chunk: string): { chunk: string; spliced: boolean } => {\n if (preHeadInjected) return { chunk, spliced: false };\n const insertion = readPreHeadInsertion();\n if (!insertion) return { chunk, spliced: false };\n const match = HEAD_OPEN_RE.exec(chunk);\n if (!match) return { chunk, spliced: false };\n const insertAt = match.index + match[0].length;\n return {\n chunk: chunk.slice(0, insertAt) + insertion + chunk.slice(insertAt),\n spliced: true,\n };\n };\n\n const flushBuffered = (\n controller: TransformStreamDefaultController<Uint8Array>,\n final = false,\n ): void => {\n if (buffered.length === 0 && !pendingHtml) return;\n const rawHtml = pendingHtml + buffered.join(\"\");\n buffered = [];\n pendingHtml = \"\";\n\n const split =\n final || !hasInlineCssManifest\n ? { complete: rawHtml, trailing: \"\" }\n : splitTrailingInlineCssRewriteBoundary(rawHtml);\n if (split.trailing) {\n pendingHtml = split.trailing;\n }\n if (!split.complete) return;\n\n if (injected && insertsPerFlush) {\n // Emit newly collected server-inserted HTML before the next Fizz HTML\n // batch so CSS-in-JS styles precede the elements they style.\n emitInsertion(controller);\n }\n\n const preparedHtml = fixPreloadAs(split.complete);\n const inlineCssResult = hasInlineCssManifest\n ? rewriteInlineCssStylesheetLinks(\n preparedHtml,\n inlineCssManifest,\n inlineCssPrependCss,\n inlineCssScriptNonce,\n )\n : { html: preparedHtml, consumedPrependCss: false };\n if (inlineCssResult.consumedPrependCss) {\n inlineCssPrependCss = \"\";\n }\n\n let working = inlineCssResult.html;\n if (!preHeadInjected) {\n const result = spliceAfterHeadOpen(working);\n if (result.spliced) {\n working = result.chunk;\n preHeadInjected = true;\n }\n }\n if (!injected) {\n const headEnd = working.indexOf(\"</head>\");\n if (headEnd !== -1) {\n const before = working.slice(0, headEnd);\n const after = working.slice(headEnd);\n controller.enqueue(\n encoder.encode(before + readInlineCssPrependFallback() + readInsertion() + after),\n );\n injected = true;\n return;\n }\n }\n controller.enqueue(encoder.encode(working));\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n buffered.push(decoder.decode(chunk, { stream: true }));\n\n if (timeoutId !== null) return;\n\n timeoutId = setTimeout(() => {\n try {\n flushBuffered(controller);\n\n const rscScripts = rscEmbed.flush();\n if (rscScripts) {\n controller.enqueue(encoder.encode(rscScripts));\n }\n } catch {\n // Stream was cancelled between when the timeout was registered and\n // when it fired (e.g. client disconnected, health-check cancelled\n // the response body). Ignore — the stream is already closed.\n }\n\n timeoutId = null;\n }, 0);\n },\n\n async flush(controller) {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n const remainder = decoder.decode();\n if (remainder) {\n buffered.push(remainder);\n }\n\n flushBuffered(controller, true);\n\n if (!injected) {\n emitInsertion(controller);\n injected = true;\n } else if (insertsPerFlush) {\n emitInsertion(controller);\n }\n\n const finalScripts = await rscEmbed.finalize();\n if (finalScripts) {\n controller.enqueue(encoder.encode(finalScripts));\n }\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/server/client-trace-metadata.d.ts
|
|
2
|
+
type ClientTraceDataEntry = {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Filter an entry list against the configured `clientTraceMetadata` allow-list.
|
|
8
|
+
* Returns `undefined` when the allow-list is unset so callers can skip
|
|
9
|
+
* rendering altogether.
|
|
10
|
+
*/
|
|
11
|
+
declare function filterClientTraceMetadata(entries: readonly ClientTraceDataEntry[], allowList: readonly string[] | undefined): ClientTraceDataEntry[] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Render the filtered entries as a sequence of self-closing `<meta>` tags.
|
|
14
|
+
* Names and values are HTML-attribute escaped. Returns an empty string when
|
|
15
|
+
* `entries` is empty or undefined so callers can append unconditionally.
|
|
16
|
+
*/
|
|
17
|
+
declare function renderClientTraceMetadataTags(entries: readonly ClientTraceDataEntry[] | undefined): string;
|
|
18
|
+
/**
|
|
19
|
+
* Convenience helper: read OTel propagation data, filter against the
|
|
20
|
+
* configured allow-list, and render the resulting `<meta>` tags. Returns an
|
|
21
|
+
* empty string when the allow-list is unset, OTel is not installed, or no
|
|
22
|
+
* matching keys were emitted by the propagator.
|
|
23
|
+
*
|
|
24
|
+
* Safe to call unconditionally on every SSR render — when nothing is
|
|
25
|
+
* configured/active this is a few `try/catch`-bounded operations and returns
|
|
26
|
+
* `""`.
|
|
27
|
+
*/
|
|
28
|
+
declare function getClientTraceMetadataHTML(allowList: readonly string[] | undefined): string;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ClientTraceDataEntry, filterClientTraceMetadata, getClientTraceMetadataHTML, renderClientTraceMetadataTags };
|
|
31
|
+
//# sourceMappingURL=client-trace-metadata.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { escapeHtmlAttr } from "./html.js";
|
|
2
|
+
//#region src/server/client-trace-metadata.ts
|
|
3
|
+
/**
|
|
4
|
+
* Client trace metadata renderer.
|
|
5
|
+
*
|
|
6
|
+
* When `experimental.clientTraceMetadata` is configured in `next.config`,
|
|
7
|
+
* vinext emits `<meta name="..." content="...">` tags in the SSR HTML head
|
|
8
|
+
* for each configured key. The values are sourced from the active
|
|
9
|
+
* OpenTelemetry context via the registered propagator.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors Next.js' implementation:
|
|
12
|
+
* - packages/next/src/server/lib/trace/utils.ts (getTracedMetadata)
|
|
13
|
+
* - packages/next/src/server/app-render/make-get-server-inserted-html.tsx (traceMetaTags)
|
|
14
|
+
*
|
|
15
|
+
* OpenTelemetry is an optional peer — we resolve `@opentelemetry/api` at
|
|
16
|
+
* runtime and silently no-op when it is not installed. This matches user
|
|
17
|
+
* expectations: apps that don't configure OTel get no meta tags, and apps
|
|
18
|
+
* that do get the filtered subset they asked for in `clientTraceMetadata`.
|
|
19
|
+
*/
|
|
20
|
+
const carrierSetter = { set(carrier, key, value) {
|
|
21
|
+
if (typeof key !== "string" || typeof value !== "string") return;
|
|
22
|
+
carrier.push({
|
|
23
|
+
key,
|
|
24
|
+
value
|
|
25
|
+
});
|
|
26
|
+
} };
|
|
27
|
+
function getOpenTelemetryTraceData() {
|
|
28
|
+
let api;
|
|
29
|
+
try {
|
|
30
|
+
const req = globalThis.require;
|
|
31
|
+
if (typeof req === "function") api = req("@opentelemetry/api");
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
if (!api) return [];
|
|
36
|
+
try {
|
|
37
|
+
const activeContext = api.context.active();
|
|
38
|
+
const entries = [];
|
|
39
|
+
api.propagation.inject(activeContext, entries, carrierSetter);
|
|
40
|
+
return entries;
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Filter an entry list against the configured `clientTraceMetadata` allow-list.
|
|
47
|
+
* Returns `undefined` when the allow-list is unset so callers can skip
|
|
48
|
+
* rendering altogether.
|
|
49
|
+
*/
|
|
50
|
+
function filterClientTraceMetadata(entries, allowList) {
|
|
51
|
+
if (!allowList || allowList.length === 0) return void 0;
|
|
52
|
+
const allowSet = new Set(allowList);
|
|
53
|
+
return entries.filter(({ key }) => allowSet.has(key));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Render the filtered entries as a sequence of self-closing `<meta>` tags.
|
|
57
|
+
* Names and values are HTML-attribute escaped. Returns an empty string when
|
|
58
|
+
* `entries` is empty or undefined so callers can append unconditionally.
|
|
59
|
+
*/
|
|
60
|
+
function renderClientTraceMetadataTags(entries) {
|
|
61
|
+
if (!entries || entries.length === 0) return "";
|
|
62
|
+
let html = "";
|
|
63
|
+
for (const { key, value } of entries) html += `<meta name="${escapeHtmlAttr(key)}" content="${escapeHtmlAttr(value)}"/>`;
|
|
64
|
+
return html;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convenience helper: read OTel propagation data, filter against the
|
|
68
|
+
* configured allow-list, and render the resulting `<meta>` tags. Returns an
|
|
69
|
+
* empty string when the allow-list is unset, OTel is not installed, or no
|
|
70
|
+
* matching keys were emitted by the propagator.
|
|
71
|
+
*
|
|
72
|
+
* Safe to call unconditionally on every SSR render — when nothing is
|
|
73
|
+
* configured/active this is a few `try/catch`-bounded operations and returns
|
|
74
|
+
* `""`.
|
|
75
|
+
*/
|
|
76
|
+
function getClientTraceMetadataHTML(allowList) {
|
|
77
|
+
if (!allowList || allowList.length === 0) return "";
|
|
78
|
+
return renderClientTraceMetadataTags(filterClientTraceMetadata(getOpenTelemetryTraceData(), allowList));
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { filterClientTraceMetadata, getClientTraceMetadataHTML, renderClientTraceMetadataTags };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=client-trace-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-trace-metadata.js","names":[],"sources":["../../src/server/client-trace-metadata.ts"],"sourcesContent":["/**\n * Client trace metadata renderer.\n *\n * When `experimental.clientTraceMetadata` is configured in `next.config`,\n * vinext emits `<meta name=\"...\" content=\"...\">` tags in the SSR HTML head\n * for each configured key. The values are sourced from the active\n * OpenTelemetry context via the registered propagator.\n *\n * This mirrors Next.js' implementation:\n * - packages/next/src/server/lib/trace/utils.ts (getTracedMetadata)\n * - packages/next/src/server/app-render/make-get-server-inserted-html.tsx (traceMetaTags)\n *\n * OpenTelemetry is an optional peer — we resolve `@opentelemetry/api` at\n * runtime and silently no-op when it is not installed. This matches user\n * expectations: apps that don't configure OTel get no meta tags, and apps\n * that do get the filtered subset they asked for in `clientTraceMetadata`.\n */\nimport { escapeHtmlAttr } from \"./html.js\";\n\nexport type ClientTraceDataEntry = {\n key: string;\n value: string;\n};\n\ntype TextMapSetter = {\n set(carrier: ClientTraceDataEntry[], key: string, value: string): void;\n};\n\nconst carrierSetter: TextMapSetter = {\n set(carrier, key, value) {\n if (typeof key !== \"string\" || typeof value !== \"string\") return;\n carrier.push({ key, value });\n },\n};\n\n/**\n * Pull entries off the active OpenTelemetry context via the registered\n * propagator. Returns an empty array when `@opentelemetry/api` is not\n * installed or when no propagator has been registered.\n *\n * The implementation mirrors Next.js's `NextTracerImpl.getTracePropagationData`:\n * we call `propagation.inject(activeContext, entries, setter)` and let the\n * setter push entries into our carrier array.\n */\ntype OpenTelemetryApi = {\n context: { active(): unknown };\n propagation: {\n inject(context: unknown, carrier: ClientTraceDataEntry[], setter: TextMapSetter): void;\n };\n};\n\nfunction getOpenTelemetryTraceData(): ClientTraceDataEntry[] {\n let api: OpenTelemetryApi | undefined;\n try {\n // Use require() at runtime so `@opentelemetry/api` is an optional peer.\n // Bundlers (Vite/esbuild) leave the `require` reference alone, so apps\n // that don't install the package never hit this branch.\n const req = (globalThis as { require?: (id: string) => unknown }).require;\n if (typeof req === \"function\") {\n api = req(\"@opentelemetry/api\") as OpenTelemetryApi;\n }\n } catch {\n return [];\n }\n\n if (!api) return [];\n\n try {\n const activeContext = api.context.active();\n const entries: ClientTraceDataEntry[] = [];\n api.propagation.inject(activeContext, entries, carrierSetter);\n return entries;\n } catch {\n return [];\n }\n}\n\n/**\n * Filter an entry list against the configured `clientTraceMetadata` allow-list.\n * Returns `undefined` when the allow-list is unset so callers can skip\n * rendering altogether.\n */\nexport function filterClientTraceMetadata(\n entries: readonly ClientTraceDataEntry[],\n allowList: readonly string[] | undefined,\n): ClientTraceDataEntry[] | undefined {\n if (!allowList || allowList.length === 0) return undefined;\n const allowSet = new Set(allowList);\n return entries.filter(({ key }) => allowSet.has(key));\n}\n\n/**\n * Render the filtered entries as a sequence of self-closing `<meta>` tags.\n * Names and values are HTML-attribute escaped. Returns an empty string when\n * `entries` is empty or undefined so callers can append unconditionally.\n */\nexport function renderClientTraceMetadataTags(\n entries: readonly ClientTraceDataEntry[] | undefined,\n): string {\n if (!entries || entries.length === 0) return \"\";\n let html = \"\";\n for (const { key, value } of entries) {\n html += `<meta name=\"${escapeHtmlAttr(key)}\" content=\"${escapeHtmlAttr(value)}\"/>`;\n }\n return html;\n}\n\n/**\n * Convenience helper: read OTel propagation data, filter against the\n * configured allow-list, and render the resulting `<meta>` tags. Returns an\n * empty string when the allow-list is unset, OTel is not installed, or no\n * matching keys were emitted by the propagator.\n *\n * Safe to call unconditionally on every SSR render — when nothing is\n * configured/active this is a few `try/catch`-bounded operations and returns\n * `\"\"`.\n */\nexport function getClientTraceMetadataHTML(allowList: readonly string[] | undefined): string {\n if (!allowList || allowList.length === 0) return \"\";\n const entries = getOpenTelemetryTraceData();\n const filtered = filterClientTraceMetadata(entries, allowList);\n return renderClientTraceMetadataTags(filtered);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,MAAM,gBAA+B,EACnC,IAAI,SAAS,KAAK,OAAO;CACvB,IAAI,OAAO,QAAQ,YAAY,OAAO,UAAU,UAAU;CAC1D,QAAQ,KAAK;EAAE;EAAK;EAAO,CAAC;GAE/B;AAkBD,SAAS,4BAAoD;CAC3D,IAAI;CACJ,IAAI;EAIF,MAAM,MAAO,WAAqD;EAClE,IAAI,OAAO,QAAQ,YACjB,MAAM,IAAI,qBAAqB;SAE3B;EACN,OAAO,EAAE;;CAGX,IAAI,CAAC,KAAK,OAAO,EAAE;CAEnB,IAAI;EACF,MAAM,gBAAgB,IAAI,QAAQ,QAAQ;EAC1C,MAAM,UAAkC,EAAE;EAC1C,IAAI,YAAY,OAAO,eAAe,SAAS,cAAc;EAC7D,OAAO;SACD;EACN,OAAO,EAAE;;;;;;;;AASb,SAAgB,0BACd,SACA,WACoC;CACpC,IAAI,CAAC,aAAa,UAAU,WAAW,GAAG,OAAO,KAAA;CACjD,MAAM,WAAW,IAAI,IAAI,UAAU;CACnC,OAAO,QAAQ,QAAQ,EAAE,UAAU,SAAS,IAAI,IAAI,CAAC;;;;;;;AAQvD,SAAgB,8BACd,SACQ;CACR,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAC7C,IAAI,OAAO;CACX,KAAK,MAAM,EAAE,KAAK,WAAW,SAC3B,QAAQ,eAAe,eAAe,IAAI,CAAC,aAAa,eAAe,MAAM,CAAC;CAEhF,OAAO;;;;;;;;;;;;AAaT,SAAgB,2BAA2B,WAAkD;CAC3F,IAAI,CAAC,aAAa,UAAU,WAAW,GAAG,OAAO;CAGjD,OAAO,8BADU,0BADD,2BACkC,EAAE,UACP,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/server/cookie-utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parse the cookie name out of a serialised Set-Cookie line.
|
|
4
|
+
*
|
|
5
|
+
* Bounded by the first `;` so the attribute portion (e.g. `Path=/`) is never
|
|
6
|
+
* mistaken for part of the name when the value happens to contain another
|
|
7
|
+
* `=`. Returns null when the line is not parseable (defensive — callers keep
|
|
8
|
+
* unparseable entries verbatim so they don't drop user-supplied cookies).
|
|
9
|
+
*/
|
|
10
|
+
declare function getSetCookieName(cookie: string): string | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { getSetCookieName };
|
|
13
|
+
//# sourceMappingURL=cookie-utils.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/server/cookie-utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parse the cookie name out of a serialised Set-Cookie line.
|
|
4
|
+
*
|
|
5
|
+
* Bounded by the first `;` so the attribute portion (e.g. `Path=/`) is never
|
|
6
|
+
* mistaken for part of the name when the value happens to contain another
|
|
7
|
+
* `=`. Returns null when the line is not parseable (defensive — callers keep
|
|
8
|
+
* unparseable entries verbatim so they don't drop user-supplied cookies).
|
|
9
|
+
*/
|
|
10
|
+
function getSetCookieName(cookie) {
|
|
11
|
+
const equalsIndex = cookie.indexOf("=");
|
|
12
|
+
if (equalsIndex <= 0) return null;
|
|
13
|
+
const semicolonIndex = cookie.indexOf(";");
|
|
14
|
+
const end = semicolonIndex === -1 ? equalsIndex : Math.min(equalsIndex, semicolonIndex);
|
|
15
|
+
return cookie.slice(0, end);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { getSetCookieName };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=cookie-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-utils.js","names":[],"sources":["../../src/server/cookie-utils.ts"],"sourcesContent":["/**\n * Parse the cookie name out of a serialised Set-Cookie line.\n *\n * Bounded by the first `;` so the attribute portion (e.g. `Path=/`) is never\n * mistaken for part of the name when the value happens to contain another\n * `=`. Returns null when the line is not parseable (defensive — callers keep\n * unparseable entries verbatim so they don't drop user-supplied cookies).\n */\nexport function getSetCookieName(cookie: string): string | null {\n const equalsIndex = cookie.indexOf(\"=\");\n if (equalsIndex <= 0) {\n return null;\n }\n const semicolonIndex = cookie.indexOf(\";\");\n const end = semicolonIndex === -1 ? equalsIndex : Math.min(equalsIndex, semicolonIndex);\n return cookie.slice(0, end);\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,iBAAiB,QAA+B;CAC9D,MAAM,cAAc,OAAO,QAAQ,IAAI;CACvC,IAAI,eAAe,GACjB,OAAO;CAET,MAAM,iBAAiB,OAAO,QAAQ,IAAI;CAC1C,MAAM,MAAM,mBAAmB,KAAK,cAAc,KAAK,IAAI,aAAa,eAAe;CACvF,OAAO,OAAO,MAAM,GAAG,IAAI"}
|
|
@@ -36,7 +36,14 @@ declare function parseCookieLocale(req: IncomingMessage, i18nConfig: NextI18nCon
|
|
|
36
36
|
* 4. Render the component to HTML
|
|
37
37
|
* 5. Wrap in _document shell and send response
|
|
38
38
|
*/
|
|
39
|
-
declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean
|
|
39
|
+
declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean,
|
|
40
|
+
/**
|
|
41
|
+
* Allow-list of OpenTelemetry propagation keys to emit as `<meta>` tags
|
|
42
|
+
* in the SSR head. Sourced from `experimental.clientTraceMetadata` in
|
|
43
|
+
* `next.config`. When undefined or empty, no meta tags are emitted.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
clientTraceMetadata?: readonly string[]): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
|
|
40
47
|
|
|
41
48
|
statusCode?: number,
|
|
42
49
|
/**
|