@wular/pnext 0.0.1
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 +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Pages-router SSR state (COMPAT). The materialized page wrappers call pagesRouterSsrState() with their
|
|
2
|
+
// pages-route pattern and resolved params; the resulting state (pattern pathname, canonical asPath,
|
|
3
|
+
// merged query) is stashed on the anchored per-request scope so the next/router shim - which SSRs the
|
|
4
|
+
// page component from a separately compiled bundle - returns the values Next's useRouter() would. The
|
|
5
|
+
// wrapper also serializes the state into a `<script type="application/json">` node so the browser-side
|
|
6
|
+
// shim reads the same values after hydration and after soft navigations.
|
|
7
|
+
|
|
8
|
+
import type { RouteParamValue } from '../../types';
|
|
9
|
+
|
|
10
|
+
export interface PagesRouterSsrState {
|
|
11
|
+
/** Pages-route pattern, e.g. `/blog/[slug]`. */
|
|
12
|
+
pathname: string;
|
|
13
|
+
/** The as-requested URL (path + original search), pre-rewrite. */
|
|
14
|
+
asPath: string;
|
|
15
|
+
/** Rewrite-destination search params merged with dynamic route params. */
|
|
16
|
+
query: Record<string, string | string[]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const STATE_KEY = Symbol.for('pnext.pagesRouterState');
|
|
20
|
+
const REQUEST_STORAGE = Symbol.for('pnext.requestStorage');
|
|
21
|
+
const WORK_UNIT_STORAGE = Symbol.for('pnext.workUnitStorage');
|
|
22
|
+
const CANONICAL_URL_KEY = Symbol.for('pnext.compat.navigation.canonicalUrl');
|
|
23
|
+
|
|
24
|
+
interface StorageLike {
|
|
25
|
+
getStore(): Record<PropertyKey, unknown> | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// All reads go through the globalThis-anchored storages (never module-local
|
|
29
|
+
// imports): the wrapper bundle, the router shim bundle, and core each carry
|
|
30
|
+
// their own compiled copy of this module, and only the anchored instances are
|
|
31
|
+
// shared between them.
|
|
32
|
+
function anchoredScope(key: symbol): Record<PropertyKey, unknown> | undefined {
|
|
33
|
+
const storage = (globalThis as Record<PropertyKey, unknown>)[key] as StorageLike | undefined;
|
|
34
|
+
return storage?.getStore?.();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function currentRequestUrl(): string | undefined {
|
|
38
|
+
const scope = anchoredScope(REQUEST_STORAGE);
|
|
39
|
+
const request = scope?.request as { url?: string } | undefined;
|
|
40
|
+
return typeof request?.url === 'string' ? request.url : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function canonicalHref(): string | undefined {
|
|
44
|
+
const unit = anchoredScope(WORK_UNIT_STORAGE);
|
|
45
|
+
const compat = unit?.compat as Record<symbol, unknown> | undefined;
|
|
46
|
+
const value = compat?.[CANONICAL_URL_KEY];
|
|
47
|
+
return typeof value === 'string' ? value : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compute and stash the pages-router state for the active render. `pattern` is the pages-route pattern
|
|
52
|
+
* the materializer knows statically; `params` the resolved dynamic params. Query mirrors Next: the
|
|
53
|
+
* (rewrite-destination) request search merged with the route params, params winning. asPath is the
|
|
54
|
+
* canonical URL the browser asked for - its own search, never a rewrite's.
|
|
55
|
+
*/
|
|
56
|
+
export function pagesRouterSsrState(
|
|
57
|
+
pattern: string,
|
|
58
|
+
params: Record<string, RouteParamValue> | undefined,
|
|
59
|
+
): PagesRouterSsrState {
|
|
60
|
+
const requestUrl = currentRequestUrl();
|
|
61
|
+
const canonical = canonicalHref() ?? requestUrl;
|
|
62
|
+
const query: Record<string, string | string[]> = {};
|
|
63
|
+
if (requestUrl) {
|
|
64
|
+
for (const [key, value] of new URL(requestUrl).searchParams) {
|
|
65
|
+
// The router-fetch cache key rides soft-navigation request URLs; it is
|
|
66
|
+
// never part of the page's query.
|
|
67
|
+
if (key !== '_rsc') query[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
71
|
+
if (value !== undefined) query[key] = value;
|
|
72
|
+
}
|
|
73
|
+
let asPath = pattern;
|
|
74
|
+
if (canonical) {
|
|
75
|
+
const url = new URL(canonical);
|
|
76
|
+
asPath = `${url.pathname}${url.search}`;
|
|
77
|
+
}
|
|
78
|
+
const state: PagesRouterSsrState = { pathname: pattern, asPath, query };
|
|
79
|
+
const scope = anchoredScope(REQUEST_STORAGE);
|
|
80
|
+
if (scope) scope[STATE_KEY] = state;
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The state stashed by the active render's page wrapper, if any. */
|
|
85
|
+
export function currentPagesRouterState(): PagesRouterSsrState | undefined {
|
|
86
|
+
const scope = anchoredScope(REQUEST_STORAGE);
|
|
87
|
+
const state = scope?.[STATE_KEY];
|
|
88
|
+
return state ? (state as PagesRouterSsrState) : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Serialize state for the inline JSON node (script-safe: `<` escaped). */
|
|
92
|
+
export function serializePagesRouterState(state: PagesRouterSsrState): string {
|
|
93
|
+
return JSON.stringify(state).replace(/</g, '\\u003c');
|
|
94
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// io() primitive (COMPAT). Marks the content AFTER `await io()` as dynamic under cacheComponents:
|
|
2
|
+
// during prerender it is a hanging promise, so awaiting it postpones the boundary and that content
|
|
3
|
+
// becomes a runtime hole; at request time it resolves so the content renders. Inside a `use cache`
|
|
4
|
+
// scope it is a no-op (the cache is filled at build, so post-io content is buildtime), and without
|
|
5
|
+
// cacheComponents it is a no-op entirely.
|
|
6
|
+
|
|
7
|
+
import { cacheComponents, hangingPromise, isPrerendering } from '../../ppr';
|
|
8
|
+
import { insideDataCacheProducer } from '../cache/revalidate';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A resolved promise pre-tagged with React's tracked-thenable state (`status: 'fulfilled'`).
|
|
12
|
+
* `React.use(io())` is a supported call site, and our use() short-circuits a fulfilled thenable instead
|
|
13
|
+
* of suspending. A plain `Promise.resolve()` has no status, so use() would throw to suspend and -
|
|
14
|
+
* because io() returns a FRESH promise on every replay and the pages render has no use()-thenable
|
|
15
|
+
* replay scope - the component would re-throw forever. `await io()` is unaffected.
|
|
16
|
+
*/
|
|
17
|
+
function resolvedIo(): Promise<void> {
|
|
18
|
+
const promise = Promise.resolve() as Promise<void> & {
|
|
19
|
+
status?: string;
|
|
20
|
+
value?: undefined;
|
|
21
|
+
};
|
|
22
|
+
promise.status = 'fulfilled';
|
|
23
|
+
promise.value = undefined;
|
|
24
|
+
return promise;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Await this to mark subsequent content dynamic. See module header for the
|
|
29
|
+
* prerender / request / use-cache / no-cacheComponents matrix.
|
|
30
|
+
*/
|
|
31
|
+
export function io(): Promise<void> {
|
|
32
|
+
if (!cacheComponents()) return resolvedIo();
|
|
33
|
+
// Inside a `use cache` / unstable_cache producer the cache boundary wins: the
|
|
34
|
+
// value is produced at build, so io() does not postpone.
|
|
35
|
+
if (insideDataCacheProducer()) return resolvedIo();
|
|
36
|
+
if (isPrerendering()) return hangingPromise<void>('io()');
|
|
37
|
+
return resolvedIo();
|
|
38
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Missing-root-params build validation (COMPAT, cacheComponents).
|
|
2
|
+
//
|
|
3
|
+
// Under cacheComponents, a ROOT param (a dynamic segment at or above a root layout) MUST be enumerated
|
|
4
|
+
// by generateStaticParams: the root layout / document shell is built ahead of the request, so its
|
|
5
|
+
// params cannot hang like an ordinary dynamic hole. A route that carries a root param with no value
|
|
6
|
+
// provided is a hard build error, with a distinct message for one vs several missing params.
|
|
7
|
+
//
|
|
8
|
+
// The message string embeds the leading `Error: ` because the CLI prints the thrown error's raw
|
|
9
|
+
// `.message`, and the Next e2e suite substring-matches it verbatim off cliOutput. Runs as a build step
|
|
10
|
+
// gated on cacheComponents, ordered before the prerender pass so the build aborts before any shell work.
|
|
11
|
+
|
|
12
|
+
import type { ResolvedConfig } from '../../config';
|
|
13
|
+
import type { RouteManifestEntry } from '../../types';
|
|
14
|
+
import { staticParamsFor } from '../../render';
|
|
15
|
+
import { PnextBuildValidationError } from '../validation';
|
|
16
|
+
import { scanRootParams } from './root-params-scan';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert a colon route pattern (`/:lang/blog/:slug`) to the bracket form Next
|
|
20
|
+
* prints in build errors (`/[lang]/blog/[slug]`). A `*`/`+` suffix marks a
|
|
21
|
+
* catch-all (`:slug*` -> `[...slug]`); a trailing `?` marks it optional
|
|
22
|
+
* (`[[...slug]]`).
|
|
23
|
+
*/
|
|
24
|
+
function toBracketPattern(pattern: string): string {
|
|
25
|
+
return pattern
|
|
26
|
+
.split('/')
|
|
27
|
+
.map(segment => {
|
|
28
|
+
if (!segment.startsWith(':')) return segment;
|
|
29
|
+
let name = segment.slice(1);
|
|
30
|
+
let optional = false;
|
|
31
|
+
let catchAll = false;
|
|
32
|
+
if (name.endsWith('?')) {
|
|
33
|
+
optional = true;
|
|
34
|
+
name = name.slice(0, -1);
|
|
35
|
+
}
|
|
36
|
+
if (name.endsWith('*') || name.endsWith('+')) {
|
|
37
|
+
catchAll = true;
|
|
38
|
+
name = name.slice(0, -1);
|
|
39
|
+
}
|
|
40
|
+
if (catchAll) return optional ? `[[...${name}]]` : `[...${name}]`;
|
|
41
|
+
return `[${name}]`;
|
|
42
|
+
})
|
|
43
|
+
.join('/');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The keys generateStaticParams actually enumerated for this route. */
|
|
47
|
+
function providedParamKeys(
|
|
48
|
+
allSets: Record<string, unknown>[],
|
|
49
|
+
): Set<string> {
|
|
50
|
+
const provided = new Set<string>();
|
|
51
|
+
for (const set of allSets) {
|
|
52
|
+
for (const [key, value] of Object.entries(set)) {
|
|
53
|
+
if (value !== undefined) provided.add(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return provided;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Throw PnextBuildValidationError for the first page route that carries a root
|
|
61
|
+
* param with no generateStaticParams value. Root params are validated in route
|
|
62
|
+
* order (route.params order), so the message lists them the way they appear in
|
|
63
|
+
* the path pattern. No-op when the app defines no root params.
|
|
64
|
+
*/
|
|
65
|
+
export async function validateRootParamsProvided(
|
|
66
|
+
config: ResolvedConfig,
|
|
67
|
+
routes: RouteManifestEntry[],
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const rootParams = scanRootParams(config.appPath);
|
|
70
|
+
if (rootParams.size === 0) return;
|
|
71
|
+
|
|
72
|
+
const pageRoutes = routes.filter(
|
|
73
|
+
route => route.kind === 'page' && !route.interception,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
for (const route of pageRoutes) {
|
|
77
|
+
const routeRootParams = route.params.filter(name => rootParams.has(name));
|
|
78
|
+
if (routeRootParams.length === 0) continue;
|
|
79
|
+
|
|
80
|
+
let provided: Set<string>;
|
|
81
|
+
try {
|
|
82
|
+
const staticParams = await staticParamsFor(config, route);
|
|
83
|
+
provided = providedParamKeys(staticParams.allSets);
|
|
84
|
+
} catch {
|
|
85
|
+
// A generateStaticParams that throws provides nothing — treat every root
|
|
86
|
+
// param as missing rather than masking the (more relevant) config error.
|
|
87
|
+
provided = new Set<string>();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const missing = routeRootParams.filter(name => !provided.has(name));
|
|
91
|
+
if (missing.length === 0) continue;
|
|
92
|
+
|
|
93
|
+
const routePattern = toBracketPattern(route.route || '/');
|
|
94
|
+
if (missing.length === 1) {
|
|
95
|
+
throw new PnextBuildValidationError(
|
|
96
|
+
`Error: A required root parameter (${missing[0]}) was not provided in ` +
|
|
97
|
+
`generateStaticParams for ${routePattern}, please provide at least one value.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
throw new PnextBuildValidationError(
|
|
101
|
+
`Error: Required root params (${missing.join(', ')}) were not provided in ` +
|
|
102
|
+
`generateStaticParams for ${routePattern}, please provide at least one value for each.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Root-params scan (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// A ROOT param is a dynamic segment that sits at or above a ROOT LAYOUT (a layout that renders
|
|
4
|
+
// `<html>`). For `app/[lang]/[locale]/layout.tsx` as the root layout, both `lang` and `locale` are root
|
|
5
|
+
// params. With multiple root layouts (route groups each owning their own `<html>`), the root-param set
|
|
6
|
+
// is per-root-layout - a name is a root param when SOME root layout has it at or above its directory.
|
|
7
|
+
//
|
|
8
|
+
// The scan drives which names `next/root-params` legitimately exports (import validation) and typegen.
|
|
9
|
+
// Route-group and slot dirs never contribute params; catch-all segments contribute their param name.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
const LAYOUT_BASENAMES = ['layout.tsx', 'layout.ts', 'layout.jsx', 'layout.js'];
|
|
15
|
+
|
|
16
|
+
/** True when a layout source renders `<html>` (i.e. it is a root/document layout). */
|
|
17
|
+
function isRootLayoutSource(source: string): boolean {
|
|
18
|
+
return /<html[\s>]/.test(source);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Extract the param name from a segment dir name, or undefined for a static one. */
|
|
22
|
+
function paramNameOfSegment(segment: string): string | undefined {
|
|
23
|
+
// [[...slug]] optional catch-all, [...slug] catch-all, [slug] dynamic.
|
|
24
|
+
const optional = /^\[\[\.\.\.(.+)\]\]$/.exec(segment);
|
|
25
|
+
if (optional) return optional[1];
|
|
26
|
+
const catchAll = /^\[\.\.\.(.+)\]$/.exec(segment);
|
|
27
|
+
if (catchAll) return catchAll[1];
|
|
28
|
+
const dynamic = /^\[(.+)\]$/.exec(segment);
|
|
29
|
+
if (dynamic) return dynamic[1];
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findLayoutFile(dir: string): string | undefined {
|
|
34
|
+
for (const base of LAYOUT_BASENAMES) {
|
|
35
|
+
const candidate = path.join(dir, base);
|
|
36
|
+
if (existsSync(candidate)) return candidate;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Walk `app/` collecting, for every root layout, the param names on the path
|
|
43
|
+
* from app root down to (and including) that layout's directory. The union is
|
|
44
|
+
* the app's root-param set.
|
|
45
|
+
*/
|
|
46
|
+
export function scanRootParams(appPath: string): Set<string> {
|
|
47
|
+
const rootParams = new Set<string>();
|
|
48
|
+
if (!existsSync(appPath)) return rootParams;
|
|
49
|
+
|
|
50
|
+
const walk = (dir: string, paramsAbove: string[]): void => {
|
|
51
|
+
const layoutFile = findLayoutFile(dir);
|
|
52
|
+
if (layoutFile) {
|
|
53
|
+
let source = '';
|
|
54
|
+
try {
|
|
55
|
+
source = readFileSync(layoutFile, 'utf8');
|
|
56
|
+
} catch {
|
|
57
|
+
source = '';
|
|
58
|
+
}
|
|
59
|
+
if (isRootLayoutSource(source)) {
|
|
60
|
+
for (const name of paramsAbove) rootParams.add(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let children: string[] = [];
|
|
64
|
+
try {
|
|
65
|
+
children = readdirSync(dir);
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const child of children) {
|
|
70
|
+
const childPath = path.join(dir, child);
|
|
71
|
+
let isDir = false;
|
|
72
|
+
try {
|
|
73
|
+
isDir = statSync(childPath).isDirectory();
|
|
74
|
+
} catch {
|
|
75
|
+
isDir = false;
|
|
76
|
+
}
|
|
77
|
+
if (!isDir) continue;
|
|
78
|
+
if (child.startsWith('@')) continue; // parallel-route slot dir
|
|
79
|
+
const param = paramNameOfSegment(child);
|
|
80
|
+
walk(childPath, param ? [...paramsAbove, param] : paramsAbove);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
walk(appPath, []);
|
|
85
|
+
return rootParams;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type RootParamValueType = 'string' | 'string[]' | 'undefined';
|
|
89
|
+
|
|
90
|
+
interface ScannedParam {
|
|
91
|
+
name: string;
|
|
92
|
+
segment: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Per-param value types for root-params typegen, mirroring Next's collection:
|
|
97
|
+
* `[name]` contributes `string`, `[...name]` contributes `string[]`,
|
|
98
|
+
* `[[...name]]` contributes `string[]` and `undefined`. With multiple root
|
|
99
|
+
* layouts, a param absent from some root also gains `undefined`.
|
|
100
|
+
*/
|
|
101
|
+
export function scanRootParamTypes(appPath: string): Map<string, Set<RootParamValueType>> {
|
|
102
|
+
const roots: Map<string, RootParamValueType[]>[] = [];
|
|
103
|
+
if (!existsSync(appPath)) return new Map();
|
|
104
|
+
|
|
105
|
+
const walk = (dir: string, paramsAbove: ScannedParam[]): void => {
|
|
106
|
+
const layoutFile = findLayoutFile(dir);
|
|
107
|
+
if (layoutFile) {
|
|
108
|
+
let source = '';
|
|
109
|
+
try {
|
|
110
|
+
source = readFileSync(layoutFile, 'utf8');
|
|
111
|
+
} catch {
|
|
112
|
+
source = '';
|
|
113
|
+
}
|
|
114
|
+
if (isRootLayoutSource(source)) {
|
|
115
|
+
const params = new Map<string, RootParamValueType[]>();
|
|
116
|
+
for (const { name, segment } of paramsAbove) {
|
|
117
|
+
params.set(
|
|
118
|
+
name,
|
|
119
|
+
segment.startsWith('[[...')
|
|
120
|
+
? ['string[]', 'undefined']
|
|
121
|
+
: segment.startsWith('[...')
|
|
122
|
+
? ['string[]']
|
|
123
|
+
: ['string'],
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
roots.push(params);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let children: string[] = [];
|
|
130
|
+
try {
|
|
131
|
+
children = readdirSync(dir);
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const child of children) {
|
|
136
|
+
const childPath = path.join(dir, child);
|
|
137
|
+
let isDir = false;
|
|
138
|
+
try {
|
|
139
|
+
isDir = statSync(childPath).isDirectory();
|
|
140
|
+
} catch {
|
|
141
|
+
isDir = false;
|
|
142
|
+
}
|
|
143
|
+
if (!isDir) continue;
|
|
144
|
+
if (child.startsWith('@')) continue; // parallel-route slot dir
|
|
145
|
+
const param = paramNameOfSegment(child);
|
|
146
|
+
walk(childPath, param ? [...paramsAbove, { name: param, segment: child }] : paramsAbove);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
walk(appPath, []);
|
|
151
|
+
|
|
152
|
+
const combined = new Map<string, Set<RootParamValueType>>();
|
|
153
|
+
for (const params of roots) {
|
|
154
|
+
for (const [name, types] of params) {
|
|
155
|
+
const set = combined.get(name) ?? new Set<RootParamValueType>();
|
|
156
|
+
for (const type of types) set.add(type);
|
|
157
|
+
combined.set(name, set);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const [name, set] of combined) {
|
|
161
|
+
if (roots.some(params => !params.has(name))) set.add('undefined');
|
|
162
|
+
}
|
|
163
|
+
return combined;
|
|
164
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// next/root-params import rewrite (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// `next/root-params` exports one async getter per root param. The set of names is app-specific, so a
|
|
4
|
+
// static alias module cannot list them as named exports - a bare `import { lang } from
|
|
5
|
+
// 'next/root-params'` would fail at bundle time with "no matching export". This transform rewrites such
|
|
6
|
+
// imports into named bindings pulled off the module's default Proxy, which resolves any getter:
|
|
7
|
+
//
|
|
8
|
+
// import { lang, locale as l } from 'next/root-params';
|
|
9
|
+
// -> import __pnextRootParams from 'next/root-params';
|
|
10
|
+
// const lang = __pnextRootParams.lang;
|
|
11
|
+
// const l = __pnextRootParams.locale;
|
|
12
|
+
//
|
|
13
|
+
// The alias map still resolves the specifier to compat/next/root-params.ts, so the default import binds
|
|
14
|
+
// the Proxy. Build-time validation that a name is actually a root param lives in root-params-scan.ts;
|
|
15
|
+
// this transform stays permissive.
|
|
16
|
+
|
|
17
|
+
import { isIdentifier, uniqueIdentifier } from '../../utils/source';
|
|
18
|
+
|
|
19
|
+
const ROOT_PARAMS_SPECIFIER = /['"]next\/root-params['"]/;
|
|
20
|
+
|
|
21
|
+
// `[^}]` for the clause, never a lazy `[\s\S]*?`: the latter backtracks across
|
|
22
|
+
// an earlier `import { X } from 'react'` and swallows that line too.
|
|
23
|
+
const IMPORT_RE =
|
|
24
|
+
/^[ \t]*import\s+\{([^}]*)\}\s+from\s+['"]next\/root-params['"]\s*;?[ \t]*(?:\r?\n)?/gm;
|
|
25
|
+
|
|
26
|
+
interface Binding {
|
|
27
|
+
imported: string;
|
|
28
|
+
local: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseBindings(clause: string): Binding[] {
|
|
32
|
+
return clause
|
|
33
|
+
.split(',')
|
|
34
|
+
.map(part => part.trim())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.flatMap(part => {
|
|
37
|
+
if (part.startsWith('type ')) return [];
|
|
38
|
+
const [importedRaw, localRaw] = part.split(/\s+as\s+/);
|
|
39
|
+
const imported = importedRaw?.trim();
|
|
40
|
+
const local = (localRaw ?? importedRaw)?.trim();
|
|
41
|
+
if (!imported || !local || !isIdentifier(imported) || !isIdentifier(local)) return [];
|
|
42
|
+
return [{ imported, local }];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Rewrite `next/root-params` named imports to default-Proxy member reads.
|
|
48
|
+
* A no-op for sources that do not import the module. Registered as a server
|
|
49
|
+
* source transform and applied in the client bundler plugin.
|
|
50
|
+
*/
|
|
51
|
+
export function rewriteRootParamsSource(source: string): string {
|
|
52
|
+
if (!ROOT_PARAMS_SPECIFIER.test(source)) return source;
|
|
53
|
+
const proxyName = uniqueIdentifier(source, '__pnextRootParams');
|
|
54
|
+
const declarations: string[] = [];
|
|
55
|
+
let importedProxy = false;
|
|
56
|
+
|
|
57
|
+
const rewritten = source.replace(IMPORT_RE, (_statement, clause: string) => {
|
|
58
|
+
const bindings = parseBindings(clause);
|
|
59
|
+
if (bindings.length === 0) return '';
|
|
60
|
+
importedProxy = true;
|
|
61
|
+
for (const { imported, local } of bindings) {
|
|
62
|
+
declarations.push(
|
|
63
|
+
`const ${local} = ${proxyName}[${JSON.stringify(imported)}];`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return '';
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!importedProxy) return source;
|
|
70
|
+
return [
|
|
71
|
+
`import ${proxyName} from ${JSON.stringify('next/root-params')};`,
|
|
72
|
+
...declarations,
|
|
73
|
+
rewritten,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// next/root-params runtime (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// `next/root-params` exports one async getter per ROOT param. Since the getter NAMES do not affect the
|
|
4
|
+
// runtime (each getter just reads its own param off the request ALS param store), a single Proxy-backed
|
|
5
|
+
// module satisfies every app.
|
|
6
|
+
//
|
|
7
|
+
// The build-time scan validates that only real root params are imported - importing a non-root name is
|
|
8
|
+
// a build/dev error there - so this runtime is intentionally permissive and a valid getter always works.
|
|
9
|
+
// Context errors (server action / route handler / unstable_cache) match Next's messages exactly.
|
|
10
|
+
|
|
11
|
+
import { currentParams, currentRequest, getWorkUnit } from '../../request/context';
|
|
12
|
+
import {
|
|
13
|
+
cacheComponents,
|
|
14
|
+
hangingPromise,
|
|
15
|
+
isPrerendering,
|
|
16
|
+
isRuntimePrefetchPrerender,
|
|
17
|
+
} from '../../ppr';
|
|
18
|
+
import { insideUnstableCacheProducer } from '../cache/revalidate';
|
|
19
|
+
import { insideUseCacheProducer, recordUseCacheRootParamRead } from '../cache/use-cache';
|
|
20
|
+
|
|
21
|
+
/** E-code errors Next throws for root-params used in an unsupported context. */
|
|
22
|
+
function rootParamContextError(name: string): Error | undefined {
|
|
23
|
+
const unit = getWorkUnit();
|
|
24
|
+
const call = `\`import('next/root-params').${name}()\``;
|
|
25
|
+
|
|
26
|
+
const inUseCache = insideUseCacheProducer();
|
|
27
|
+
const inUnstableCache = insideUnstableCacheProducer();
|
|
28
|
+
|
|
29
|
+
// Root params inside a `use cache` NESTED within `unstable_cache`: E1140.
|
|
30
|
+
// (The `use cache` scope sits inside the unstable_cache producer, so both
|
|
31
|
+
// markers are active.) Root params are not available in that context.
|
|
32
|
+
if (inUseCache && inUnstableCache) {
|
|
33
|
+
return Object.assign(
|
|
34
|
+
new Error(
|
|
35
|
+
`Root params (${call}) were used inside a cache scope nested within \`unstable_cache\`. Root params are not available in this context.`,
|
|
36
|
+
),
|
|
37
|
+
{ digest: 'E1140' },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inside a plain `use cache` (not under unstable_cache): legal. Record the
|
|
42
|
+
// read so the entry's cache key participates in this root param (Stage B-3),
|
|
43
|
+
// then let the getter resolve the value below (no error).
|
|
44
|
+
if (inUseCache) {
|
|
45
|
+
recordUseCacheRootParamRead(name);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Inside `unstable_cache` (a data-cache producer that is NOT a `use cache`
|
|
50
|
+
// scope): E1141.
|
|
51
|
+
if (inUnstableCache) {
|
|
52
|
+
return Object.assign(
|
|
53
|
+
new Error(
|
|
54
|
+
`Root params (${call}) were used inside \`unstable_cache\`. This is not supported. Use \`"use cache"\` instead.`,
|
|
55
|
+
),
|
|
56
|
+
{ digest: 'E1141' },
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!unit) return undefined;
|
|
61
|
+
|
|
62
|
+
if (unit.phase === 'action') {
|
|
63
|
+
return new Error(
|
|
64
|
+
`${call} was used inside a Server Action. This is not supported. ` +
|
|
65
|
+
`Functions from 'next/root-params' can only be called in the context of a route.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Route handlers are detected via routeKind (core sets no 'handler' phase).
|
|
70
|
+
if (unit.phase !== 'after' && unit.routeKind === 'route-handler') {
|
|
71
|
+
const route = (() => {
|
|
72
|
+
try {
|
|
73
|
+
return new URL(currentRequest()?.url ?? '').pathname;
|
|
74
|
+
} catch {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return new Error(
|
|
79
|
+
`Route ${route} used ${call} inside a Route Handler. ` +
|
|
80
|
+
`Support for this API in Route Handlers is planned for a future version of Next.js.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read a single root param off the request param store. Async (matching Next's
|
|
89
|
+
* getter shape). A catch-all resolves to an array; an absent optional catch-all
|
|
90
|
+
* resolves to undefined.
|
|
91
|
+
*/
|
|
92
|
+
export function rootParam<T = string | string[] | undefined>(name: string): Promise<T> {
|
|
93
|
+
const error = rootParamContextError(name);
|
|
94
|
+
if (error) return Promise.reject(error);
|
|
95
|
+
// A runtime-prefetch prerender renders against the REAL sampled request, so root params are concrete
|
|
96
|
+
// there (root params are always available in static prerenders, so a runtime prefetch has them too).
|
|
97
|
+
// Hanging them would postpone the root layout itself - the whole document, not a boundary - and the
|
|
98
|
+
// render would produce no shell at all.
|
|
99
|
+
if (
|
|
100
|
+
!insideUseCacheProducer() &&
|
|
101
|
+
cacheComponents() &&
|
|
102
|
+
isPrerendering() &&
|
|
103
|
+
!isRuntimePrefetchPrerender()
|
|
104
|
+
) {
|
|
105
|
+
return hangingPromise<T>(`rootParam(${name})`);
|
|
106
|
+
}
|
|
107
|
+
const params = currentParams();
|
|
108
|
+
return Promise.resolve(params[name] as T);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The aliased module surface: a Proxy whose every string property is an async
|
|
113
|
+
* getter for that root param. `import { lang } from 'next/root-params'` picks up
|
|
114
|
+
* `rootParam('lang')`. Non-root imports are rejected at build (the scan), so
|
|
115
|
+
* this Proxy never needs to distinguish them at runtime.
|
|
116
|
+
*/
|
|
117
|
+
const rootParamsModule = new Proxy(
|
|
118
|
+
{},
|
|
119
|
+
{
|
|
120
|
+
get(_target, prop: string | symbol): unknown {
|
|
121
|
+
if (typeof prop !== 'string') return undefined;
|
|
122
|
+
if (prop === '__esModule') return true;
|
|
123
|
+
if (prop === 'default') return undefined;
|
|
124
|
+
return () => rootParam(prop);
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
) as Record<string, () => Promise<unknown>>;
|
|
128
|
+
|
|
129
|
+
export default rootParamsModule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Legacy segment-config incompatibility under cacheComponents (COMPAT). Under `cacheComponents: true`
|
|
2
|
+
// the legacy per-segment route config exports are incompatible and must error at build. This module
|
|
3
|
+
// produces the exact Next message for a given offending key and file path; it is delivered ready-to-wire
|
|
4
|
+
// rather than wired into build validation here.
|
|
5
|
+
|
|
6
|
+
/** The legacy segment-config exports that cacheComponents rejects. */
|
|
7
|
+
export const INCOMPATIBLE_SEGMENT_KEYS = [
|
|
8
|
+
'revalidate',
|
|
9
|
+
'dynamicParams',
|
|
10
|
+
'dynamic',
|
|
11
|
+
'fetchCache',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type IncompatibleSegmentKey = (typeof INCOMPATIBLE_SEGMENT_KEYS)[number];
|
|
15
|
+
|
|
16
|
+
/** Next's exact incompat message for an offending segment-config export. */
|
|
17
|
+
export function segmentConfigIncompatMessage(key: IncompatibleSegmentKey, file: string): string {
|
|
18
|
+
return `"${key}" is not compatible with \`nextConfig.cacheComponents\`. Please remove it. Used in ${file}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const EXPORT_RE = (key: string) =>
|
|
22
|
+
new RegExp(`\\bexport\\s+const\\s+${key}\\b`);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scan a page/layout source for an incompatible segment-config export, returning
|
|
26
|
+
* the first offending key (or undefined). Layout configs propagate to child
|
|
27
|
+
* pages, so callers should scan the layout chain too.
|
|
28
|
+
*/
|
|
29
|
+
export function findIncompatibleSegmentKey(source: string): IncompatibleSegmentKey | undefined {
|
|
30
|
+
for (const key of INCOMPATIBLE_SEGMENT_KEYS) {
|
|
31
|
+
if (EXPORT_RE(key).test(source)) return key;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|