vinext 0.0.52 → 0.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build/clean-output.d.ts +14 -0
- package/dist/build/clean-output.js +36 -0
- package/dist/build/clean-output.js.map +1 -0
- package/dist/build/prerender.d.ts +6 -2
- package/dist/build/prerender.js +49 -11
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/run-prerender.js +10 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/static-export.d.ts +5 -0
- package/dist/build/static-export.js +8 -3
- package/dist/build/static-export.js.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/instrumentation-client-inject.d.ts +34 -0
- package/dist/client/instrumentation-client-inject.js +57 -0
- package/dist/client/instrumentation-client-inject.js.map +1 -0
- package/dist/client/navigation-runtime.d.ts +14 -1
- package/dist/client/navigation-runtime.js +16 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/vinext-next-data.js.map +1 -1
- package/dist/client/window-next.d.ts +10 -2
- package/dist/client/window-next.js.map +1 -1
- package/dist/cloudflare/tpr.js +1 -1
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.js +2 -1
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +12 -3
- package/dist/config/next-config.js +44 -14
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +29 -7
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +4 -2
- package/dist/entries/app-rsc-entry.js +23 -3
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-client-entry.js +22 -1
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +211 -31
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/plugins/fonts.js +25 -2
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/routing/route-trie.js +13 -18
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/utils.d.ts +11 -1
- package/dist/routing/utils.js +15 -1
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.js +18 -9
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-action-result.d.ts +16 -1
- package/dist/server/app-browser-action-result.js +15 -1
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +22 -12
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-elements.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +12 -3
- package/dist/server/app-fallback-renderer.js +10 -5
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-history-state.js +6 -2
- package/dist/server/app-history-state.js.map +1 -1
- package/dist/server/app-interception-context-header.d.ts +33 -0
- package/dist/server/app-interception-context-header.js +44 -0
- package/dist/server/app-interception-context-header.js.map +1 -0
- package/dist/server/app-mounted-slots-header.d.ts +19 -0
- package/dist/server/app-mounted-slots-header.js +40 -1
- package/dist/server/app-mounted-slots-header.js.map +1 -1
- package/dist/server/app-optimistic-routing.js +26 -18
- package/dist/server/app-optimistic-routing.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +1 -0
- package/dist/server/app-page-boundary-render.js +2 -0
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +1 -0
- package/dist/server/app-page-boundary.js +2 -0
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +2 -0
- package/dist/server/app-page-cache.js +7 -1
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +3 -0
- package/dist/server/app-page-dispatch.js +11 -4
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +5 -2
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +1 -0
- package/dist/server/app-page-execution.js +2 -0
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.d.ts +1 -0
- package/dist/server/app-page-head.js +8 -0
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-render-observation.js +1 -1
- package/dist/server/app-page-render.d.ts +1 -0
- package/dist/server/app-page-render.js +5 -2
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-response.d.ts +11 -1
- package/dist/server/app-page-response.js +14 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +1 -0
- package/dist/server/app-page-route-wiring.js +19 -6
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +1 -0
- package/dist/server/app-page-stream.js +2 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +3 -0
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-rsc-handler.d.ts +2 -0
- package/dist/server/app-rsc-handler.js +18 -9
- package/dist/server/app-rsc-handler.js.map +1 -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-segment-config.d.ts +4 -1
- package/dist/server/app-segment-config.js +6 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +1 -0
- package/dist/server/app-server-action-execution.js +4 -0
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.js +39 -3
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +24 -1
- package/dist/server/app-ssr-stream.js +78 -5
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/app-static-generation.d.ts +1 -0
- package/dist/server/app-static-generation.js +2 -1
- package/dist/server/app-static-generation.js.map +1 -1
- package/dist/server/default-not-found-module.d.ts +20 -0
- package/dist/server/default-not-found-module.js +20 -0
- package/dist/server/default-not-found-module.js.map +1 -0
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +23 -7
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/headers.js.map +1 -1
- package/dist/server/image-optimization.d.ts +13 -4
- package/dist/server/image-optimization.js +15 -4
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/middleware.js +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +18 -0
- package/dist/server/pages-api-route.js +3 -1
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-body-parser-config.d.ts +60 -0
- package/dist/server/pages-body-parser-config.js +79 -0
- package/dist/server/pages-body-parser-config.js.map +1 -0
- package/dist/server/pages-data-route.js +1 -0
- package/dist/server/pages-data-route.js.map +1 -1
- package/dist/server/pages-default-404.d.ts +31 -0
- package/dist/server/pages-default-404.js +40 -0
- package/dist/server/pages-default-404.js.map +1 -0
- package/dist/server/pages-node-compat.d.ts +10 -0
- package/dist/server/pages-node-compat.js +12 -1
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +40 -0
- package/dist/server/pages-page-data.js +16 -14
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +2 -0
- package/dist/server/pages-page-response.js +11 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prerender-route-params.d.ts +14 -0
- package/dist/server/prerender-route-params.js +94 -0
- package/dist/server/prerender-route-params.js.map +1 -0
- package/dist/server/prod-server.d.ts +3 -23
- package/dist/server/prod-server.js +40 -57
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/proxy-trust.d.ts +41 -0
- package/dist/server/proxy-trust.js +70 -0
- package/dist/server/proxy-trust.js.map +1 -0
- package/dist/server/request-pipeline.d.ts +3 -3
- package/dist/server/request-pipeline.js +5 -4
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/seed-cache.js +12 -6
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/static-file-cache.js +1 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/server/streaming-metadata.d.ts +5 -0
- package/dist/server/streaming-metadata.js +10 -0
- package/dist/server/streaming-metadata.js.map +1 -0
- package/dist/shims/app-router-scroll-state.d.ts +12 -0
- package/dist/shims/app-router-scroll-state.js +38 -0
- package/dist/shims/app-router-scroll-state.js.map +1 -0
- package/dist/shims/app-router-scroll.d.ts +14 -0
- package/dist/shims/app-router-scroll.js +100 -0
- package/dist/shims/app-router-scroll.js.map +1 -0
- package/dist/shims/before-interactive-context.d.ts +30 -0
- package/dist/shims/before-interactive-context.js +10 -0
- package/dist/shims/before-interactive-context.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts +1 -1
- package/dist/shims/cache-runtime.js +14 -1
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/default-not-found.d.ts +12 -0
- package/dist/shims/default-not-found.js +61 -0
- package/dist/shims/default-not-found.js.map +1 -0
- package/dist/shims/font-local.d.ts +5 -0
- package/dist/shims/font-local.js +6 -2
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/head.js +4 -4
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +6 -2
- package/dist/shims/headers.js +64 -21
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts +1 -1
- package/dist/shims/image.js +4 -4
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/pages-data-target.d.ts +58 -0
- package/dist/shims/internal/pages-data-target.js +91 -0
- package/dist/shims/internal/pages-data-target.js.map +1 -0
- package/dist/shims/internal/pages-data-url.d.ts +42 -0
- package/dist/shims/internal/pages-data-url.js +73 -0
- package/dist/shims/internal/pages-data-url.js.map +1 -0
- package/dist/shims/link.js +59 -9
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +2 -1
- package/dist/shims/metadata.js +61 -2
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.js +32 -9
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +376 -77
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.js +86 -12
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.js +1 -0
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/url-utils.d.ts +2 -1
- package/dist/shims/url-utils.js +15 -4
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/html-limited-bots.d.ts +5 -0
- package/dist/utils/html-limited-bots.js +15 -0
- package/dist/utils/html-limited-bots.js.map +1 -0
- package/dist/utils/query.d.ts +6 -0
- package/dist/utils/query.js +10 -1
- package/dist/utils/query.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"font-local.js","names":[],"sources":["../../src/shims/font-local.ts"],"sourcesContent":["/**\n * next/font/local shim\n *\n * Provides a runtime-compatible shim for Next.js local fonts.\n * Generates @font-face CSS declarations and returns an object\n * with className, style, and variable properties.\n *\n * Supports both client-side injection and SSR collection,\n * matching the patterns used by the Google font shim.\n *\n * Usage:\n * import localFont from 'next/font/local';\n * const myFont = localFont({ src: './my-font.woff2' });\n * // myFont.className -> unique CSS class\n * // myFont.style -> { fontFamily: \"'__local_font_0', sans-serif\" }\n * // myFont.variable -> generated class name when requested\n */\nimport {\n escapeCSSString,\n formatFontClassRule,\n resolveSingleFaceStyle,\n sanitizeCSSVarName,\n sanitizeFallback,\n sanitizeFontDescriptorValue,\n type FontStyle,\n} from \"./font-utils.js\";\n\n/**\n * Validate a CSS property name for use in declarations.\n *\n * Only allows standard CSS property names (lowercase letters and hyphens)\n * and custom properties (--prefixed). Rejects anything that could inject\n * CSS rules via crafted property names.\n */\nfunction sanitizeCSSProperty(prop: string): string | undefined {\n if (/^(--)?[a-zA-Z][a-zA-Z0-9-]*$/.test(prop)) return prop;\n return undefined;\n}\n\nlet classCounter = 0;\nconst injectedFonts = new Set<string>();\n\ntype LocalFontSrc = {\n path: string;\n weight?: string;\n style?: string;\n};\n\ntype LocalFontOptions = {\n src: string | LocalFontSrc | LocalFontSrc[];\n display?: string;\n weight?: string;\n style?: string;\n fallback?: string[];\n preload?: boolean;\n variable?: string;\n adjustFontFallback?: boolean | string;\n declarations?: Array<{ prop: string; value: string }>;\n};\n\ntype FontResult = {\n className: string;\n style: FontStyle;\n variable?: string;\n};\n\nfunction generateFontFaceCSS(\n family: string,\n options: LocalFontOptions,\n sources: LocalFontSrc[],\n): string {\n const display = options.display ?? \"swap\";\n const rules: string[] = [];\n\n for (const src of sources) {\n const weight = sanitizeFontDescriptorValue(src.weight ?? options.weight ?? \"400\") ?? \"400\";\n const style = sanitizeFontDescriptorValue(src.style ?? options.style ?? \"normal\") ?? \"normal\";\n const format = src.path.endsWith(\".woff2\")\n ? \"woff2\"\n : src.path.endsWith(\".woff\")\n ? \"woff\"\n : src.path.endsWith(\".ttf\")\n ? \"truetype\"\n : src.path.endsWith(\".otf\")\n ? \"opentype\"\n : \"woff2\";\n\n rules.push(`@font-face {\n font-family: '${escapeCSSString(family)}';\n src: url('${escapeCSSString(src.path)}') format('${format}');\n font-weight: ${weight};\n font-style: ${style};\n font-display: ${display};\n}`);\n }\n\n // Add extra declarations if provided — sanitize prop/value to prevent injection\n if (options.declarations) {\n for (const decl of options.declarations) {\n const safeProp = sanitizeCSSProperty(decl.prop);\n const safeValue = sanitizeFontDescriptorValue(decl.value);\n if (safeProp && safeValue) {\n rules.push(\n `@font-face { font-family: '${escapeCSSString(family)}'; ${safeProp}: ${safeValue}; }`,\n );\n }\n }\n }\n\n return rules.join(\"\\n\");\n}\n\n// SSR: collect font styles for injection in <head>\nconst ssrFontStyles: string[] = [];\n\n// SSR: collect font file URLs for <link rel=\"preload\"> injection\nconst ssrFontPreloads: Array<{ href: string; type: string }> = [];\nconst ssrFontPreloadHrefs = new Set<string>();\n\n/**\n * Get collected SSR font styles (used by the renderer).\n * Note: We don't clear the arrays because fonts are loaded at module import\n * time and need to persist across all requests in the Workers environment.\n */\nexport function getSSRFontStyles(): string[] {\n return [...ssrFontStyles];\n}\n\n/**\n * Get collected SSR font preload data (used by the renderer).\n * Returns an array of { href, type } objects for emitting\n * <link rel=\"preload\" as=\"font\" ...> tags.\n */\nexport function getSSRFontPreloads(): Array<{ href: string; type: string }> {\n return [...ssrFontPreloads];\n}\n\nfunction injectFontFaceCSS(css: string, id: string): void {\n if (injectedFonts.has(id)) return;\n injectedFonts.add(id);\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font\", id);\n document.head.appendChild(style);\n}\n\n/** Track which className CSS rules have been injected. */\nconst injectedClassRules = new Set<string>();\n\n/**\n * Inject a CSS rule that maps a className to the exported font style.\n *\n * This is what makes `<div className={font.className}>` apply the font.\n *\n * In Next.js, the .className class sets font-family and any single\n * font-weight/font-style. CSS variables are handled separately by .variable.\n */\nfunction injectClassNameRule(className: string, fontStyle: FontStyle): void {\n if (injectedClassRules.has(className)) return;\n injectedClassRules.add(className);\n\n const css = formatFontClassRule(className, fontStyle);\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n // On client, inject a <style> tag\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font-class\", className);\n document.head.appendChild(style);\n}\n\n/** Track which variable class CSS rules have been injected. */\nconst injectedVariableRules = new Set<string>();\n\n/** Track which :root CSS variable rules have been injected. */\nconst injectedRootVariables = new Set<string>();\n\n/**\n * Inject a CSS rule that sets a CSS variable on an element.\n * This is what makes `<html className={font.variable}>` set the CSS variable\n * that can be referenced by other styles (e.g., Tailwind's font-sans).\n *\n * In Next.js, the .variable class ONLY sets the CSS variable — it does NOT\n * set font-family. This is critical because apps commonly apply multiple\n * .variable classes to <body> (e.g., geistSans.variable + geistMono.variable).\n * If we also set font-family here, the last class wins due to CSS cascade,\n * causing all text to use that font (e.g., everything becomes monospace).\n */\nfunction injectVariableClassRule(\n variableClassName: string,\n cssVarName: string,\n fontFamily: string,\n): void {\n if (injectedVariableRules.has(variableClassName)) return;\n injectedVariableRules.add(variableClassName);\n\n // Only set the CSS variable — do NOT set font-family.\n // This matches Next.js behavior where .variable classes only define CSS variables.\n let css = `.${variableClassName} { ${cssVarName}: ${fontFamily}; }\\n`;\n\n // Also inject at :root so CSS variable inheritance works throughout the page.\n // This ensures Tailwind utilities like `font-sans` that reference these\n // variables via var(--font-geist-sans) work correctly.\n if (!injectedRootVariables.has(cssVarName)) {\n injectedRootVariables.add(cssVarName);\n css += `:root { ${cssVarName}: ${fontFamily}; }\\n`;\n }\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n // On client, inject a <style> tag\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font-variable\", variableClassName);\n document.head.appendChild(style);\n}\n\n/**\n * Normalize the `src` option into a flat array of `{ path, weight?, style? }`.\n * Handles string, single object, and array forms.\n */\nfunction normalizeSources(options: LocalFontOptions): LocalFontSrc[] {\n if (Array.isArray(options.src)) return options.src;\n if (typeof options.src === \"string\") return [{ path: options.src }];\n return [options.src];\n}\n\n/**\n * Determine the MIME type for a font file based on its extension.\n * Uses endsWith() only — matching the approach in generateFontFaceCSS —\n * to avoid false positives from substring matches (e.g. \".woff\" matching \".woff2\").\n */\nfunction getFontMimeType(pathOrUrl: string): string {\n if (pathOrUrl.endsWith(\".woff2\")) return \"font/woff2\";\n if (pathOrUrl.endsWith(\".woff\")) return \"font/woff\";\n if (pathOrUrl.endsWith(\".ttf\")) return \"font/ttf\";\n if (pathOrUrl.endsWith(\".otf\")) return \"font/opentype\";\n return \"font/woff2\";\n}\n\n/**\n * Collect font source URLs for preload link generation.\n * Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.\n */\nfunction collectFontPreloads(sources: LocalFontSrc[]): void {\n if (typeof document !== \"undefined\") return; // client-side, skip\n\n for (const src of sources) {\n const href = src.path;\n // Only collect URLs that are absolute (start with /) — relative paths\n // would resolve incorrectly from different page URLs. The vinext:local-fonts\n // Vite transform should have already resolved them to absolute URLs.\n if (href && href.startsWith(\"/\") && !ssrFontPreloadHrefs.has(href)) {\n ssrFontPreloadHrefs.add(href);\n ssrFontPreloads.push({ href, type: getFontMimeType(href) });\n }\n }\n}\n\nexport default function localFont(options: LocalFontOptions): FontResult {\n const id = classCounter++;\n const sources = normalizeSources(options);\n const singleSource = sources.length === 1 ? sources[0] : undefined;\n const family = `__local_font_${id}`;\n const className = `__font_local_${id}`;\n const fallback = options.fallback ?? [\"sans-serif\"];\n // Sanitize each fallback name to prevent CSS injection via crafted values\n const fontFamily = `'${family}', ${fallback.map(sanitizeFallback).join(\", \")}`;\n // Validate CSS variable name — reject anything that could inject CSS\n const cssVarName = options.variable ? sanitizeCSSVarName(options.variable) : undefined;\n // In Next.js, `variable` returns a CLASS NAME that sets the CSS variable.\n // Users apply this class to set the CSS variable on that element.\n const variableClassName = `__variable_local_${id}`;\n const style = singleSource\n ? resolveSingleFaceStyle({\n fontFamily,\n weight: singleSource.weight ?? options.weight,\n style: singleSource.style ?? options.style,\n })\n : { fontFamily };\n\n // Collect font URLs for preload <link> tags (SSR only)\n collectFontPreloads(sources);\n\n // Inject @font-face declarations\n const css = generateFontFaceCSS(family, options, sources);\n injectFontFaceCSS(css, family);\n\n // Inject the className -> font-family CSS rule\n injectClassNameRule(className, style);\n\n // Inject a CSS rule for the variable class name if variable is specified.\n // This is what makes `<html className={font.variable}>` set the CSS variable.\n if (cssVarName) {\n injectVariableClassRule(variableClassName, cssVarName, fontFamily);\n }\n\n return {\n className,\n style,\n ...(cssVarName ? { variable: variableClassName } : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"font-local.js","names":[],"sources":["../../src/shims/font-local.ts"],"sourcesContent":["/**\n * next/font/local shim\n *\n * Provides a runtime-compatible shim for Next.js local fonts.\n * Generates @font-face CSS declarations and returns an object\n * with className, style, and variable properties.\n *\n * Supports both client-side injection and SSR collection,\n * matching the patterns used by the Google font shim.\n *\n * Usage:\n * import localFont from 'next/font/local';\n * const myFont = localFont({ src: './my-font.woff2' });\n * // myFont.className -> unique CSS class\n * // myFont.style -> { fontFamily: \"'__local_font_0', sans-serif\" }\n * // myFont.variable -> generated class name when requested\n */\nimport {\n escapeCSSString,\n formatFontClassRule,\n resolveSingleFaceStyle,\n sanitizeCSSVarName,\n sanitizeFallback,\n sanitizeFontDescriptorValue,\n type FontStyle,\n} from \"./font-utils.js\";\n\n/**\n * Validate a CSS property name for use in declarations.\n *\n * Only allows standard CSS property names (lowercase letters and hyphens)\n * and custom properties (--prefixed). Rejects anything that could inject\n * CSS rules via crafted property names.\n */\nfunction sanitizeCSSProperty(prop: string): string | undefined {\n if (/^(--)?[a-zA-Z][a-zA-Z0-9-]*$/.test(prop)) return prop;\n return undefined;\n}\n\nfunction sanitizeInternalFontFamily(name: unknown): string | undefined {\n if (typeof name !== \"string\" || name.length === 0) return undefined;\n if (/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(name)) return name;\n return undefined;\n}\n\nlet classCounter = 0;\nconst injectedFonts = new Set<string>();\n\ntype LocalFontSrc = {\n path: string;\n weight?: string;\n style?: string;\n};\n\ntype LocalFontOptions = {\n src: string | LocalFontSrc | LocalFontSrc[];\n display?: string;\n weight?: string;\n style?: string;\n fallback?: string[];\n preload?: boolean;\n variable?: string;\n adjustFontFallback?: boolean | string;\n declarations?: Array<{ prop: string; value: string }>;\n _vinext?: {\n font?: {\n family?: unknown;\n };\n };\n};\n\ntype FontResult = {\n className: string;\n style: FontStyle;\n variable?: string;\n};\n\nfunction generateFontFaceCSS(\n family: string,\n options: LocalFontOptions,\n sources: LocalFontSrc[],\n): string {\n const display = options.display ?? \"swap\";\n const rules: string[] = [];\n\n for (const src of sources) {\n const weight = sanitizeFontDescriptorValue(src.weight ?? options.weight ?? \"400\") ?? \"400\";\n const style = sanitizeFontDescriptorValue(src.style ?? options.style ?? \"normal\") ?? \"normal\";\n const format = src.path.endsWith(\".woff2\")\n ? \"woff2\"\n : src.path.endsWith(\".woff\")\n ? \"woff\"\n : src.path.endsWith(\".ttf\")\n ? \"truetype\"\n : src.path.endsWith(\".otf\")\n ? \"opentype\"\n : \"woff2\";\n\n rules.push(`@font-face {\n font-family: '${escapeCSSString(family)}';\n src: url('${escapeCSSString(src.path)}') format('${format}');\n font-weight: ${weight};\n font-style: ${style};\n font-display: ${display};\n}`);\n }\n\n // Add extra declarations if provided — sanitize prop/value to prevent injection\n if (options.declarations) {\n for (const decl of options.declarations) {\n const safeProp = sanitizeCSSProperty(decl.prop);\n const safeValue = sanitizeFontDescriptorValue(decl.value);\n if (safeProp && safeValue) {\n rules.push(\n `@font-face { font-family: '${escapeCSSString(family)}'; ${safeProp}: ${safeValue}; }`,\n );\n }\n }\n }\n\n return rules.join(\"\\n\");\n}\n\n// SSR: collect font styles for injection in <head>\nconst ssrFontStyles: string[] = [];\n\n// SSR: collect font file URLs for <link rel=\"preload\"> injection\nconst ssrFontPreloads: Array<{ href: string; type: string }> = [];\nconst ssrFontPreloadHrefs = new Set<string>();\n\n/**\n * Get collected SSR font styles (used by the renderer).\n * Note: We don't clear the arrays because fonts are loaded at module import\n * time and need to persist across all requests in the Workers environment.\n */\nexport function getSSRFontStyles(): string[] {\n return [...ssrFontStyles];\n}\n\n/**\n * Get collected SSR font preload data (used by the renderer).\n * Returns an array of { href, type } objects for emitting\n * <link rel=\"preload\" as=\"font\" ...> tags.\n */\nexport function getSSRFontPreloads(): Array<{ href: string; type: string }> {\n return [...ssrFontPreloads];\n}\n\nfunction injectFontFaceCSS(css: string, id: string): void {\n if (injectedFonts.has(id)) return;\n injectedFonts.add(id);\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font\", id);\n document.head.appendChild(style);\n}\n\n/** Track which className CSS rules have been injected. */\nconst injectedClassRules = new Set<string>();\n\n/**\n * Inject a CSS rule that maps a className to the exported font style.\n *\n * This is what makes `<div className={font.className}>` apply the font.\n *\n * In Next.js, the .className class sets font-family and any single\n * font-weight/font-style. CSS variables are handled separately by .variable.\n */\nfunction injectClassNameRule(className: string, fontStyle: FontStyle): void {\n if (injectedClassRules.has(className)) return;\n injectedClassRules.add(className);\n\n const css = formatFontClassRule(className, fontStyle);\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n // On client, inject a <style> tag\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font-class\", className);\n document.head.appendChild(style);\n}\n\n/** Track which variable class CSS rules have been injected. */\nconst injectedVariableRules = new Set<string>();\n\n/** Track which :root CSS variable rules have been injected. */\nconst injectedRootVariables = new Set<string>();\n\n/**\n * Inject a CSS rule that sets a CSS variable on an element.\n * This is what makes `<html className={font.variable}>` set the CSS variable\n * that can be referenced by other styles (e.g., Tailwind's font-sans).\n *\n * In Next.js, the .variable class ONLY sets the CSS variable — it does NOT\n * set font-family. This is critical because apps commonly apply multiple\n * .variable classes to <body> (e.g., geistSans.variable + geistMono.variable).\n * If we also set font-family here, the last class wins due to CSS cascade,\n * causing all text to use that font (e.g., everything becomes monospace).\n */\nfunction injectVariableClassRule(\n variableClassName: string,\n cssVarName: string,\n fontFamily: string,\n): void {\n if (injectedVariableRules.has(variableClassName)) return;\n injectedVariableRules.add(variableClassName);\n\n // Only set the CSS variable — do NOT set font-family.\n // This matches Next.js behavior where .variable classes only define CSS variables.\n let css = `.${variableClassName} { ${cssVarName}: ${fontFamily}; }\\n`;\n\n // Also inject at :root so CSS variable inheritance works throughout the page.\n // This ensures Tailwind utilities like `font-sans` that reference these\n // variables via var(--font-geist-sans) work correctly.\n if (!injectedRootVariables.has(cssVarName)) {\n injectedRootVariables.add(cssVarName);\n css += `:root { ${cssVarName}: ${fontFamily}; }\\n`;\n }\n\n // On server, store the CSS for SSR injection\n if (typeof document === \"undefined\") {\n ssrFontStyles.push(css);\n return;\n }\n\n // On client, inject a <style> tag\n const style = document.createElement(\"style\");\n style.textContent = css;\n style.setAttribute(\"data-vinext-font-variable\", variableClassName);\n document.head.appendChild(style);\n}\n\n/**\n * Normalize the `src` option into a flat array of `{ path, weight?, style? }`.\n * Handles string, single object, and array forms.\n */\nfunction normalizeSources(options: LocalFontOptions): LocalFontSrc[] {\n if (Array.isArray(options.src)) return options.src;\n if (typeof options.src === \"string\") return [{ path: options.src }];\n return [options.src];\n}\n\n/**\n * Determine the MIME type for a font file based on its extension.\n * Uses endsWith() only — matching the approach in generateFontFaceCSS —\n * to avoid false positives from substring matches (e.g. \".woff\" matching \".woff2\").\n */\nfunction getFontMimeType(pathOrUrl: string): string {\n if (pathOrUrl.endsWith(\".woff2\")) return \"font/woff2\";\n if (pathOrUrl.endsWith(\".woff\")) return \"font/woff\";\n if (pathOrUrl.endsWith(\".ttf\")) return \"font/ttf\";\n if (pathOrUrl.endsWith(\".otf\")) return \"font/opentype\";\n return \"font/woff2\";\n}\n\n/**\n * Collect font source URLs for preload link generation.\n * Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.\n */\nfunction collectFontPreloads(sources: LocalFontSrc[]): void {\n if (typeof document !== \"undefined\") return; // client-side, skip\n\n for (const src of sources) {\n const href = src.path;\n // Only collect URLs that are absolute (start with /) — relative paths\n // would resolve incorrectly from different page URLs. The vinext:local-fonts\n // Vite transform should have already resolved them to absolute URLs.\n if (href && href.startsWith(\"/\") && !ssrFontPreloadHrefs.has(href)) {\n ssrFontPreloadHrefs.add(href);\n ssrFontPreloads.push({ href, type: getFontMimeType(href) });\n }\n }\n}\n\nexport default function localFont(options: LocalFontOptions): FontResult {\n const id = classCounter++;\n const sources = normalizeSources(options);\n const singleSource = sources.length === 1 ? sources[0] : undefined;\n const family = sanitizeInternalFontFamily(options._vinext?.font?.family) ?? `__local_font_${id}`;\n const className = `__font_local_${id}`;\n const fallback = options.fallback ?? [\"sans-serif\"];\n // Sanitize each fallback name to prevent CSS injection via crafted values\n const fontFamily = `'${family}', ${fallback.map(sanitizeFallback).join(\", \")}`;\n // Validate CSS variable name — reject anything that could inject CSS\n const cssVarName = options.variable ? sanitizeCSSVarName(options.variable) : undefined;\n // In Next.js, `variable` returns a CLASS NAME that sets the CSS variable.\n // Users apply this class to set the CSS variable on that element.\n const variableClassName = `__variable_local_${id}`;\n const style = singleSource\n ? resolveSingleFaceStyle({\n fontFamily,\n weight: singleSource.weight ?? options.weight,\n style: singleSource.style ?? options.style,\n })\n : { fontFamily };\n\n // Collect font URLs for preload <link> tags (SSR only)\n collectFontPreloads(sources);\n\n // Inject @font-face declarations\n const css = generateFontFaceCSS(family, options, sources);\n // The exposed family can repeat across modules; the generated class stays\n // unique for each localFont call and is the correct injection identity.\n injectFontFaceCSS(css, className);\n\n // Inject the className -> font-family CSS rule\n injectClassNameRule(className, style);\n\n // Inject a CSS rule for the variable class name if variable is specified.\n // This is what makes `<html className={font.variable}>` set the CSS variable.\n if (cssVarName) {\n injectVariableClassRule(variableClassName, cssVarName, fontFamily);\n }\n\n return {\n className,\n style,\n ...(cssVarName ? { variable: variableClassName } : {}),\n };\n}\n"],"mappings":"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"}
|
package/dist/shims/head.js
CHANGED
|
@@ -159,9 +159,9 @@ function headChildToHTML(tag, props) {
|
|
|
159
159
|
attrs.push(key);
|
|
160
160
|
}
|
|
161
161
|
const attrStr = attrs.length ? " " + attrs.join(" ") : "";
|
|
162
|
-
if (SELF_CLOSING_HEAD_TAGS.has(tag)) return `<${tag}${attrStr} data-
|
|
162
|
+
if (SELF_CLOSING_HEAD_TAGS.has(tag)) return `<${tag}${attrStr} data-next-head="" />`;
|
|
163
163
|
if (RAW_CONTENT_TAGS.has(tag) && innerHTML) innerHTML = escapeInlineContent(innerHTML, tag);
|
|
164
|
-
return `<${tag}${attrStr} data-
|
|
164
|
+
return `<${tag}${attrStr} data-next-head="">${innerHTML}</${tag}>`;
|
|
165
165
|
}
|
|
166
166
|
function escapeHTML(s) {
|
|
167
167
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -204,13 +204,13 @@ function _applyHeadPropsToElement(domEl, props) {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
function syncClientHead() {
|
|
207
|
-
document.querySelectorAll("[data-
|
|
207
|
+
document.querySelectorAll("[data-next-head]").forEach((el) => el.remove());
|
|
208
208
|
for (const child of reduceHeadChildren([..._clientHeadChildren.values()])) {
|
|
209
209
|
if (typeof child.type !== "string") continue;
|
|
210
210
|
const domEl = document.createElement(child.type);
|
|
211
211
|
const props = child.props;
|
|
212
212
|
_applyHeadPropsToElement(domEl, props);
|
|
213
|
-
domEl.setAttribute("data-
|
|
213
|
+
domEl.setAttribute("data-next-head", "");
|
|
214
214
|
document.head.appendChild(domEl);
|
|
215
215
|
}
|
|
216
216
|
}
|
package/dist/shims/head.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.js","names":[],"sources":["../../src/shims/head.ts"],"sourcesContent":["/**\n * next/head shim\n *\n * In the Pages Router, <Head> manages document <head> elements.\n * - On the server: collects elements into a module-level array that the\n * dev-server reads after render and injects into the HTML <head>.\n * - On the client: reduces all mounted <Head> instances into one deduped\n * document.head projection and applies it with DOM manipulation.\n */\nimport React, { useEffect, useRef, Children, isValidElement } from \"react\";\n\ntype HeadProps = {\n children?: React.ReactNode;\n};\n\n// --- SSR head collection ---\n// State uses a registration pattern so this module can be bundled for the\n// browser. The ALS-backed implementation lives in head-state.ts (server-only).\n\nlet _ssrHeadChildren: React.ReactNode[] = [];\nconst _clientHeadChildren = new Map<symbol, React.ReactNode>();\n\nlet _getSSRHeadChildren = (): React.ReactNode[] => _ssrHeadChildren;\nlet _resetSSRHeadImpl = (): void => {\n _ssrHeadChildren = [];\n};\n\n/**\n * Register ALS-backed state accessors. Called by head-state.ts on import.\n * @internal\n */\nexport function _registerHeadStateAccessors(accessors: {\n getSSRHeadChildren: () => React.ReactNode[];\n resetSSRHead: () => void;\n}): void {\n _getSSRHeadChildren = accessors.getSSRHeadChildren;\n _resetSSRHeadImpl = accessors.resetSSRHead;\n}\n\n/** Reset the SSR head collector. Call before render. */\nexport function resetSSRHead(): void {\n _resetSSRHeadImpl();\n}\n\n/** Get collected head HTML. Call after render. */\nexport function getSSRHeadHTML(): string {\n return reduceHeadChildren(_getSSRHeadChildren())\n .map((child) => headChildToHTML(child.type as string, child.props as Record<string, unknown>))\n .filter(Boolean)\n .join(\"\\n \");\n}\n\n/**\n * Tags allowed inside <head>. Anything else is silently dropped.\n * This prevents injection of dangerous elements like <iframe>, <object>, etc.\n */\nconst ALLOWED_HEAD_TAGS = new Set([\"title\", \"meta\", \"link\", \"style\", \"script\", \"base\", \"noscript\"]);\nconst ALLOWED_HEAD_TAGS_LIST = Array.from(ALLOWED_HEAD_TAGS).join(\", \");\nconst META_TYPES = [\"name\", \"httpEquiv\", \"charSet\", \"itemProp\"] as const;\n\n/** Self-closing tags: no inner content, emit as <tag ... /> */\nconst SELF_CLOSING_HEAD_TAGS = new Set([\"meta\", \"link\", \"base\"]);\n\n/** Tags whose content is raw text — closing-tag sequences must be escaped during SSR. */\nconst RAW_CONTENT_TAGS = new Set([\"script\", \"style\"]);\n\ntype HeadDOMElement = Pick<HTMLElement, \"innerHTML\" | \"setAttribute\" | \"textContent\">;\n\nfunction warnDisallowedHeadTag(tag: string): void {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[vinext] <Head> ignoring disallowed tag <${tag}>. ` +\n `Only ${ALLOWED_HEAD_TAGS_LIST} are allowed.`,\n );\n }\n}\n\nfunction collectHeadElements(\n list: React.ReactElement[],\n child: React.ReactNode,\n): React.ReactElement[] {\n if (\n child == null ||\n typeof child === \"boolean\" ||\n typeof child === \"string\" ||\n typeof child === \"number\"\n ) {\n return list;\n }\n if (!isValidElement(child)) {\n return list;\n }\n if (child.type === React.Fragment) {\n return Children.toArray((child.props as { children?: React.ReactNode }).children).reduce(\n collectHeadElements,\n list,\n );\n }\n if (typeof child.type !== \"string\") {\n return list;\n }\n if (!ALLOWED_HEAD_TAGS.has(child.type)) {\n warnDisallowedHeadTag(child.type);\n return list;\n }\n return list.concat(child);\n}\n\nfunction normalizeHeadKey(key: React.Key | null): string | null {\n if (key == null || typeof key === \"number\") return null;\n const normalizedKey = String(key);\n const separatorIndex = normalizedKey.indexOf(\"$\");\n return separatorIndex > 0 ? normalizedKey.slice(separatorIndex + 1) : null;\n}\n\nfunction createUniqueHeadFilter(): (child: React.ReactElement) => boolean {\n const keys = new Set<string>();\n const tags = new Set<string>();\n const metaTypes = new Set<string>();\n const metaCategories = new Map<string, Set<string>>();\n\n return (child) => {\n let isUnique = true;\n const normalizedKey = normalizeHeadKey(child.key);\n const hasKey = normalizedKey !== null;\n if (normalizedKey) {\n if (keys.has(normalizedKey)) {\n isUnique = false;\n } else {\n keys.add(normalizedKey);\n }\n }\n\n switch (child.type) {\n case \"title\":\n case \"base\":\n if (tags.has(child.type)) {\n isUnique = false;\n } else {\n tags.add(child.type);\n }\n break;\n case \"meta\": {\n const props = child.props as Record<string, unknown>;\n for (const metaType of META_TYPES) {\n if (!Object.prototype.hasOwnProperty.call(props, metaType)) continue;\n if (metaType === \"charSet\") {\n if (metaTypes.has(metaType)) {\n isUnique = false;\n } else {\n metaTypes.add(metaType);\n }\n continue;\n }\n\n const category = props[metaType];\n if (typeof category !== \"string\") continue;\n\n let categories = metaCategories.get(metaType);\n if (!categories) {\n categories = new Set<string>();\n metaCategories.set(metaType, categories);\n }\n\n if ((metaType !== \"name\" || !hasKey) && categories.has(category)) {\n isUnique = false;\n } else {\n categories.add(category);\n }\n }\n break;\n }\n default:\n break;\n }\n\n return isUnique;\n };\n}\n\nexport function reduceHeadChildren(headChildren: React.ReactNode[]): React.ReactElement[] {\n return headChildren\n .reduce<React.ReactNode[]>(\n (flattenedChildren, child) => flattenedChildren.concat(Children.toArray(child)),\n [],\n )\n .reduce(collectHeadElements, [])\n .reverse()\n .filter(createUniqueHeadFilter())\n .reverse();\n}\n\n/**\n * Validate an HTML attribute name. Rejects names that could break out of\n * the attribute context during SSR serialization, or that represent inline\n * event handlers (on*). Only allows alphanumeric characters, hyphens, and\n * common data-attribute patterns.\n */\nconst SAFE_ATTR_NAME_RE = /^[a-zA-Z][a-zA-Z0-9\\-:.]*$/;\n\nexport function isSafeAttrName(name: string): boolean {\n if (!SAFE_ATTR_NAME_RE.test(name)) return false;\n // Block inline event handlers (onclick, onerror, etc.)\n if (name.length > 2 && name[0] === \"o\" && name[1] === \"n\" && name[2] >= \"A\" && name[2] <= \"z\")\n return false;\n return true;\n}\n\n/**\n * Convert props + tag to an HTML string for SSR head injection.\n * Callers must only pass tags that have already been validated against\n * ALLOWED_HEAD_TAGS (e.g. via reduceHeadChildren / collectHeadElements).\n */\nfunction headChildToHTML(tag: string, props: Record<string, unknown>): string {\n const attrs: string[] = [];\n let innerHTML = \"\";\n\n // dangerouslySetInnerHTML takes precedence over children, regardless of\n // prop iteration order. Check it first to match Next.js semantics.\n const rawHtml = getDangerouslySetInnerHTML(props.dangerouslySetInnerHTML);\n if (rawHtml != null) {\n // Intentionally raw — developer explicitly opted in.\n // SECURITY NOTE: This injects raw HTML. Developers must never pass\n // unsanitized user input here — it is a stored XSS vector.\n innerHTML = rawHtml;\n } else if (typeof props.children === \"string\") {\n innerHTML = escapeHTML(props.children);\n } else if (Array.isArray(props.children)) {\n innerHTML = escapeHTML(props.children.join(\"\"));\n }\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n } else if (key === \"className\") {\n attrs.push(`class=\"${escapeAttr(String(value))}\"`);\n } else if (typeof value === \"string\") {\n if (!isSafeAttrName(key)) continue;\n attrs.push(`${key}=\"${escapeAttr(value)}\"`);\n } else if (typeof value === \"boolean\" && value) {\n if (!isSafeAttrName(key)) continue;\n attrs.push(key);\n }\n }\n\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\";\n\n if (SELF_CLOSING_HEAD_TAGS.has(tag)) {\n return `<${tag}${attrStr} data-vinext-head=\"true\" />`;\n }\n\n // For raw-content tags (script, style), escape closing-tag sequences so the\n // HTML parser doesn't prematurely terminate the element.\n if (RAW_CONTENT_TAGS.has(tag) && innerHTML) {\n innerHTML = escapeInlineContent(innerHTML, tag);\n }\n\n return `<${tag}${attrStr} data-vinext-head=\"true\">${innerHTML}</${tag}>`;\n}\n\nfunction escapeHTML(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nexport function escapeAttr(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\n/**\n * Escape content that will be placed inside a raw <script> or <style> tag\n * during SSR. The HTML parser treats `</script>` (or `</style>`) as the end\n * of the block regardless of JavaScript string context, so any occurrence\n * of `</` followed by the tag name must be escaped.\n *\n * We replace `</script` and `</style` (case-insensitive) with `<\\/script`\n * and `<\\/style` respectively. The `<\\/` form is harmless in JS/CSS string\n * context but prevents the HTML parser from seeing a closing tag.\n */\nexport function escapeInlineContent(content: string, tag: string): string {\n // Build a pattern like `<\\/script` or `<\\/style`, case-insensitive.\n // `tag` is always a literal developer-controlled value (\"script\" or \"style\")\n // guarded by the RAW_CONTENT_TAGS.has(tag) check at all call sites — never user input.\n const pattern = new RegExp(`<\\\\/(${tag})`, \"gi\");\n return content.replace(pattern, \"<\\\\/$1\");\n}\n\nfunction getDangerouslySetInnerHTML(value: unknown): string | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n\n const html = Reflect.get(value, \"__html\");\n return typeof html === \"string\" ? html : undefined;\n}\n\nexport function _applyHeadPropsToElement(\n domEl: HeadDOMElement,\n props: Record<string, unknown>,\n): void {\n const rawHtml = getDangerouslySetInnerHTML(props.dangerouslySetInnerHTML);\n\n if (rawHtml != null) {\n domEl.innerHTML = rawHtml;\n } else if (typeof props.children === \"string\") {\n domEl.textContent = props.children;\n } else if (Array.isArray(props.children)) {\n domEl.textContent = props.children.join(\"\");\n }\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n } else if (key === \"className\") {\n domEl.setAttribute(\"class\", String(value));\n } else if (typeof value === \"boolean\" && value) {\n if (!isSafeAttrName(key)) continue;\n domEl.setAttribute(key, \"\");\n } else if (typeof value === \"string\") {\n if (!isSafeAttrName(key)) continue;\n domEl.setAttribute(key, value);\n }\n }\n}\n\nfunction syncClientHead(): void {\n document.querySelectorAll(\"[data-vinext-head]\").forEach((el) => el.remove());\n\n for (const child of reduceHeadChildren([..._clientHeadChildren.values()])) {\n if (typeof child.type !== \"string\") continue;\n\n const domEl = document.createElement(child.type);\n const props = child.props as Record<string, unknown>;\n _applyHeadPropsToElement(domEl, props);\n\n domEl.setAttribute(\"data-vinext-head\", \"true\");\n document.head.appendChild(domEl);\n }\n}\n\n// --- Component ---\n\nfunction Head({ children }: HeadProps): null {\n const headInstanceIdRef = useRef<symbol | null>(null);\n if (headInstanceIdRef.current === null) {\n headInstanceIdRef.current = Symbol(\"vinext-head\");\n }\n\n // SSR path: collect elements for later injection\n if (typeof window === \"undefined\") {\n _getSSRHeadChildren().push(children);\n return null;\n }\n\n // Client path: update the shared head projection after hydration.\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useEffect(() => {\n const instanceId = headInstanceIdRef.current!;\n _clientHeadChildren.set(instanceId, children);\n syncClientHead();\n\n return () => {\n _clientHeadChildren.delete(instanceId);\n syncClientHead();\n };\n }, [children]);\n\n return null;\n}\n\nexport default Head;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"head.js","names":[],"sources":["../../src/shims/head.ts"],"sourcesContent":["/**\n * next/head shim\n *\n * In the Pages Router, <Head> manages document <head> elements.\n * - On the server: collects elements into a module-level array that the\n * dev-server reads after render and injects into the HTML <head>.\n * - On the client: reduces all mounted <Head> instances into one deduped\n * document.head projection and applies it with DOM manipulation.\n */\nimport React, { useEffect, useRef, Children, isValidElement } from \"react\";\n\ntype HeadProps = {\n children?: React.ReactNode;\n};\n\n// --- SSR head collection ---\n// State uses a registration pattern so this module can be bundled for the\n// browser. The ALS-backed implementation lives in head-state.ts (server-only).\n\nlet _ssrHeadChildren: React.ReactNode[] = [];\nconst _clientHeadChildren = new Map<symbol, React.ReactNode>();\n\nlet _getSSRHeadChildren = (): React.ReactNode[] => _ssrHeadChildren;\nlet _resetSSRHeadImpl = (): void => {\n _ssrHeadChildren = [];\n};\n\n/**\n * Register ALS-backed state accessors. Called by head-state.ts on import.\n * @internal\n */\nexport function _registerHeadStateAccessors(accessors: {\n getSSRHeadChildren: () => React.ReactNode[];\n resetSSRHead: () => void;\n}): void {\n _getSSRHeadChildren = accessors.getSSRHeadChildren;\n _resetSSRHeadImpl = accessors.resetSSRHead;\n}\n\n/** Reset the SSR head collector. Call before render. */\nexport function resetSSRHead(): void {\n _resetSSRHeadImpl();\n}\n\n/** Get collected head HTML. Call after render. */\nexport function getSSRHeadHTML(): string {\n return reduceHeadChildren(_getSSRHeadChildren())\n .map((child) => headChildToHTML(child.type as string, child.props as Record<string, unknown>))\n .filter(Boolean)\n .join(\"\\n \");\n}\n\n/**\n * Tags allowed inside <head>. Anything else is silently dropped.\n * This prevents injection of dangerous elements like <iframe>, <object>, etc.\n */\nconst ALLOWED_HEAD_TAGS = new Set([\"title\", \"meta\", \"link\", \"style\", \"script\", \"base\", \"noscript\"]);\nconst ALLOWED_HEAD_TAGS_LIST = Array.from(ALLOWED_HEAD_TAGS).join(\", \");\nconst META_TYPES = [\"name\", \"httpEquiv\", \"charSet\", \"itemProp\"] as const;\n\n/** Self-closing tags: no inner content, emit as <tag ... /> */\nconst SELF_CLOSING_HEAD_TAGS = new Set([\"meta\", \"link\", \"base\"]);\n\n/** Tags whose content is raw text — closing-tag sequences must be escaped during SSR. */\nconst RAW_CONTENT_TAGS = new Set([\"script\", \"style\"]);\n\ntype HeadDOMElement = Pick<HTMLElement, \"innerHTML\" | \"setAttribute\" | \"textContent\">;\n\nfunction warnDisallowedHeadTag(tag: string): void {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[vinext] <Head> ignoring disallowed tag <${tag}>. ` +\n `Only ${ALLOWED_HEAD_TAGS_LIST} are allowed.`,\n );\n }\n}\n\nfunction collectHeadElements(\n list: React.ReactElement[],\n child: React.ReactNode,\n): React.ReactElement[] {\n if (\n child == null ||\n typeof child === \"boolean\" ||\n typeof child === \"string\" ||\n typeof child === \"number\"\n ) {\n return list;\n }\n if (!isValidElement(child)) {\n return list;\n }\n if (child.type === React.Fragment) {\n return Children.toArray((child.props as { children?: React.ReactNode }).children).reduce(\n collectHeadElements,\n list,\n );\n }\n if (typeof child.type !== \"string\") {\n return list;\n }\n if (!ALLOWED_HEAD_TAGS.has(child.type)) {\n warnDisallowedHeadTag(child.type);\n return list;\n }\n return list.concat(child);\n}\n\nfunction normalizeHeadKey(key: React.Key | null): string | null {\n if (key == null || typeof key === \"number\") return null;\n const normalizedKey = String(key);\n const separatorIndex = normalizedKey.indexOf(\"$\");\n return separatorIndex > 0 ? normalizedKey.slice(separatorIndex + 1) : null;\n}\n\nfunction createUniqueHeadFilter(): (child: React.ReactElement) => boolean {\n const keys = new Set<string>();\n const tags = new Set<string>();\n const metaTypes = new Set<string>();\n const metaCategories = new Map<string, Set<string>>();\n\n return (child) => {\n let isUnique = true;\n const normalizedKey = normalizeHeadKey(child.key);\n const hasKey = normalizedKey !== null;\n if (normalizedKey) {\n if (keys.has(normalizedKey)) {\n isUnique = false;\n } else {\n keys.add(normalizedKey);\n }\n }\n\n switch (child.type) {\n case \"title\":\n case \"base\":\n if (tags.has(child.type)) {\n isUnique = false;\n } else {\n tags.add(child.type);\n }\n break;\n case \"meta\": {\n const props = child.props as Record<string, unknown>;\n for (const metaType of META_TYPES) {\n if (!Object.prototype.hasOwnProperty.call(props, metaType)) continue;\n if (metaType === \"charSet\") {\n if (metaTypes.has(metaType)) {\n isUnique = false;\n } else {\n metaTypes.add(metaType);\n }\n continue;\n }\n\n const category = props[metaType];\n if (typeof category !== \"string\") continue;\n\n let categories = metaCategories.get(metaType);\n if (!categories) {\n categories = new Set<string>();\n metaCategories.set(metaType, categories);\n }\n\n if ((metaType !== \"name\" || !hasKey) && categories.has(category)) {\n isUnique = false;\n } else {\n categories.add(category);\n }\n }\n break;\n }\n default:\n break;\n }\n\n return isUnique;\n };\n}\n\nexport function reduceHeadChildren(headChildren: React.ReactNode[]): React.ReactElement[] {\n return headChildren\n .reduce<React.ReactNode[]>(\n (flattenedChildren, child) => flattenedChildren.concat(Children.toArray(child)),\n [],\n )\n .reduce(collectHeadElements, [])\n .reverse()\n .filter(createUniqueHeadFilter())\n .reverse();\n}\n\n/**\n * Validate an HTML attribute name. Rejects names that could break out of\n * the attribute context during SSR serialization, or that represent inline\n * event handlers (on*). Only allows alphanumeric characters, hyphens, and\n * common data-attribute patterns.\n */\nconst SAFE_ATTR_NAME_RE = /^[a-zA-Z][a-zA-Z0-9\\-:.]*$/;\n\nexport function isSafeAttrName(name: string): boolean {\n if (!SAFE_ATTR_NAME_RE.test(name)) return false;\n // Block inline event handlers (onclick, onerror, etc.)\n if (name.length > 2 && name[0] === \"o\" && name[1] === \"n\" && name[2] >= \"A\" && name[2] <= \"z\")\n return false;\n return true;\n}\n\n/**\n * Convert props + tag to an HTML string for SSR head injection.\n * Callers must only pass tags that have already been validated against\n * ALLOWED_HEAD_TAGS (e.g. via reduceHeadChildren / collectHeadElements).\n */\nfunction headChildToHTML(tag: string, props: Record<string, unknown>): string {\n const attrs: string[] = [];\n let innerHTML = \"\";\n\n // dangerouslySetInnerHTML takes precedence over children, regardless of\n // prop iteration order. Check it first to match Next.js semantics.\n const rawHtml = getDangerouslySetInnerHTML(props.dangerouslySetInnerHTML);\n if (rawHtml != null) {\n // Intentionally raw — developer explicitly opted in.\n // SECURITY NOTE: This injects raw HTML. Developers must never pass\n // unsanitized user input here — it is a stored XSS vector.\n innerHTML = rawHtml;\n } else if (typeof props.children === \"string\") {\n innerHTML = escapeHTML(props.children);\n } else if (Array.isArray(props.children)) {\n innerHTML = escapeHTML(props.children.join(\"\"));\n }\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n } else if (key === \"className\") {\n attrs.push(`class=\"${escapeAttr(String(value))}\"`);\n } else if (typeof value === \"string\") {\n if (!isSafeAttrName(key)) continue;\n attrs.push(`${key}=\"${escapeAttr(value)}\"`);\n } else if (typeof value === \"boolean\" && value) {\n if (!isSafeAttrName(key)) continue;\n attrs.push(key);\n }\n }\n\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\";\n\n if (SELF_CLOSING_HEAD_TAGS.has(tag)) {\n return `<${tag}${attrStr} data-next-head=\"\" />`;\n }\n\n // For raw-content tags (script, style), escape closing-tag sequences so the\n // HTML parser doesn't prematurely terminate the element.\n if (RAW_CONTENT_TAGS.has(tag) && innerHTML) {\n innerHTML = escapeInlineContent(innerHTML, tag);\n }\n\n return `<${tag}${attrStr} data-next-head=\"\">${innerHTML}</${tag}>`;\n}\n\nfunction escapeHTML(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nexport function escapeAttr(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\n/**\n * Escape content that will be placed inside a raw <script> or <style> tag\n * during SSR. The HTML parser treats `</script>` (or `</style>`) as the end\n * of the block regardless of JavaScript string context, so any occurrence\n * of `</` followed by the tag name must be escaped.\n *\n * We replace `</script` and `</style` (case-insensitive) with `<\\/script`\n * and `<\\/style` respectively. The `<\\/` form is harmless in JS/CSS string\n * context but prevents the HTML parser from seeing a closing tag.\n */\nexport function escapeInlineContent(content: string, tag: string): string {\n // Build a pattern like `<\\/script` or `<\\/style`, case-insensitive.\n // `tag` is always a literal developer-controlled value (\"script\" or \"style\")\n // guarded by the RAW_CONTENT_TAGS.has(tag) check at all call sites — never user input.\n const pattern = new RegExp(`<\\\\/(${tag})`, \"gi\");\n return content.replace(pattern, \"<\\\\/$1\");\n}\n\nfunction getDangerouslySetInnerHTML(value: unknown): string | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n\n const html = Reflect.get(value, \"__html\");\n return typeof html === \"string\" ? html : undefined;\n}\n\nexport function _applyHeadPropsToElement(\n domEl: HeadDOMElement,\n props: Record<string, unknown>,\n): void {\n const rawHtml = getDangerouslySetInnerHTML(props.dangerouslySetInnerHTML);\n\n if (rawHtml != null) {\n domEl.innerHTML = rawHtml;\n } else if (typeof props.children === \"string\") {\n domEl.textContent = props.children;\n } else if (Array.isArray(props.children)) {\n domEl.textContent = props.children.join(\"\");\n }\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n } else if (key === \"className\") {\n domEl.setAttribute(\"class\", String(value));\n } else if (typeof value === \"boolean\" && value) {\n if (!isSafeAttrName(key)) continue;\n domEl.setAttribute(key, \"\");\n } else if (typeof value === \"string\") {\n if (!isSafeAttrName(key)) continue;\n domEl.setAttribute(key, value);\n }\n }\n}\n\nfunction syncClientHead(): void {\n document.querySelectorAll(\"[data-next-head]\").forEach((el) => el.remove());\n\n for (const child of reduceHeadChildren([..._clientHeadChildren.values()])) {\n if (typeof child.type !== \"string\") continue;\n\n const domEl = document.createElement(child.type);\n const props = child.props as Record<string, unknown>;\n _applyHeadPropsToElement(domEl, props);\n\n domEl.setAttribute(\"data-next-head\", \"\");\n document.head.appendChild(domEl);\n }\n}\n\n// --- Component ---\n\nfunction Head({ children }: HeadProps): null {\n const headInstanceIdRef = useRef<symbol | null>(null);\n if (headInstanceIdRef.current === null) {\n headInstanceIdRef.current = Symbol(\"vinext-head\");\n }\n\n // SSR path: collect elements for later injection\n if (typeof window === \"undefined\") {\n _getSSRHeadChildren().push(children);\n return null;\n }\n\n // Client path: update the shared head projection after hydration.\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useEffect(() => {\n const instanceId = headInstanceIdRef.current!;\n _clientHeadChildren.set(instanceId, children);\n syncClientHead();\n\n return () => {\n _clientHeadChildren.delete(instanceId);\n syncClientHead();\n };\n }, [children]);\n\n return null;\n}\n\nexport default Head;\n"],"mappings":"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"}
|
package/dist/shims/headers.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ type HeadersContext = {
|
|
|
9
9
|
mutableCookies?: RequestCookies;
|
|
10
10
|
readonlyCookies?: RequestCookies;
|
|
11
11
|
readonlyHeaders?: Headers;
|
|
12
|
+
draftModeSecret?: string;
|
|
13
|
+
};
|
|
14
|
+
type HeadersContextFromRequestOptions = {
|
|
15
|
+
draftModeSecret?: string;
|
|
12
16
|
};
|
|
13
17
|
type HeadersAccessPhase = "render" | "action" | "route-handler";
|
|
14
18
|
type VinextHeadersShimState = {
|
|
@@ -127,7 +131,7 @@ declare function applyMiddlewareRequestHeaders(middlewareResponseHeaders: Header
|
|
|
127
131
|
* Cookie parsing is also deferred: the `cookie` header string is not split
|
|
128
132
|
* until the first call to `cookies()` or `draftMode()`.
|
|
129
133
|
*/
|
|
130
|
-
declare function headersContextFromRequest(request: Request): HeadersContext;
|
|
134
|
+
declare function headersContextFromRequest(request: Request, options?: HeadersContextFromRequestOptions): HeadersContext;
|
|
131
135
|
/**
|
|
132
136
|
* Read-only Headers instance from the incoming request.
|
|
133
137
|
* Returns a Promise in Next.js 15+ style (but resolves synchronously since
|
|
@@ -150,7 +154,7 @@ declare function getAndClearPendingCookies(): string[];
|
|
|
150
154
|
* Called by the framework after rendering to attach the header to the response.
|
|
151
155
|
*/
|
|
152
156
|
declare function getDraftModeCookieHeader(): string | null;
|
|
153
|
-
declare function isDraftModeRequest(request: Request): boolean;
|
|
157
|
+
declare function isDraftModeRequest(request: Request, draftModeSecret: string): boolean;
|
|
154
158
|
type DraftModeResult = {
|
|
155
159
|
readonly isEnabled: boolean;
|
|
156
160
|
enable(): void;
|
package/dist/shims/headers.js
CHANGED
|
@@ -5,6 +5,15 @@ import { buildRequestHeadersFromMiddlewareResponse } from "../server/middleware-
|
|
|
5
5
|
import { serializeSetCookie, validateCookieAttributeValue, validateCookieName } from "./internal/cookie-serialize.js";
|
|
6
6
|
import { parseCookieHeader } from "./internal/parse-cookie-header.js";
|
|
7
7
|
//#region src/shims/headers.ts
|
|
8
|
+
/**
|
|
9
|
+
* next/headers shim
|
|
10
|
+
*
|
|
11
|
+
* Provides cookies() and headers() functions for App Router Server Components.
|
|
12
|
+
* These read from a request context set by the RSC handler before rendering.
|
|
13
|
+
*
|
|
14
|
+
* In Next.js 15+, cookies() and headers() return Promises (async).
|
|
15
|
+
* We support both the sync (legacy) and async patterns.
|
|
16
|
+
*/
|
|
8
17
|
const _FALLBACK_KEY = Symbol.for("vinext.nextHeadersShim.fallback");
|
|
9
18
|
const _g = globalThis;
|
|
10
19
|
const _als = getOrCreateAls("vinext.nextHeadersShim.als");
|
|
@@ -119,12 +128,24 @@ function consumeRenderRequestApiUsage() {
|
|
|
119
128
|
const _USE_CACHE_ALS_KEY = Symbol.for("vinext.cacheRuntime.contextAls");
|
|
120
129
|
/** Symbol used by cache.ts to store the unstable_cache ALS on globalThis */
|
|
121
130
|
const _UNSTABLE_CACHE_ALS_KEY = Symbol.for("vinext.unstableCache.als");
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
function _getGlobalCacheScopeStorage(key) {
|
|
132
|
+
const value = Reflect.get(globalThis, key);
|
|
133
|
+
if (!value || typeof value !== "object") return null;
|
|
134
|
+
const getStore = Reflect.get(value, "getStore");
|
|
135
|
+
if (typeof getStore !== "function") return null;
|
|
136
|
+
return { getStore: () => getStore.call(value) };
|
|
137
|
+
}
|
|
138
|
+
function _getUseCacheGuardContext() {
|
|
139
|
+
const store = _getGlobalCacheScopeStorage(_USE_CACHE_ALS_KEY)?.getStore();
|
|
140
|
+
if (!store || typeof store !== "object") return null;
|
|
141
|
+
return store;
|
|
142
|
+
}
|
|
143
|
+
function _isInsidePublicUseCache() {
|
|
144
|
+
const ctx = _getUseCacheGuardContext();
|
|
145
|
+
return ctx !== null && ctx.variant !== "private";
|
|
125
146
|
}
|
|
126
147
|
function _isInsideUnstableCache() {
|
|
127
|
-
return
|
|
148
|
+
return _getGlobalCacheScopeStorage(_UNSTABLE_CACHE_ALS_KEY)?.getStore() === true;
|
|
128
149
|
}
|
|
129
150
|
/**
|
|
130
151
|
* Throw if the current execution is inside a "use cache" or unstable_cache()
|
|
@@ -134,7 +155,7 @@ function _isInsideUnstableCache() {
|
|
|
134
155
|
* @param apiName - The name of the API being called (e.g. "connection()")
|
|
135
156
|
*/
|
|
136
157
|
function throwIfInsideCacheScope(apiName) {
|
|
137
|
-
if (
|
|
158
|
+
if (_isInsidePublicUseCache()) {
|
|
138
159
|
const error = /* @__PURE__ */ new Error(`\`${apiName}\` cannot be called inside "use cache". If you need this data inside a cached function, call \`${apiName}\` outside and pass the required data as an argument.`);
|
|
139
160
|
try {
|
|
140
161
|
const ctx = getRequestContext();
|
|
@@ -402,7 +423,7 @@ function _getReadonlyHeaders(ctx) {
|
|
|
402
423
|
* Cookie parsing is also deferred: the `cookie` header string is not split
|
|
403
424
|
* until the first call to `cookies()` or `draftMode()`.
|
|
404
425
|
*/
|
|
405
|
-
function headersContextFromRequest(request) {
|
|
426
|
+
function headersContextFromRequest(request, options) {
|
|
406
427
|
let _mutable = null;
|
|
407
428
|
const headersProxy = new Proxy(request.headers, { get(target, prop) {
|
|
408
429
|
const src = _mutable ?? target;
|
|
@@ -423,7 +444,8 @@ function headersContextFromRequest(request) {
|
|
|
423
444
|
headers: headersProxy,
|
|
424
445
|
get cookies() {
|
|
425
446
|
return getCookies();
|
|
426
|
-
}
|
|
447
|
+
},
|
|
448
|
+
draftModeSecret: options?.draftModeSecret
|
|
427
449
|
};
|
|
428
450
|
}
|
|
429
451
|
/**
|
|
@@ -474,11 +496,6 @@ function getAndClearPendingCookies() {
|
|
|
474
496
|
}
|
|
475
497
|
const DRAFT_MODE_COOKIE = "__prerender_bypass";
|
|
476
498
|
const DRAFT_MODE_EXPIRED_DATE = (/* @__PURE__ */ new Date(0)).toUTCString();
|
|
477
|
-
function getDraftSecret() {
|
|
478
|
-
const secret = process.env.__VINEXT_DRAFT_SECRET;
|
|
479
|
-
if (!secret) throw new Error("[vinext] __VINEXT_DRAFT_SECRET is not defined. This should be set by the Vite plugin at build time.");
|
|
480
|
-
return secret;
|
|
481
|
-
}
|
|
482
499
|
/**
|
|
483
500
|
* Get any Set-Cookie header generated by draftMode().enable()/disable().
|
|
484
501
|
* Called by the framework after rendering to attach the header to the response.
|
|
@@ -489,15 +506,39 @@ function getDraftModeCookieHeader() {
|
|
|
489
506
|
state.draftModeCookieHeader = null;
|
|
490
507
|
return header;
|
|
491
508
|
}
|
|
492
|
-
function
|
|
509
|
+
function validateDraftModeSecret(secret) {
|
|
510
|
+
if (secret.length === 0) throw new Error("[vinext] draft mode secret must be a non-empty string.");
|
|
511
|
+
return secret;
|
|
512
|
+
}
|
|
513
|
+
function createDraftModeSecret() {
|
|
514
|
+
const crypto = globalThis.crypto;
|
|
515
|
+
if (crypto && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
516
|
+
throw new Error("[vinext] draft mode secret is not initialized. This should be initialized by the server entry before handling requests.");
|
|
517
|
+
}
|
|
518
|
+
function ensureContextDraftModeSecret(ctx) {
|
|
519
|
+
if (ctx.draftModeSecret !== void 0) return validateDraftModeSecret(ctx.draftModeSecret);
|
|
520
|
+
const secret = createDraftModeSecret();
|
|
521
|
+
ctx.draftModeSecret = secret;
|
|
522
|
+
return secret;
|
|
523
|
+
}
|
|
524
|
+
function isDraftModeRequest(request, draftModeSecret) {
|
|
493
525
|
const cookieHeader = request.headers.get("cookie");
|
|
494
526
|
if (!cookieHeader) return false;
|
|
495
|
-
return parseCookieHeader(cookieHeader).get(DRAFT_MODE_COOKIE) ===
|
|
527
|
+
return parseCookieHeader(cookieHeader).get(DRAFT_MODE_COOKIE) === validateDraftModeSecret(draftModeSecret);
|
|
496
528
|
}
|
|
497
529
|
function draftModeCookieAttributes() {
|
|
498
530
|
if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") return "Path=/; HttpOnly; SameSite=Lax";
|
|
499
531
|
return "Path=/; HttpOnly; SameSite=None; Secure";
|
|
500
532
|
}
|
|
533
|
+
function createDraftModeScopeError(expression) {
|
|
534
|
+
return /* @__PURE__ */ new Error(`${expression} can only be called from a Server Component, Route Handler, or Server Action.`);
|
|
535
|
+
}
|
|
536
|
+
function requireActiveDraftModeContext(state, expectedContext, expression) {
|
|
537
|
+
const currentContext = state.headersContext;
|
|
538
|
+
if (currentContext !== expectedContext) throw createDraftModeScopeError(expression);
|
|
539
|
+
if (currentContext.accessError) throw currentContext.accessError;
|
|
540
|
+
return currentContext;
|
|
541
|
+
}
|
|
501
542
|
/**
|
|
502
543
|
* Draft mode — check/toggle via a `__prerender_bypass` cookie.
|
|
503
544
|
*
|
|
@@ -509,22 +550,24 @@ async function draftMode() {
|
|
|
509
550
|
markRenderRequestApiUsage("draftMode");
|
|
510
551
|
throwIfInsideCacheScope("draftMode()");
|
|
511
552
|
const state = _getState();
|
|
512
|
-
|
|
513
|
-
|
|
553
|
+
const context = state.headersContext;
|
|
554
|
+
if (!context) throw createDraftModeScopeError("draftMode()");
|
|
555
|
+
if (context.accessError) throw context.accessError;
|
|
556
|
+
const secret = ensureContextDraftModeSecret(context);
|
|
514
557
|
return {
|
|
515
558
|
get isEnabled() {
|
|
516
|
-
return
|
|
559
|
+
return context.cookies.get(DRAFT_MODE_COOKIE) === secret;
|
|
517
560
|
},
|
|
518
561
|
enable() {
|
|
562
|
+
const activeContext = requireActiveDraftModeContext(state, context, "draftMode().enable()");
|
|
519
563
|
markDynamicUsage();
|
|
520
|
-
|
|
521
|
-
if (state.headersContext) state.headersContext.cookies.set(DRAFT_MODE_COOKIE, secret);
|
|
564
|
+
activeContext.cookies.set(DRAFT_MODE_COOKIE, secret);
|
|
522
565
|
state.draftModeCookieHeader = `${DRAFT_MODE_COOKIE}=${secret}; ${draftModeCookieAttributes()}`;
|
|
523
566
|
},
|
|
524
567
|
disable() {
|
|
568
|
+
const activeContext = requireActiveDraftModeContext(state, context, "draftMode().disable()");
|
|
525
569
|
markDynamicUsage();
|
|
526
|
-
|
|
527
|
-
if (state.headersContext) state.headersContext.cookies.delete(DRAFT_MODE_COOKIE);
|
|
570
|
+
activeContext.cookies.delete(DRAFT_MODE_COOKIE);
|
|
528
571
|
state.draftModeCookieHeader = `${DRAFT_MODE_COOKIE}=; ${draftModeCookieAttributes()}; Expires=${DRAFT_MODE_EXPIRED_DATE}`;
|
|
529
572
|
}
|
|
530
573
|
};
|