vinext 0.0.46 → 0.0.48
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 +8 -6
- package/dist/build/layout-classification.js +3 -1
- package/dist/build/layout-classification.js.map +1 -1
- package/dist/build/prerender.d.ts +2 -1
- package/dist/build/prerender.js +80 -24
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/report.d.ts +9 -5
- package/dist/build/report.js +17 -7
- package/dist/build/report.js.map +1 -1
- package/dist/build/route-classification-injector.d.ts +35 -0
- package/dist/build/route-classification-injector.js +61 -0
- package/dist/build/route-classification-injector.js.map +1 -0
- package/dist/build/route-classification-manifest.d.ts +1 -1
- package/dist/build/run-prerender.d.ts +5 -0
- package/dist/build/run-prerender.js +4 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/server-manifest.js +2 -7
- package/dist/build/server-manifest.js.map +1 -1
- package/dist/build/standalone.js +3 -5
- package/dist/build/standalone.js.map +1 -1
- package/dist/build/static-export.d.ts +1 -1
- package/dist/check.js +45 -29
- package/dist/check.js.map +1 -1
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.js +121 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli.js +11 -20
- package/dist/cli.js.map +1 -1
- package/dist/cloudflare/kv-cache-handler.js +29 -9
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/config/config-matchers.js +46 -37
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +4 -2
- package/dist/config/next-config.js +3 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts +18 -2
- package/dist/deploy.js +47 -4
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +4 -3
- package/dist/entries/app-rsc-entry.js +379 -858
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +1 -1
- package/dist/entries/app-rsc-manifest.js +6 -1
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.js +3 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +19 -61
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +12 -3
- package/dist/entries/runtime-entry-module.js +15 -4
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +40 -58
- package/dist/index.js.map +1 -1
- package/dist/plugins/fonts.js +54 -32
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/og-assets.js +15 -16
- package/dist/plugins/og-assets.js.map +1 -1
- package/dist/plugins/rsc-client-shim-excludes.d.ts +2 -1
- package/dist/plugins/rsc-client-shim-excludes.js +11 -1
- package/dist/plugins/rsc-client-shim-excludes.js.map +1 -1
- package/dist/routing/app-route-graph.d.ts +195 -0
- package/dist/routing/app-route-graph.js +1022 -0
- package/dist/routing/app-route-graph.js.map +1 -0
- package/dist/routing/app-router.d.ts +14 -88
- package/dist/routing/app-router.js +21 -712
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +3 -1
- package/dist/routing/file-matcher.js +6 -1
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.js +10 -19
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-matching.d.ts +28 -0
- package/dist/routing/route-matching.js +44 -0
- package/dist/routing/route-matching.js.map +1 -0
- package/dist/routing/route-pattern.js +4 -1
- package/dist/routing/route-pattern.js.map +1 -1
- package/dist/routing/route-trie.d.ts +8 -0
- package/dist/routing/route-trie.js +12 -1
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/route-validation.js +3 -4
- package/dist/routing/route-validation.js.map +1 -1
- package/dist/routing/utils.d.ts +8 -1
- package/dist/routing/utils.js +25 -2
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/app-browser-entry.js +145 -294
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-error.d.ts +3 -4
- package/dist/server/app-browser-error.js +8 -4
- package/dist/server/app-browser-error.js.map +1 -1
- package/dist/server/app-browser-navigation-controller.d.ts +75 -0
- package/dist/server/app-browser-navigation-controller.js +290 -0
- package/dist/server/app-browser-navigation-controller.js.map +1 -0
- package/dist/server/app-browser-state.d.ts +33 -15
- package/dist/server/app-browser-state.js +52 -59
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +68 -0
- package/dist/server/app-browser-visible-commit.js +182 -0
- package/dist/server/app-browser-visible-commit.js.map +1 -0
- package/dist/server/app-client-reference-preloader.d.ts +15 -0
- package/dist/server/app-client-reference-preloader.js +46 -0
- package/dist/server/app-client-reference-preloader.js.map +1 -0
- package/dist/server/app-elements-wire.d.ts +130 -0
- package/dist/server/app-elements-wire.js +205 -0
- package/dist/server/app-elements-wire.js.map +1 -0
- package/dist/server/app-elements.d.ts +2 -84
- package/dist/server/app-elements.js +4 -107
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +57 -0
- package/dist/server/app-fallback-renderer.js +79 -0
- package/dist/server/app-fallback-renderer.js.map +1 -0
- package/dist/server/app-hook-warning-suppression.d.ts +7 -0
- package/dist/server/app-hook-warning-suppression.js +12 -0
- package/dist/server/app-hook-warning-suppression.js.map +1 -0
- package/dist/server/app-middleware.d.ts +2 -1
- package/dist/server/app-middleware.js +34 -11
- package/dist/server/app-middleware.js.map +1 -1
- package/dist/server/app-mounted-slots-header.d.ts +17 -0
- package/dist/server/app-mounted-slots-header.js +21 -0
- package/dist/server/app-mounted-slots-header.js.map +1 -0
- package/dist/server/app-page-boundary-render.d.ts +3 -3
- package/dist/server/app-page-boundary-render.js +8 -5
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.js +2 -1
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +19 -4
- package/dist/server/app-page-cache.js +60 -22
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +9 -5
- package/dist/server/app-page-dispatch.js +41 -17
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +61 -0
- package/dist/server/app-page-element-builder.js +142 -0
- package/dist/server/app-page-element-builder.js.map +1 -0
- package/dist/server/app-page-execution.d.ts +23 -5
- package/dist/server/app-page-execution.js +39 -24
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.js +2 -1
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-method.js +2 -5
- package/dist/server/app-page-method.js.map +1 -1
- package/dist/server/app-page-params.d.ts +2 -1
- package/dist/server/app-page-params.js +3 -3
- package/dist/server/app-page-params.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +1 -1
- package/dist/server/app-page-probe.js +5 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render.d.ts +6 -2
- package/dist/server/app-page-render.js +118 -30
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +19 -5
- package/dist/server/app-page-request.js +49 -7
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.d.ts +1 -0
- package/dist/server/app-page-response.js +6 -9
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +20 -4
- package/dist/server/app-page-route-wiring.js +15 -12
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +9 -2
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-post-middleware-context.d.ts +16 -0
- package/dist/server/app-post-middleware-context.js +28 -0
- package/dist/server/app-post-middleware-context.js.map +1 -0
- package/dist/server/app-prerender-endpoints.js +3 -2
- package/dist/server/app-prerender-endpoints.js.map +1 -1
- package/dist/server/app-request-context.d.ts +22 -0
- package/dist/server/app-request-context.js +30 -0
- package/dist/server/app-request-context.js.map +1 -0
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +7 -2
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +8 -5
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +2 -1
- package/dist/server/app-route-handler-execution.js +2 -2
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-policy.js +13 -13
- package/dist/server/app-route-handler-policy.js.map +1 -1
- package/dist/server/app-route-handler-response.d.ts +4 -2
- package/dist/server/app-route-handler-response.js +9 -7
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-route-handler-runtime.d.ts +9 -1
- package/dist/server/app-route-handler-runtime.js +11 -1
- package/dist/server/app-route-handler-runtime.js.map +1 -1
- package/dist/server/app-router-entry.js +9 -4
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +34 -0
- package/dist/server/app-rsc-cache-busting.js +137 -0
- package/dist/server/app-rsc-cache-busting.js.map +1 -0
- package/dist/server/app-rsc-error-handler.d.ts +21 -0
- package/dist/server/app-rsc-error-handler.js +30 -0
- package/dist/server/app-rsc-error-handler.js.map +1 -0
- package/dist/server/app-rsc-handler.d.ts +117 -0
- package/dist/server/app-rsc-handler.js +271 -0
- package/dist/server/app-rsc-handler.js.map +1 -0
- package/dist/server/app-rsc-request-normalization.d.ts +42 -0
- package/dist/server/app-rsc-request-normalization.js +67 -0
- package/dist/server/app-rsc-request-normalization.js.map +1 -0
- package/dist/server/app-rsc-response-finalizer.d.ts +30 -0
- package/dist/server/app-rsc-response-finalizer.js +38 -0
- package/dist/server/app-rsc-response-finalizer.js.map +1 -0
- package/dist/server/app-rsc-route-matching.js +8 -4
- package/dist/server/app-rsc-route-matching.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +33 -0
- package/dist/server/app-segment-config.js +90 -0
- package/dist/server/app-segment-config.js.map +1 -0
- package/dist/server/app-server-action-execution.d.ts +2 -0
- package/dist/server/app-server-action-execution.js +45 -51
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.js +21 -20
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +44 -0
- package/dist/server/artifact-compatibility.js +82 -0
- package/dist/server/artifact-compatibility.js.map +1 -0
- package/dist/server/cache-control.d.ts +24 -0
- package/dist/server/cache-control.js +33 -0
- package/dist/server/cache-control.js.map +1 -0
- package/dist/server/cache-proof.d.ts +200 -0
- package/dist/server/cache-proof.js +342 -0
- package/dist/server/cache-proof.js.map +1 -0
- package/dist/server/dev-error-overlay-store.d.ts +23 -0
- package/dist/server/dev-error-overlay-store.js +67 -0
- package/dist/server/dev-error-overlay-store.js.map +1 -0
- package/dist/server/dev-error-overlay.d.ts +15 -0
- package/dist/server/dev-error-overlay.js +548 -0
- package/dist/server/dev-error-overlay.js.map +1 -0
- package/dist/server/dev-origin-check.js +8 -4
- package/dist/server/dev-origin-check.js.map +1 -1
- package/dist/server/dev-server.js +1 -6
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/http-error-responses.d.ts +67 -0
- package/dist/server/http-error-responses.js +77 -0
- package/dist/server/http-error-responses.js.map +1 -0
- package/dist/server/image-optimization.js +2 -1
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation-runtime.d.ts +44 -0
- package/dist/server/instrumentation-runtime.js +29 -0
- package/dist/server/instrumentation-runtime.js.map +1 -0
- package/dist/server/isr-cache.d.ts +2 -7
- package/dist/server/isr-cache.js +7 -10
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-route-response.js +6 -5
- package/dist/server/metadata-route-response.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +1 -0
- package/dist/server/metadata-routes.js +6 -0
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-matcher.js +2 -2
- package/dist/server/middleware-matcher.js.map +1 -1
- package/dist/server/middleware-response-headers.js +21 -0
- package/dist/server/middleware-response-headers.js.map +1 -1
- package/dist/server/middleware-runtime.js +3 -3
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/navigation-trace.d.ts +33 -0
- package/dist/server/navigation-trace.js +35 -0
- package/dist/server/navigation-trace.js.map +1 -0
- package/dist/server/next-error-digest.d.ts +44 -0
- package/dist/server/next-error-digest.js +40 -0
- package/dist/server/next-error-digest.js.map +1 -0
- package/dist/server/pages-api-route.js +2 -1
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-node-compat.js +4 -16
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +2 -1
- package/dist/server/pages-page-data.js +6 -5
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +3 -8
- package/dist/server/pages-page-response.js +46 -15
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +6 -0
- package/dist/server/prod-server.js +28 -21
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +42 -1
- package/dist/server/request-pipeline.js +97 -17
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/rsc-stream-hints.d.ts +3 -1
- package/dist/server/rsc-stream-hints.js +4 -1
- package/dist/server/rsc-stream-hints.js.map +1 -1
- package/dist/server/seed-cache.js +19 -8
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +2 -2
- package/dist/shims/cache-runtime.js +31 -17
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +15 -3
- package/dist/shims/cache.js +45 -20
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +17 -1
- package/dist/shims/error-boundary.js +31 -1
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +4 -1
- package/dist/shims/fetch-cache.js +57 -16
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head-state.js +2 -3
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/headers.js +4 -44
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-state.js +2 -3
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/image.js +93 -5
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/als-registry.d.ts +15 -0
- package/dist/shims/internal/als-registry.js +55 -0
- package/dist/shims/internal/als-registry.js.map +1 -0
- package/dist/shims/internal/cookie-serialize.d.ts +46 -0
- package/dist/shims/internal/cookie-serialize.js +51 -0
- package/dist/shims/internal/cookie-serialize.js.map +1 -0
- package/dist/shims/link.js +31 -26
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +26 -1
- package/dist/shims/metadata.js +94 -4
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.js +2 -3
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +2 -7
- package/dist/shims/navigation.js +44 -36
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/request-context.js +2 -4
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/router-state.js +2 -3
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.js +2 -2
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.js +5 -30
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +1 -1
- package/dist/shims/slot.js +5 -4
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/thenable-params.d.ts +5 -2
- package/dist/shims/thenable-params.js +26 -6
- package/dist/shims/thenable-params.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/unified-request-context.js +3 -14
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/shims/use-merged-ref.d.ts +7 -0
- package/dist/shims/use-merged-ref.js +40 -0
- package/dist/shims/use-merged-ref.js.map +1 -0
- package/dist/utils/base-path.d.ts +7 -1
- package/dist/utils/base-path.js +12 -1
- package/dist/utils/base-path.js.map +1 -1
- package/dist/utils/cache-control-metadata.d.ts +6 -0
- package/dist/utils/cache-control-metadata.js +16 -0
- package/dist/utils/cache-control-metadata.js.map +1 -0
- package/dist/utils/safe-json-file.d.ts +18 -0
- package/dist/utils/safe-json-file.js +25 -0
- package/dist/utils/safe-json-file.js.map +1 -0
- package/dist/utils/text-stream.d.ts +29 -0
- package/dist/utils/text-stream.js +66 -0
- package/dist/utils/text-stream.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als-registry.js","names":[],"sources":["../../../src/shims/internal/als-registry.ts"],"sourcesContent":["/**\n * Shared helper for registering AsyncLocalStorage instances on `globalThis`\n * via `Symbol.for(...)` so that they survive multiple module instances.\n *\n * Why this helper exists\n * ----------------------\n * Vite's multi-environment setup (RSC / SSR / client) and HMR can load a\n * single source module under several different specifiers, producing more\n * than one module instance at runtime. If each instance kept its own\n * module-local `new AsyncLocalStorage()`, request-scoped state would silently\n * fork across instances — `headers()` in one environment wouldn't see what\n * `connection()` registered in another, concurrent requests would stomp each\n * other, etc.\n *\n * The fix every shim was applying inline:\n *\n * const _ALS_KEY = Symbol.for(\"vinext.foo.als\");\n * const _g = globalThis as unknown as Record<PropertyKey, unknown>;\n * const _als = (_g[_ALS_KEY] ??=\n * new AsyncLocalStorage<T>()) as AsyncLocalStorage<T>;\n *\n * This helper packages that pattern.\n *\n * Cross-bundle singleton property — preserved\n * -------------------------------------------\n * - `Symbol.for(key)` consults the global symbol registry and returns the\n * same symbol regardless of which module instance calls it.\n * - `globalThis[sym]` is a single slot shared by every module instance.\n * - `??=` only assigns when the slot is empty, so the first caller wins and\n * every subsequent caller (in any module instance) reads the same ALS.\n *\n * The helper module itself never holds the ALS by reference — it always\n * round-trips through `globalThis`. So even if this helper file is itself\n * loaded under multiple module instances, every copy still hands back the\n * one true ALS for a given key.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst _g = globalThis as unknown as Record<PropertyKey, unknown>;\n\n/**\n * Get (or lazily create) the AsyncLocalStorage registered on `globalThis`\n * under `Symbol.for(key)`. Multiple callers — including callers in different\n * module instances — that pass the same `key` receive the same ALS instance.\n *\n * @param key - String key fed to `Symbol.for(...)`. By convention vinext\n * shims use a dotted namespace such as `\"vinext.cache.als\"`.\n */\nexport function getOrCreateAls<T>(key: string): AsyncLocalStorage<T> {\n const sym = Symbol.for(key);\n return (_g[sym] ??= new AsyncLocalStorage<T>()) as AsyncLocalStorage<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAM,KAAK;;;;;;;;;AAUX,SAAgB,eAAkB,KAAmC;CACnE,MAAM,MAAM,OAAO,IAAI,IAAI;AAC3B,QAAQ,GAAG,SAAS,IAAI,mBAAsB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/shims/internal/cookie-serialize.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared Set-Cookie serialization for the next/headers and next/server shims.
|
|
4
|
+
*
|
|
5
|
+
* Two call sites — `cookies().set()` in headers.ts and `ResponseCookies.set()`
|
|
6
|
+
* in server.ts — produce identical Set-Cookie header strings. Keep the
|
|
7
|
+
* encoding, attribute order, and validation in one place so subtle RFC 6265
|
|
8
|
+
* details (escaping, attribute ordering, etc.) cannot drift between them.
|
|
9
|
+
*
|
|
10
|
+
* Note: this is a value-encoding helper for response cookies only. The
|
|
11
|
+
* request `Cookie:` header serialization in `RequestCookies._serialize`
|
|
12
|
+
* (server.ts) intentionally lives separately — it builds a different format
|
|
13
|
+
* (no attributes, just `name=value; name=value`) and shouldn't share this
|
|
14
|
+
* code.
|
|
15
|
+
*/
|
|
16
|
+
type SerializeSetCookieOptions = {
|
|
17
|
+
path?: string;
|
|
18
|
+
domain?: string;
|
|
19
|
+
maxAge?: number;
|
|
20
|
+
expires?: Date;
|
|
21
|
+
httpOnly?: boolean;
|
|
22
|
+
secure?: boolean;
|
|
23
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
24
|
+
};
|
|
25
|
+
declare function validateCookieName(name: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Validate cookie attribute values (path, domain) to prevent injection
|
|
28
|
+
* via semicolons, newlines, or other control characters.
|
|
29
|
+
*/
|
|
30
|
+
declare function validateCookieAttributeValue(value: string, attributeName: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Build a Set-Cookie header string from a cookie name, value, and attributes.
|
|
33
|
+
*
|
|
34
|
+
* - Encodes the value with `encodeURIComponent`.
|
|
35
|
+
* - Defaults `Path` to `/` (matching @edge-runtime/cookies and Next.js).
|
|
36
|
+
* - Validates path/domain to reject control characters and semicolons.
|
|
37
|
+
* - Emits attributes in the order: Path, Domain, Max-Age, Expires, HttpOnly,
|
|
38
|
+
* Secure, SameSite.
|
|
39
|
+
*
|
|
40
|
+
* The caller is responsible for validating the cookie name (typically before
|
|
41
|
+
* mutating any internal state) via `validateCookieName`.
|
|
42
|
+
*/
|
|
43
|
+
declare function serializeSetCookie(name: string, value: string, options?: SerializeSetCookieOptions): string;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { serializeSetCookie, validateCookieAttributeValue, validateCookieName };
|
|
46
|
+
//# sourceMappingURL=cookie-serialize.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/shims/internal/cookie-serialize.ts
|
|
2
|
+
/**
|
|
3
|
+
* RFC 6265 §4.1.1: cookie-name is a token (RFC 2616 §2.2).
|
|
4
|
+
* Allowed: any visible ASCII (0x21-0x7E) except separators: ()<>@,;:\"/[]?={}
|
|
5
|
+
*/
|
|
6
|
+
const VALID_COOKIE_NAME_RE = /^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+$/;
|
|
7
|
+
function validateCookieName(name) {
|
|
8
|
+
if (!name || !VALID_COOKIE_NAME_RE.test(name)) throw new Error(`Invalid cookie name: ${JSON.stringify(name)}`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate cookie attribute values (path, domain) to prevent injection
|
|
12
|
+
* via semicolons, newlines, or other control characters.
|
|
13
|
+
*/
|
|
14
|
+
function validateCookieAttributeValue(value, attributeName) {
|
|
15
|
+
for (let i = 0; i < value.length; i++) {
|
|
16
|
+
const code = value.charCodeAt(i);
|
|
17
|
+
if (code <= 31 || code === 127 || value[i] === ";") throw new Error(`Invalid cookie ${attributeName} value: ${JSON.stringify(value)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a Set-Cookie header string from a cookie name, value, and attributes.
|
|
22
|
+
*
|
|
23
|
+
* - Encodes the value with `encodeURIComponent`.
|
|
24
|
+
* - Defaults `Path` to `/` (matching @edge-runtime/cookies and Next.js).
|
|
25
|
+
* - Validates path/domain to reject control characters and semicolons.
|
|
26
|
+
* - Emits attributes in the order: Path, Domain, Max-Age, Expires, HttpOnly,
|
|
27
|
+
* Secure, SameSite.
|
|
28
|
+
*
|
|
29
|
+
* The caller is responsible for validating the cookie name (typically before
|
|
30
|
+
* mutating any internal state) via `validateCookieName`.
|
|
31
|
+
*/
|
|
32
|
+
function serializeSetCookie(name, value, options) {
|
|
33
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
34
|
+
const path = options?.path ?? "/";
|
|
35
|
+
validateCookieAttributeValue(path, "Path");
|
|
36
|
+
parts.push(`Path=${path}`);
|
|
37
|
+
if (options?.domain) {
|
|
38
|
+
validateCookieAttributeValue(options.domain, "Domain");
|
|
39
|
+
parts.push(`Domain=${options.domain}`);
|
|
40
|
+
}
|
|
41
|
+
if (options?.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
42
|
+
if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
43
|
+
if (options?.httpOnly) parts.push("HttpOnly");
|
|
44
|
+
if (options?.secure) parts.push("Secure");
|
|
45
|
+
if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
46
|
+
return parts.join("; ");
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { serializeSetCookie, validateCookieAttributeValue, validateCookieName };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=cookie-serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-serialize.js","names":[],"sources":["../../../src/shims/internal/cookie-serialize.ts"],"sourcesContent":["/**\n * Shared Set-Cookie serialization for the next/headers and next/server shims.\n *\n * Two call sites — `cookies().set()` in headers.ts and `ResponseCookies.set()`\n * in server.ts — produce identical Set-Cookie header strings. Keep the\n * encoding, attribute order, and validation in one place so subtle RFC 6265\n * details (escaping, attribute ordering, etc.) cannot drift between them.\n *\n * Note: this is a value-encoding helper for response cookies only. The\n * request `Cookie:` header serialization in `RequestCookies._serialize`\n * (server.ts) intentionally lives separately — it builds a different format\n * (no attributes, just `name=value; name=value`) and shouldn't share this\n * code.\n */\n\ntype SerializeSetCookieOptions = {\n path?: string;\n domain?: string;\n maxAge?: number;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n};\n\n/**\n * RFC 6265 §4.1.1: cookie-name is a token (RFC 2616 §2.2).\n * Allowed: any visible ASCII (0x21-0x7E) except separators: ()<>@,;:\\\"/[]?={}\n */\nconst VALID_COOKIE_NAME_RE =\n /^[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2E\\x30-\\x39\\x41-\\x5A\\x5E-\\x7A\\x7C\\x7E]+$/;\n\nexport function validateCookieName(name: string): void {\n if (!name || !VALID_COOKIE_NAME_RE.test(name)) {\n throw new Error(`Invalid cookie name: ${JSON.stringify(name)}`);\n }\n}\n\n/**\n * Validate cookie attribute values (path, domain) to prevent injection\n * via semicolons, newlines, or other control characters.\n */\nexport function validateCookieAttributeValue(value: string, attributeName: string): void {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code <= 0x1f || code === 0x7f || value[i] === \";\") {\n throw new Error(`Invalid cookie ${attributeName} value: ${JSON.stringify(value)}`);\n }\n }\n}\n\n/**\n * Build a Set-Cookie header string from a cookie name, value, and attributes.\n *\n * - Encodes the value with `encodeURIComponent`.\n * - Defaults `Path` to `/` (matching @edge-runtime/cookies and Next.js).\n * - Validates path/domain to reject control characters and semicolons.\n * - Emits attributes in the order: Path, Domain, Max-Age, Expires, HttpOnly,\n * Secure, SameSite.\n *\n * The caller is responsible for validating the cookie name (typically before\n * mutating any internal state) via `validateCookieName`.\n */\nexport function serializeSetCookie(\n name: string,\n value: string,\n options?: SerializeSetCookieOptions,\n): string {\n const parts = [`${name}=${encodeURIComponent(value)}`];\n const path = options?.path ?? \"/\";\n validateCookieAttributeValue(path, \"Path\");\n parts.push(`Path=${path}`);\n if (options?.domain) {\n validateCookieAttributeValue(options.domain, \"Domain\");\n parts.push(`Domain=${options.domain}`);\n }\n if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n if (options?.httpOnly) parts.push(\"HttpOnly\");\n if (options?.secure) parts.push(\"Secure\");\n if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`);\n return parts.join(\"; \");\n}\n"],"mappings":";;;;;AA6BA,MAAM,uBACJ;AAEF,SAAgB,mBAAmB,MAAoB;AACrD,KAAI,CAAC,QAAQ,CAAC,qBAAqB,KAAK,KAAK,CAC3C,OAAM,IAAI,MAAM,wBAAwB,KAAK,UAAU,KAAK,GAAG;;;;;;AAQnE,SAAgB,6BAA6B,OAAe,eAA6B;AACvF,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM,WAAW,EAAE;AAChC,MAAI,QAAQ,MAAQ,SAAS,OAAQ,MAAM,OAAO,IAChD,OAAM,IAAI,MAAM,kBAAkB,cAAc,UAAU,KAAK,UAAU,MAAM,GAAG;;;;;;;;;;;;;;;AAiBxF,SAAgB,mBACd,MACA,OACA,SACQ;CACR,MAAM,QAAQ,CAAC,GAAG,KAAK,GAAG,mBAAmB,MAAM,GAAG;CACtD,MAAM,OAAO,SAAS,QAAQ;AAC9B,8BAA6B,MAAM,OAAO;AAC1C,OAAM,KAAK,QAAQ,OAAO;AAC1B,KAAI,SAAS,QAAQ;AACnB,+BAA6B,QAAQ,QAAQ,SAAS;AACtD,QAAM,KAAK,UAAU,QAAQ,SAAS;;AAExC,KAAI,SAAS,WAAW,KAAA,EAAW,OAAM,KAAK,WAAW,QAAQ,SAAS;AAC1E,KAAI,SAAS,QAAS,OAAM,KAAK,WAAW,QAAQ,QAAQ,aAAa,GAAG;AAC5E,KAAI,SAAS,SAAU,OAAM,KAAK,WAAW;AAC7C,KAAI,SAAS,OAAQ,OAAM,KAAK,SAAS;AACzC,KAAI,SAAS,SAAU,OAAM,KAAK,YAAY,QAAQ,WAAW;AACjE,QAAO,MAAM,KAAK,KAAK"}
|
package/dist/shims/link.js
CHANGED
|
@@ -3,8 +3,10 @@ import { isDangerousScheme } from "./url-safety.js";
|
|
|
3
3
|
import { resolveRelativeHref, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
|
|
4
4
|
import { addLocalePrefix, getDomainLocaleUrl } from "../utils/domain-locale.js";
|
|
5
5
|
import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.js";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { AppElementsWire } from "../server/app-elements-wire.js";
|
|
7
|
+
import "../server/app-elements.js";
|
|
8
|
+
import { VINEXT_RSC_MOUNTED_SLOTS_HEADER, createRscRequestHeaders, createRscRequestUrl } from "../server/app-rsc-cache-busting.js";
|
|
9
|
+
import { getCurrentInterceptionContext, getMountedSlotsHeader, getPrefetchedUrls, navigateClientSide, prefetchRscResponse } from "./navigation.js";
|
|
8
10
|
import { getI18nContext } from "./i18n-context.js";
|
|
9
11
|
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
10
12
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -55,31 +57,34 @@ function prefetchUrl(href) {
|
|
|
55
57
|
prefetchHref = localPath;
|
|
56
58
|
}
|
|
57
59
|
const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);
|
|
58
|
-
const rscUrl = toRscUrl(fullHref);
|
|
59
|
-
const interceptionContext = getCurrentInterceptionContext();
|
|
60
|
-
const cacheKey = createAppPayloadCacheKey(rscUrl, interceptionContext);
|
|
61
|
-
const prefetched = getPrefetchedUrls();
|
|
62
|
-
if (prefetched.has(cacheKey)) return;
|
|
63
|
-
prefetched.add(cacheKey);
|
|
64
60
|
(window.requestIdleCallback ?? ((fn) => setTimeout(fn, 100)))(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
headers
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
(async () => {
|
|
62
|
+
if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") {
|
|
63
|
+
const interceptionContext = getCurrentInterceptionContext();
|
|
64
|
+
const mountedSlotsHeader = getMountedSlotsHeader();
|
|
65
|
+
const headers = createRscRequestHeaders({ interceptionContext });
|
|
66
|
+
if (mountedSlotsHeader) headers.set(VINEXT_RSC_MOUNTED_SLOTS_HEADER, mountedSlotsHeader);
|
|
67
|
+
const rscUrl = await createRscRequestUrl(fullHref, headers);
|
|
68
|
+
const cacheKey = AppElementsWire.encodeCacheKey(rscUrl, interceptionContext);
|
|
69
|
+
const prefetched = getPrefetchedUrls();
|
|
70
|
+
if (prefetched.has(cacheKey)) return;
|
|
71
|
+
prefetched.add(cacheKey);
|
|
72
|
+
prefetchRscResponse(rscUrl, fetch(rscUrl, {
|
|
73
|
+
headers,
|
|
74
|
+
credentials: "include",
|
|
75
|
+
priority: "low",
|
|
76
|
+
purpose: "prefetch"
|
|
77
|
+
}), interceptionContext, mountedSlotsHeader);
|
|
78
|
+
} else if (window.__NEXT_DATA__?.__vinext?.pageModuleUrl) {
|
|
79
|
+
const link = document.createElement("link");
|
|
80
|
+
link.rel = "prefetch";
|
|
81
|
+
link.href = fullHref;
|
|
82
|
+
link.as = "document";
|
|
83
|
+
document.head.appendChild(link);
|
|
84
|
+
}
|
|
85
|
+
})().catch((error) => {
|
|
86
|
+
console.error("[vinext] RSC prefetch setup error:", error);
|
|
87
|
+
});
|
|
83
88
|
});
|
|
84
89
|
}
|
|
85
90
|
/**
|
package/dist/shims/link.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.js","names":[],"sources":["../../src/shims/link.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * next/link shim\n *\n * Renders an <a> tag with client-side navigation support.\n * On click, prevents full page reload and triggers client-side\n * page swap via the router's navigation system.\n */\nimport React, {\n forwardRef,\n useRef,\n useEffect,\n useCallback,\n useContext,\n createContext,\n useState,\n type AnchorHTMLAttributes,\n type MouseEvent,\n} from \"react\";\n// Import shared RSC prefetch utilities from navigation shim (relative path\n// so this resolves both via the Vite plugin and in direct vitest imports)\nimport {\n getCurrentInterceptionContext,\n toRscUrl,\n getPrefetchedUrls,\n getMountedSlotsHeader,\n navigateClientSide,\n prefetchRscResponse,\n} from \"./navigation.js\";\nimport { createAppPayloadCacheKey } from \"../server/app-elements.js\";\nimport { isDangerousScheme } from \"./url-safety.js\";\nimport {\n resolveRelativeHref,\n toBrowserNavigationHref,\n toSameOriginAppPath,\n withBasePath,\n} from \"./url-utils.js\";\nimport { appendSearchParamsToUrl, type UrlQuery, urlQueryToSearchParams } from \"../utils/query.js\";\nimport { addLocalePrefix, getDomainLocaleUrl, type DomainLocale } from \"../utils/domain-locale.js\";\nimport { getI18nContext } from \"./i18n-context.js\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\n\ntype NavigateEvent = {\n url: URL;\n /** Call to prevent the Link's default navigation (e.g. for View Transitions). */\n preventDefault(): void;\n /** Whether preventDefault() has been called. */\n defaultPrevented: boolean;\n};\n\ntype LinkProps = {\n href: string | { pathname?: string; query?: UrlQuery };\n /** URL displayed in the browser (when href is a route pattern like /user/[id]) */\n as?: string;\n /** Replace the current history entry instead of pushing */\n replace?: boolean;\n /** Prefetch the page in the background (default: true, uses IntersectionObserver) */\n prefetch?: boolean;\n /** Whether to pass the href to the child element */\n passHref?: boolean;\n /** Scroll to top on navigation (default: true) */\n scroll?: boolean;\n /** Locale for i18n (used for locale-prefixed URLs) */\n locale?: string | false;\n /** Called before navigation happens (Next.js 16). Return value is ignored. */\n onNavigate?: (event: NavigateEvent) => void;\n children?: React.ReactNode;\n} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\">;\n\n// ---------------------------------------------------------------------------\n// useLinkStatus — reports the pending state of a parent <Link> navigation\n// ---------------------------------------------------------------------------\n\ntype LinkStatusContextValue = {\n pending: boolean;\n};\n\nconst LinkStatusContext = createContext<LinkStatusContextValue>({ pending: false });\n\n/**\n * useLinkStatus returns the pending state of the enclosing <Link>.\n * In Next.js, this is used to show loading indicators while a\n * prefetch-triggered navigation is in progress.\n */\nexport function useLinkStatus(): LinkStatusContextValue {\n return useContext(LinkStatusContext);\n}\n\n/** basePath from next.config.js, injected by the plugin at build time */\nconst __basePath: string = process.env.__NEXT_ROUTER_BASEPATH ?? \"\";\n\nfunction resolveHref(href: LinkProps[\"href\"]): string {\n if (typeof href === \"string\") return href;\n let url = href.pathname ?? \"/\";\n if (href.query) {\n const params = urlQueryToSearchParams(href.query);\n url = appendSearchParamsToUrl(url, params);\n }\n return url;\n}\n\n// ---------------------------------------------------------------------------\n// Prefetching infrastructure\n// ---------------------------------------------------------------------------\n\n/**\n * Prefetch a URL for faster navigation.\n *\n * For App Router (RSC): fetches the .rsc payload in the background and\n * stores it in an in-memory cache for instant use during navigation.\n * For Pages Router: injects a <link rel=\"prefetch\"> for the page module.\n *\n * Uses `requestIdleCallback` (or `setTimeout` fallback) to avoid blocking\n * the main thread during initial page load.\n */\nfunction prefetchUrl(href: string): void {\n if (typeof window === \"undefined\") return;\n\n // Normalize same-origin absolute URLs to local paths before prefetching\n let prefetchHref = href;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n const localPath = toSameOriginAppPath(href, __basePath);\n if (localPath == null) return; // truly external — don't prefetch\n prefetchHref = localPath;\n }\n\n const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);\n\n // Distinguish the same visible URL when it is prefetched from different\n // interception sources such as /feed vs /gallery.\n const rscUrl = toRscUrl(fullHref);\n const interceptionContext = getCurrentInterceptionContext();\n const cacheKey = createAppPayloadCacheKey(rscUrl, interceptionContext);\n const prefetched = getPrefetchedUrls();\n if (prefetched.has(cacheKey)) return;\n prefetched.add(cacheKey);\n\n const schedule = window.requestIdleCallback ?? ((fn: () => void) => setTimeout(fn, 100));\n\n schedule(() => {\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n const mountedSlotsHeader = getMountedSlotsHeader();\n const headers = new Headers({ Accept: \"text/x-component\" });\n if (mountedSlotsHeader) {\n headers.set(\"X-Vinext-Mounted-Slots\", mountedSlotsHeader);\n }\n if (interceptionContext !== null) {\n headers.set(\"X-Vinext-Interception-Context\", interceptionContext);\n }\n prefetchRscResponse(\n rscUrl,\n fetch(rscUrl, {\n headers,\n credentials: \"include\",\n priority: \"low\" as const,\n // @ts-expect-error — purpose is a valid fetch option in some browsers\n purpose: \"prefetch\",\n }),\n interceptionContext,\n mountedSlotsHeader,\n );\n } else if ((window.__NEXT_DATA__ as VinextNextData | undefined)?.__vinext?.pageModuleUrl) {\n // Pages Router: inject a prefetch link for the target page module\n // We can't easily resolve the target page's module URL from the Link,\n // so we create a <link rel=\"prefetch\"> for the HTML page which helps\n // the browser's preload scanner.\n const link = document.createElement(\"link\");\n link.rel = \"prefetch\";\n link.href = fullHref;\n link.as = \"document\";\n document.head.appendChild(link);\n }\n });\n}\n\n/**\n * Shared IntersectionObserver for viewport-based prefetching.\n * All Link elements use the same observer to minimize resource usage.\n */\nlet sharedObserver: IntersectionObserver | null = null;\nconst observerCallbacks = new WeakMap<Element, () => void>();\n\nfunction getSharedObserver(): IntersectionObserver | null {\n if (typeof window === \"undefined\" || typeof IntersectionObserver === \"undefined\") return null;\n if (sharedObserver) return sharedObserver;\n\n sharedObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const callback = observerCallbacks.get(entry.target);\n if (callback) {\n callback();\n // Unobserve after prefetching — only prefetch once\n sharedObserver?.unobserve(entry.target);\n observerCallbacks.delete(entry.target);\n }\n }\n }\n },\n {\n // Start prefetching when the link is within 250px of the viewport.\n // This gives the browser a head start before the user scrolls to it.\n rootMargin: \"250px\",\n },\n );\n\n return sharedObserver;\n}\n\nfunction getDefaultLocale(): string | undefined {\n if (typeof window !== \"undefined\") {\n return window.__VINEXT_DEFAULT_LOCALE__;\n }\n return getI18nContext()?.defaultLocale;\n}\n\nfunction getDomainLocales(): readonly DomainLocale[] | undefined {\n if (typeof window !== \"undefined\") {\n return (window.__NEXT_DATA__ as VinextNextData | undefined)?.domainLocales;\n }\n return getI18nContext()?.domainLocales;\n}\n\nfunction getCurrentHostname(): string | undefined {\n if (typeof window !== \"undefined\") return window.location.hostname;\n return getI18nContext()?.hostname;\n}\n\nfunction getDomainLocaleHref(href: string, locale: string): string | undefined {\n // Only cross-domain locale switches need a special absolute URL here.\n // Same-domain cases fall back to the standard locale-prefix logic below.\n return getDomainLocaleUrl(href, locale, {\n basePath: __basePath,\n currentHostname: getCurrentHostname(),\n domainItems: getDomainLocales(),\n });\n}\n\n/**\n * Apply locale prefix to a URL path based on the locale prop.\n * - locale=\"fr\" → prepend /fr (unless it already has a locale prefix)\n * - locale={false} → use the href as-is (no locale prefix, link to default)\n * - locale=undefined → use current locale (href as-is in most cases)\n */\nfunction applyLocaleToHref(href: string, locale: string | false | undefined): string {\n if (locale === false) {\n // Explicit false: no locale prefix\n return href;\n }\n\n if (locale === undefined) {\n // No locale prop: keep current behavior (href as-is)\n return href;\n }\n\n // Absolute and protocol-relative URLs must not be prefixed — locale\n // only applies to local paths.\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n return href;\n }\n\n const domainLocaleHref = getDomainLocaleHref(href, locale);\n if (domainLocaleHref) {\n return domainLocaleHref;\n }\n\n return addLocalePrefix(href, locale, getDefaultLocale() ?? \"\");\n}\n\nconst Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n {\n href,\n as,\n replace = false,\n prefetch: prefetchProp,\n scroll = true,\n children,\n onClick,\n onNavigate,\n ...rest\n },\n forwardedRef,\n) {\n // Extract locale from rest props\n const { locale, ...restWithoutLocale } = rest;\n\n // If `as` is provided, use it as the actual URL (legacy Next.js pattern\n // where href is a route pattern like \"/user/[id]\" and as is \"/user/1\")\n const resolvedHref = as ?? resolveHref(href);\n\n const isDangerous = typeof resolvedHref === \"string\" && isDangerousScheme(resolvedHref);\n\n // Apply locale prefix if specified (safe even for dangerous hrefs since we\n // won't use the result when isDangerous is true)\n const localizedHref = applyLocaleToHref(isDangerous ? \"/\" : resolvedHref, locale);\n // Full href with basePath for browser URLs and fetches\n const fullHref = withBasePath(localizedHref, __basePath);\n\n // Track pending state for useLinkStatus()\n const [pending, setPending] = useState(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Prefetching: observe the element when it enters the viewport.\n // prefetch={false} disables, prefetch={true} or undefined/null (default) enables.\n const internalRef = useRef<HTMLAnchorElement | null>(null);\n const shouldPrefetch = prefetchProp !== false && !isDangerous;\n\n const setRefs = useCallback(\n (node: HTMLAnchorElement | null) => {\n internalRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef)\n (forwardedRef as React.MutableRefObject<HTMLAnchorElement | null>).current = node;\n },\n [forwardedRef],\n );\n\n useEffect(() => {\n if (!shouldPrefetch || typeof window === \"undefined\") return;\n const node = internalRef.current;\n if (!node) return;\n\n // Normalize same-origin absolute URLs; skip truly external ones\n let hrefToPrefetch = localizedHref;\n if (\n localizedHref.startsWith(\"http://\") ||\n localizedHref.startsWith(\"https://\") ||\n localizedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(localizedHref, __basePath);\n if (localPath == null) return; // truly external\n hrefToPrefetch = localPath;\n }\n\n const observer = getSharedObserver();\n if (!observer) return;\n\n observerCallbacks.set(node, () => prefetchUrl(hrefToPrefetch));\n observer.observe(node);\n\n return () => {\n observer.unobserve(node);\n observerCallbacks.delete(node);\n };\n }, [shouldPrefetch, localizedHref]);\n\n const handleClick = async (e: MouseEvent<HTMLAnchorElement>) => {\n if (onClick) onClick(e);\n if (e.defaultPrevented) return;\n\n // Only intercept left clicks without modifiers (standard link behavior)\n if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n // Don't intercept links with target (e.g. target=\"_blank\")\n if (e.currentTarget.target && e.currentTarget.target !== \"_self\") {\n return;\n }\n\n // External links: let the browser handle it.\n // Same-origin absolute URLs (e.g. http://localhost:3000/about) are\n // normalized to local paths so they get client-side navigation.\n let navigateHref = localizedHref;\n if (\n resolvedHref.startsWith(\"http://\") ||\n resolvedHref.startsWith(\"https://\") ||\n resolvedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(resolvedHref, __basePath);\n if (localPath == null) return; // truly external\n navigateHref = localPath;\n }\n\n e.preventDefault();\n\n // Resolve relative hrefs (#hash, ?query) against the current URL once so\n // onNavigate and the actual navigation target stay in sync.\n const absoluteHref = resolveRelativeHref(navigateHref, window.location.href, __basePath);\n const absoluteFullHref = toBrowserNavigationHref(\n navigateHref,\n window.location.href,\n __basePath,\n );\n\n // Call onNavigate callback if provided (Next.js 16 View Transitions support)\n if (onNavigate) {\n try {\n const navUrl = new URL(absoluteFullHref, window.location.origin);\n let prevented = false;\n const navEvent: NavigateEvent = {\n url: navUrl,\n preventDefault() {\n prevented = true;\n },\n get defaultPrevented() {\n return prevented;\n },\n };\n onNavigate(navEvent);\n // If the callback called preventDefault(), skip Link's default navigation.\n // The callback is responsible for its own navigation (e.g. via View Transitions API).\n if (navEvent.defaultPrevented) {\n return;\n }\n } catch {\n // Ignore URL parsing errors for relative/hash hrefs\n }\n }\n\n // App Router: delegate to navigateClientSide which handles scroll save,\n // hash-only changes, RSC fetch, and two-phase URL commit.\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n setPending(true);\n try {\n await navigateClientSide(navigateHref, replace ? \"replace\" : \"push\", scroll);\n } finally {\n if (mountedRef.current) setPending(false);\n }\n } else {\n // Next.js only consumes onRouterTransitionStart in the App Router.\n // Pages Router still executes instrumentation-client side effects\n // during startup, but it does not invoke the named export on navigation.\n // Pages Router: use the Router singleton\n try {\n const routerModule = await import(\"next/router\");\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- vinext's Router shim accepts (url, as, options)\n const Router = routerModule.default as any;\n if (replace) {\n await Router.replace(absoluteHref, undefined, { scroll });\n } else {\n await Router.push(absoluteHref, undefined, { scroll });\n }\n } catch {\n // Fallback to hard navigation if router fails\n if (replace) {\n window.history.replaceState({}, \"\", absoluteFullHref);\n } else {\n window.history.pushState({}, \"\", absoluteFullHref);\n }\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }\n }\n };\n\n // Remove props that shouldn't be on <a>\n const { passHref: _p, ...anchorProps } = restWithoutLocale;\n\n const linkStatusValue = React.useMemo(() => ({ pending }), [pending]);\n\n // Block dangerous URI schemes (javascript:, data:, vbscript:).\n // Render an inert <a> without href to prevent XSS while preserving\n // styling and attributes like className, id, aria-*.\n // This check is placed after all hooks to satisfy the Rules of Hooks.\n if (isDangerous) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(`<Link> blocked dangerous href: ${resolvedHref}`);\n }\n return <a {...anchorProps}>{children}</a>;\n }\n\n return (\n <LinkStatusContext.Provider value={linkStatusValue}>\n <a ref={setRefs} href={fullHref} onClick={handleClick} {...anchorProps}>\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n});\n\nexport default Link;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"link.js","names":[],"sources":["../../src/shims/link.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * next/link shim\n *\n * Renders an <a> tag with client-side navigation support.\n * On click, prevents full page reload and triggers client-side\n * page swap via the router's navigation system.\n */\nimport React, {\n forwardRef,\n useRef,\n useEffect,\n useCallback,\n useContext,\n createContext,\n useState,\n type AnchorHTMLAttributes,\n type MouseEvent,\n} from \"react\";\n// Import shared RSC prefetch utilities from navigation shim (relative path\n// so this resolves both via the Vite plugin and in direct vitest imports)\nimport {\n getCurrentInterceptionContext,\n getPrefetchedUrls,\n getMountedSlotsHeader,\n navigateClientSide,\n prefetchRscResponse,\n} from \"./navigation.js\";\nimport { AppElementsWire } from \"../server/app-elements.js\";\nimport {\n createRscRequestHeaders,\n createRscRequestUrl,\n VINEXT_RSC_MOUNTED_SLOTS_HEADER,\n} from \"../server/app-rsc-cache-busting.js\";\nimport { isDangerousScheme } from \"./url-safety.js\";\nimport {\n resolveRelativeHref,\n toBrowserNavigationHref,\n toSameOriginAppPath,\n withBasePath,\n} from \"./url-utils.js\";\nimport { appendSearchParamsToUrl, type UrlQuery, urlQueryToSearchParams } from \"../utils/query.js\";\nimport { addLocalePrefix, getDomainLocaleUrl, type DomainLocale } from \"../utils/domain-locale.js\";\nimport { getI18nContext } from \"./i18n-context.js\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\n\ntype NavigateEvent = {\n url: URL;\n /** Call to prevent the Link's default navigation (e.g. for View Transitions). */\n preventDefault(): void;\n /** Whether preventDefault() has been called. */\n defaultPrevented: boolean;\n};\n\ntype LinkProps = {\n href: string | { pathname?: string; query?: UrlQuery };\n /** URL displayed in the browser (when href is a route pattern like /user/[id]) */\n as?: string;\n /** Replace the current history entry instead of pushing */\n replace?: boolean;\n /** Prefetch the page in the background (default: true, uses IntersectionObserver) */\n prefetch?: boolean;\n /** Whether to pass the href to the child element */\n passHref?: boolean;\n /** Scroll to top on navigation (default: true) */\n scroll?: boolean;\n /** Locale for i18n (used for locale-prefixed URLs) */\n locale?: string | false;\n /** Called before navigation happens (Next.js 16). Return value is ignored. */\n onNavigate?: (event: NavigateEvent) => void;\n children?: React.ReactNode;\n} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\">;\n\n// ---------------------------------------------------------------------------\n// useLinkStatus — reports the pending state of a parent <Link> navigation\n// ---------------------------------------------------------------------------\n\ntype LinkStatusContextValue = {\n pending: boolean;\n};\n\nconst LinkStatusContext = createContext<LinkStatusContextValue>({ pending: false });\n\n/**\n * useLinkStatus returns the pending state of the enclosing <Link>.\n * In Next.js, this is used to show loading indicators while a\n * prefetch-triggered navigation is in progress.\n */\nexport function useLinkStatus(): LinkStatusContextValue {\n return useContext(LinkStatusContext);\n}\n\n/** basePath from next.config.js, injected by the plugin at build time */\nconst __basePath: string = process.env.__NEXT_ROUTER_BASEPATH ?? \"\";\n\nfunction resolveHref(href: LinkProps[\"href\"]): string {\n if (typeof href === \"string\") return href;\n let url = href.pathname ?? \"/\";\n if (href.query) {\n const params = urlQueryToSearchParams(href.query);\n url = appendSearchParamsToUrl(url, params);\n }\n return url;\n}\n\n// ---------------------------------------------------------------------------\n// Prefetching infrastructure\n// ---------------------------------------------------------------------------\n\n/**\n * Prefetch a URL for faster navigation.\n *\n * For App Router (RSC): fetches the .rsc payload in the background and\n * stores it in an in-memory cache for instant use during navigation.\n * For Pages Router: injects a <link rel=\"prefetch\"> for the page module.\n *\n * Uses `requestIdleCallback` (or `setTimeout` fallback) to avoid blocking\n * the main thread during initial page load.\n */\nfunction prefetchUrl(href: string): void {\n if (typeof window === \"undefined\") return;\n\n // Normalize same-origin absolute URLs to local paths before prefetching\n let prefetchHref = href;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n const localPath = toSameOriginAppPath(href, __basePath);\n if (localPath == null) return; // truly external — don't prefetch\n prefetchHref = localPath;\n }\n\n const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);\n\n const schedule = window.requestIdleCallback ?? ((fn: () => void) => setTimeout(fn, 100));\n\n schedule(() => {\n void (async () => {\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n const interceptionContext = getCurrentInterceptionContext();\n const mountedSlotsHeader = getMountedSlotsHeader();\n const headers = createRscRequestHeaders({ interceptionContext });\n if (mountedSlotsHeader) {\n headers.set(VINEXT_RSC_MOUNTED_SLOTS_HEADER, mountedSlotsHeader);\n }\n // Distinguish the same visible URL when it is prefetched from different\n // request contexts such as /feed vs /gallery or different mounted slots.\n const rscUrl = await createRscRequestUrl(fullHref, headers);\n const cacheKey = AppElementsWire.encodeCacheKey(rscUrl, interceptionContext);\n const prefetched = getPrefetchedUrls();\n if (prefetched.has(cacheKey)) return;\n prefetched.add(cacheKey);\n prefetchRscResponse(\n rscUrl,\n fetch(rscUrl, {\n headers,\n credentials: \"include\",\n priority: \"low\" as const,\n // @ts-expect-error — purpose is a valid fetch option in some browsers\n purpose: \"prefetch\",\n }),\n interceptionContext,\n mountedSlotsHeader,\n );\n } else if ((window.__NEXT_DATA__ as VinextNextData | undefined)?.__vinext?.pageModuleUrl) {\n // Pages Router: inject a prefetch link for the target page module\n // We can't easily resolve the target page's module URL from the Link,\n // so we create a <link rel=\"prefetch\"> for the HTML page which helps\n // the browser's preload scanner.\n const link = document.createElement(\"link\");\n link.rel = \"prefetch\";\n link.href = fullHref;\n link.as = \"document\";\n document.head.appendChild(link);\n }\n })().catch((error) => {\n console.error(\"[vinext] RSC prefetch setup error:\", error);\n });\n });\n}\n\n/**\n * Shared IntersectionObserver for viewport-based prefetching.\n * All Link elements use the same observer to minimize resource usage.\n */\nlet sharedObserver: IntersectionObserver | null = null;\nconst observerCallbacks = new WeakMap<Element, () => void>();\n\nfunction getSharedObserver(): IntersectionObserver | null {\n if (typeof window === \"undefined\" || typeof IntersectionObserver === \"undefined\") return null;\n if (sharedObserver) return sharedObserver;\n\n sharedObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const callback = observerCallbacks.get(entry.target);\n if (callback) {\n callback();\n // Unobserve after prefetching — only prefetch once\n sharedObserver?.unobserve(entry.target);\n observerCallbacks.delete(entry.target);\n }\n }\n }\n },\n {\n // Start prefetching when the link is within 250px of the viewport.\n // This gives the browser a head start before the user scrolls to it.\n rootMargin: \"250px\",\n },\n );\n\n return sharedObserver;\n}\n\nfunction getDefaultLocale(): string | undefined {\n if (typeof window !== \"undefined\") {\n return window.__VINEXT_DEFAULT_LOCALE__;\n }\n return getI18nContext()?.defaultLocale;\n}\n\nfunction getDomainLocales(): readonly DomainLocale[] | undefined {\n if (typeof window !== \"undefined\") {\n return (window.__NEXT_DATA__ as VinextNextData | undefined)?.domainLocales;\n }\n return getI18nContext()?.domainLocales;\n}\n\nfunction getCurrentHostname(): string | undefined {\n if (typeof window !== \"undefined\") return window.location.hostname;\n return getI18nContext()?.hostname;\n}\n\nfunction getDomainLocaleHref(href: string, locale: string): string | undefined {\n // Only cross-domain locale switches need a special absolute URL here.\n // Same-domain cases fall back to the standard locale-prefix logic below.\n return getDomainLocaleUrl(href, locale, {\n basePath: __basePath,\n currentHostname: getCurrentHostname(),\n domainItems: getDomainLocales(),\n });\n}\n\n/**\n * Apply locale prefix to a URL path based on the locale prop.\n * - locale=\"fr\" → prepend /fr (unless it already has a locale prefix)\n * - locale={false} → use the href as-is (no locale prefix, link to default)\n * - locale=undefined → use current locale (href as-is in most cases)\n */\nfunction applyLocaleToHref(href: string, locale: string | false | undefined): string {\n if (locale === false) {\n // Explicit false: no locale prefix\n return href;\n }\n\n if (locale === undefined) {\n // No locale prop: keep current behavior (href as-is)\n return href;\n }\n\n // Absolute and protocol-relative URLs must not be prefixed — locale\n // only applies to local paths.\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n return href;\n }\n\n const domainLocaleHref = getDomainLocaleHref(href, locale);\n if (domainLocaleHref) {\n return domainLocaleHref;\n }\n\n return addLocalePrefix(href, locale, getDefaultLocale() ?? \"\");\n}\n\nconst Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n {\n href,\n as,\n replace = false,\n prefetch: prefetchProp,\n scroll = true,\n children,\n onClick,\n onNavigate,\n ...rest\n },\n forwardedRef,\n) {\n // Extract locale from rest props\n const { locale, ...restWithoutLocale } = rest;\n\n // If `as` is provided, use it as the actual URL (legacy Next.js pattern\n // where href is a route pattern like \"/user/[id]\" and as is \"/user/1\")\n const resolvedHref = as ?? resolveHref(href);\n\n const isDangerous = typeof resolvedHref === \"string\" && isDangerousScheme(resolvedHref);\n\n // Apply locale prefix if specified (safe even for dangerous hrefs since we\n // won't use the result when isDangerous is true)\n const localizedHref = applyLocaleToHref(isDangerous ? \"/\" : resolvedHref, locale);\n // Full href with basePath for browser URLs and fetches\n const fullHref = withBasePath(localizedHref, __basePath);\n\n // Track pending state for useLinkStatus()\n const [pending, setPending] = useState(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Prefetching: observe the element when it enters the viewport.\n // prefetch={false} disables, prefetch={true} or undefined/null (default) enables.\n const internalRef = useRef<HTMLAnchorElement | null>(null);\n const shouldPrefetch = prefetchProp !== false && !isDangerous;\n\n const setRefs = useCallback(\n (node: HTMLAnchorElement | null) => {\n internalRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef)\n (forwardedRef as React.MutableRefObject<HTMLAnchorElement | null>).current = node;\n },\n [forwardedRef],\n );\n\n useEffect(() => {\n if (!shouldPrefetch || typeof window === \"undefined\") return;\n const node = internalRef.current;\n if (!node) return;\n\n // Normalize same-origin absolute URLs; skip truly external ones\n let hrefToPrefetch = localizedHref;\n if (\n localizedHref.startsWith(\"http://\") ||\n localizedHref.startsWith(\"https://\") ||\n localizedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(localizedHref, __basePath);\n if (localPath == null) return; // truly external\n hrefToPrefetch = localPath;\n }\n\n const observer = getSharedObserver();\n if (!observer) return;\n\n observerCallbacks.set(node, () => prefetchUrl(hrefToPrefetch));\n observer.observe(node);\n\n return () => {\n observer.unobserve(node);\n observerCallbacks.delete(node);\n };\n }, [shouldPrefetch, localizedHref]);\n\n const handleClick = async (e: MouseEvent<HTMLAnchorElement>) => {\n if (onClick) onClick(e);\n if (e.defaultPrevented) return;\n\n // Only intercept left clicks without modifiers (standard link behavior)\n if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n // Don't intercept links with target (e.g. target=\"_blank\")\n if (e.currentTarget.target && e.currentTarget.target !== \"_self\") {\n return;\n }\n\n // External links: let the browser handle it.\n // Same-origin absolute URLs (e.g. http://localhost:3000/about) are\n // normalized to local paths so they get client-side navigation.\n let navigateHref = localizedHref;\n if (\n resolvedHref.startsWith(\"http://\") ||\n resolvedHref.startsWith(\"https://\") ||\n resolvedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(resolvedHref, __basePath);\n if (localPath == null) return; // truly external\n navigateHref = localPath;\n }\n\n e.preventDefault();\n\n // Resolve relative hrefs (#hash, ?query) against the current URL once so\n // onNavigate and the actual navigation target stay in sync.\n const absoluteHref = resolveRelativeHref(navigateHref, window.location.href, __basePath);\n const absoluteFullHref = toBrowserNavigationHref(\n navigateHref,\n window.location.href,\n __basePath,\n );\n\n // Call onNavigate callback if provided (Next.js 16 View Transitions support)\n if (onNavigate) {\n try {\n const navUrl = new URL(absoluteFullHref, window.location.origin);\n let prevented = false;\n const navEvent: NavigateEvent = {\n url: navUrl,\n preventDefault() {\n prevented = true;\n },\n get defaultPrevented() {\n return prevented;\n },\n };\n onNavigate(navEvent);\n // If the callback called preventDefault(), skip Link's default navigation.\n // The callback is responsible for its own navigation (e.g. via View Transitions API).\n if (navEvent.defaultPrevented) {\n return;\n }\n } catch {\n // Ignore URL parsing errors for relative/hash hrefs\n }\n }\n\n // App Router: delegate to navigateClientSide which handles scroll save,\n // hash-only changes, RSC fetch, and two-phase URL commit.\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n setPending(true);\n try {\n await navigateClientSide(navigateHref, replace ? \"replace\" : \"push\", scroll);\n } finally {\n if (mountedRef.current) setPending(false);\n }\n } else {\n // Next.js only consumes onRouterTransitionStart in the App Router.\n // Pages Router still executes instrumentation-client side effects\n // during startup, but it does not invoke the named export on navigation.\n // Pages Router: use the Router singleton\n try {\n const routerModule = await import(\"next/router\");\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- vinext's Router shim accepts (url, as, options)\n const Router = routerModule.default as any;\n if (replace) {\n await Router.replace(absoluteHref, undefined, { scroll });\n } else {\n await Router.push(absoluteHref, undefined, { scroll });\n }\n } catch {\n // Fallback to hard navigation if router fails\n if (replace) {\n window.history.replaceState({}, \"\", absoluteFullHref);\n } else {\n window.history.pushState({}, \"\", absoluteFullHref);\n }\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }\n }\n };\n\n // Remove props that shouldn't be on <a>\n const { passHref: _p, ...anchorProps } = restWithoutLocale;\n\n const linkStatusValue = React.useMemo(() => ({ pending }), [pending]);\n\n // Block dangerous URI schemes (javascript:, data:, vbscript:).\n // Render an inert <a> without href to prevent XSS while preserving\n // styling and attributes like className, id, aria-*.\n // This check is placed after all hooks to satisfy the Rules of Hooks.\n if (isDangerous) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(`<Link> blocked dangerous href: ${resolvedHref}`);\n }\n return <a {...anchorProps}>{children}</a>;\n }\n\n return (\n <LinkStatusContext.Provider value={linkStatusValue}>\n <a ref={setRefs} href={fullHref} onClick={handleClick} {...anchorProps}>\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n});\n\nexport default Link;\n"],"mappings":"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"}
|
package/dist/shims/metadata.d.ts
CHANGED
|
@@ -247,6 +247,31 @@ type MetadataMergeEntry = {
|
|
|
247
247
|
* Shallow merge: later entries override earlier ones (per Next.js docs).
|
|
248
248
|
*/
|
|
249
249
|
declare function mergeMetadata(metadataList: Metadata[]): Metadata;
|
|
250
|
+
/**
|
|
251
|
+
* Post-process merged metadata to cross-fill openGraph and Twitter fields.
|
|
252
|
+
*
|
|
253
|
+
* Next.js runs this once after all layouts/pages and file-based metadata
|
|
254
|
+
* have been resolved. When openGraph exists, it auto-fills missing
|
|
255
|
+
* twitter:title/description/images from openGraph (falling back to root
|
|
256
|
+
* metadata title/description). Existing openGraph/twitter objects also inherit
|
|
257
|
+
* missing title/description from root metadata.
|
|
258
|
+
*
|
|
259
|
+
* Ported from Next.js:
|
|
260
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/resolve-metadata.ts
|
|
261
|
+
*/
|
|
262
|
+
declare function postProcessMetadata(merged: Metadata): Metadata;
|
|
263
|
+
/**
|
|
264
|
+
* Merge metadata from multiple sources (layouts + page).
|
|
265
|
+
*
|
|
266
|
+
* The list is ordered [rootLayout, nestedLayout, ..., page].
|
|
267
|
+
* Title template from layouts applies to the page title but NOT to
|
|
268
|
+
* the segment that defines the template itself. `title.absolute`
|
|
269
|
+
* skips all templates. `title.default` is the fallback when no
|
|
270
|
+
* child provides a title.
|
|
271
|
+
*
|
|
272
|
+
* For top-level keys, later entries override earlier ones. `other` custom meta
|
|
273
|
+
* tags are the exception: Next.js merges those across segments.
|
|
274
|
+
*/
|
|
250
275
|
declare function mergeMetadataEntries(entries: readonly MetadataMergeEntry[]): Metadata;
|
|
251
276
|
/**
|
|
252
277
|
* Resolve metadata from a module. Handles both static `metadata` export
|
|
@@ -265,5 +290,5 @@ declare function MetadataHead({
|
|
|
265
290
|
metadata: Metadata;
|
|
266
291
|
}): _$react_jsx_runtime0.JSX.Element;
|
|
267
292
|
//#endregion
|
|
268
|
-
export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, resolveModuleMetadata, resolveModuleViewport };
|
|
293
|
+
export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, resolveModuleMetadata, resolveModuleViewport };
|
|
269
294
|
//# sourceMappingURL=metadata.d.ts.map
|
package/dist/shims/metadata.js
CHANGED
|
@@ -73,11 +73,95 @@ function ViewportHead({ viewport }) {
|
|
|
73
73
|
* Shallow merge: later entries override earlier ones (per Next.js docs).
|
|
74
74
|
*/
|
|
75
75
|
function mergeMetadata(metadataList) {
|
|
76
|
-
return mergeMetadataEntries(metadataList.map((metadata, index) => ({
|
|
76
|
+
return postProcessMetadata(mergeMetadataEntries(metadataList.map((metadata, index) => ({
|
|
77
77
|
isPage: index === metadataList.length - 1,
|
|
78
78
|
metadata
|
|
79
|
-
})));
|
|
79
|
+
}))));
|
|
80
80
|
}
|
|
81
|
+
function isPlainObject(value) {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof URL);
|
|
83
|
+
}
|
|
84
|
+
function isOtherMetadata(value) {
|
|
85
|
+
if (!isPlainObject(value)) return false;
|
|
86
|
+
return Object.values(value).every((item) => {
|
|
87
|
+
if (typeof item === "string") return true;
|
|
88
|
+
return Array.isArray(item) && item.every((nestedItem) => typeof nestedItem === "string");
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract a plain string title from a metadata title value.
|
|
93
|
+
*/
|
|
94
|
+
function resolveStringTitle(title) {
|
|
95
|
+
if (typeof title === "string") return title;
|
|
96
|
+
if (title && typeof title === "object") return title.absolute ?? title.default ?? void 0;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Post-process merged metadata to cross-fill openGraph and Twitter fields.
|
|
100
|
+
*
|
|
101
|
+
* Next.js runs this once after all layouts/pages and file-based metadata
|
|
102
|
+
* have been resolved. When openGraph exists, it auto-fills missing
|
|
103
|
+
* twitter:title/description/images from openGraph (falling back to root
|
|
104
|
+
* metadata title/description). Existing openGraph/twitter objects also inherit
|
|
105
|
+
* missing title/description from root metadata.
|
|
106
|
+
*
|
|
107
|
+
* Ported from Next.js:
|
|
108
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/resolve-metadata.ts
|
|
109
|
+
*/
|
|
110
|
+
function postProcessMetadata(merged) {
|
|
111
|
+
const result = { ...merged };
|
|
112
|
+
const resolvedTitle = resolveStringTitle(result.title);
|
|
113
|
+
if (result.openGraph) {
|
|
114
|
+
const og = { ...result.openGraph };
|
|
115
|
+
if (!og.title && resolvedTitle) og.title = resolvedTitle;
|
|
116
|
+
if (!og.description && result.description) og.description = result.description;
|
|
117
|
+
result.openGraph = og;
|
|
118
|
+
}
|
|
119
|
+
if (result.openGraph) {
|
|
120
|
+
const autoFill = {};
|
|
121
|
+
const existingTwitter = result.twitter;
|
|
122
|
+
const hasTwTitle = existingTwitter ? Boolean(existingTwitter.title) : false;
|
|
123
|
+
const hasTwDescription = existingTwitter ? Boolean(existingTwitter.description) : false;
|
|
124
|
+
const hasTwImages = existingTwitter ? Object.prototype.hasOwnProperty.call(existingTwitter, "images") && Boolean(existingTwitter.images) : false;
|
|
125
|
+
if (!hasTwTitle) {
|
|
126
|
+
if (result.openGraph.title) autoFill.title = result.openGraph.title;
|
|
127
|
+
else if (resolvedTitle) autoFill.title = resolvedTitle;
|
|
128
|
+
}
|
|
129
|
+
if (!hasTwDescription) autoFill.description = result.openGraph.description || result.description || void 0;
|
|
130
|
+
if (!hasTwImages) autoFill.images = result.openGraph.images;
|
|
131
|
+
if (Object.keys(autoFill).length > 0) if (existingTwitter) result.twitter = {
|
|
132
|
+
...existingTwitter,
|
|
133
|
+
...autoFill
|
|
134
|
+
};
|
|
135
|
+
else result.twitter = autoFill;
|
|
136
|
+
}
|
|
137
|
+
if (result.twitter) {
|
|
138
|
+
const tw = { ...result.twitter };
|
|
139
|
+
if (!tw.title && resolvedTitle) tw.title = resolvedTitle;
|
|
140
|
+
if (!tw.description && result.description) tw.description = result.description;
|
|
141
|
+
result.twitter = tw;
|
|
142
|
+
}
|
|
143
|
+
if (result.twitter) {
|
|
144
|
+
const tw = { ...result.twitter };
|
|
145
|
+
if (!tw.card) {
|
|
146
|
+
const images = tw.images;
|
|
147
|
+
tw.card = (Array.isArray(images) ? images.length > 0 : Boolean(images)) ? "summary_large_image" : "summary";
|
|
148
|
+
}
|
|
149
|
+
result.twitter = tw;
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Merge metadata from multiple sources (layouts + page).
|
|
155
|
+
*
|
|
156
|
+
* The list is ordered [rootLayout, nestedLayout, ..., page].
|
|
157
|
+
* Title template from layouts applies to the page title but NOT to
|
|
158
|
+
* the segment that defines the template itself. `title.absolute`
|
|
159
|
+
* skips all templates. `title.default` is the fallback when no
|
|
160
|
+
* child provides a title.
|
|
161
|
+
*
|
|
162
|
+
* For top-level keys, later entries override earlier ones. `other` custom meta
|
|
163
|
+
* tags are the exception: Next.js merges those across segments.
|
|
164
|
+
*/
|
|
81
165
|
function mergeMetadataEntries(entries) {
|
|
82
166
|
if (entries.length === 0) return {};
|
|
83
167
|
const merged = {};
|
|
@@ -89,7 +173,13 @@ function mergeMetadataEntries(entries) {
|
|
|
89
173
|
if (contributesTitle && !isPage && meta.title && typeof meta.title === "object" && meta.title.template) parentTemplate = meta.title.template;
|
|
90
174
|
for (const key of Object.keys(meta)) {
|
|
91
175
|
if (key === "title") continue;
|
|
92
|
-
|
|
176
|
+
const incoming = meta[key];
|
|
177
|
+
const existing = merged[key];
|
|
178
|
+
if (key === "other" && isOtherMetadata(existing) && isOtherMetadata(incoming)) merged.other = {
|
|
179
|
+
...existing,
|
|
180
|
+
...incoming
|
|
181
|
+
};
|
|
182
|
+
else merged[key] = incoming;
|
|
93
183
|
}
|
|
94
184
|
if (contributesTitle && meta.title !== void 0) merged.title = meta.title;
|
|
95
185
|
}
|
|
@@ -587,6 +677,6 @@ function MetadataHead({ metadata }) {
|
|
|
587
677
|
return /* @__PURE__ */ jsx(Fragment$1, { children: elements });
|
|
588
678
|
}
|
|
589
679
|
//#endregion
|
|
590
|
-
export { DEFAULT_VIEWPORT, MetadataHead, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, resolveModuleMetadata, resolveModuleViewport };
|
|
680
|
+
export { DEFAULT_VIEWPORT, MetadataHead, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, resolveModuleMetadata, resolveModuleViewport };
|
|
591
681
|
|
|
592
682
|
//# sourceMappingURL=metadata.js.map
|