@wular/pnext 0.0.4 → 0.0.7
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 +22 -16
- package/bin/pnext +6 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +87 -22
- package/reference/config.md +40 -93
- package/reference/css.md +42 -33
- package/reference/dev.md +26 -33
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +31 -46
- package/reference/navigation.md +65 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +78 -75
- package/reference/routing.md +97 -107
- package/reference/typegen.md +27 -29
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +121 -114
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +52 -53
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +72 -64
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +94 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +802 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +32 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +2022 -1995
- package/src/{api → client}/router/types.ts +98 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +86 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2087 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +87 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +954 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +428 -406
- package/src/styles.d.ts +9 -0
- package/src/types.ts +324 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/reference/overview.md +0 -35
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -8,16 +8,13 @@
|
|
|
8
8
|
// - bundlerExtensions.clientEsbuildPlugins <- action client-stub plugin
|
|
9
9
|
//
|
|
10
10
|
// The request interceptors read the live per-server routing state core publishes via
|
|
11
|
-
// routing/request-
|
|
11
|
+
// routing/request-environment.ts and call core render/import helpers directly (compat to core is allowed),
|
|
12
12
|
// so the thin `{ config }` interceptor context stays unchanged.
|
|
13
|
+
//
|
|
14
|
+
// The implementation (build step, interceptors, render extension, dev/prod registry arming) lives in
|
|
15
|
+
// ../actions/extensions; this module just wires it into the extension registries.
|
|
13
16
|
|
|
14
|
-
import
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import type { ResolvedConfig } from '../../config';
|
|
19
|
-
import { pathToFileHref } from '../../config';
|
|
20
|
-
import { nextCompatEnabled } from '../../compat';
|
|
17
|
+
import type { ResolvedConfig } from '../../config'
|
|
21
18
|
import {
|
|
22
19
|
registerBuildSteps,
|
|
23
20
|
registerInitHooks,
|
|
@@ -27,97 +24,48 @@ import {
|
|
|
27
24
|
setBundlerExtensions,
|
|
28
25
|
setRenderExtensions,
|
|
29
26
|
withSniff,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type ServerActionPropContext,
|
|
35
|
-
} from '../../extensions';
|
|
36
|
-
import { setActionModuleExtensions } from '../../render/hooks';
|
|
37
|
-
import { getRequestRuntime, type RequestRuntime } from '../../routing/request-runtime';
|
|
38
|
-
import { setClientActionBundler } from '../../dev/client-actions';
|
|
39
|
-
import { toPosixPath } from '../../utils/fs';
|
|
40
|
-
import type { ActionManifestEntry, BuildManifest, RouteManifestEntry } from '../../types';
|
|
41
|
-
import { devServerModuleHref } from '../../dev/imports';
|
|
42
|
-
import { serverBundleTargetForRuntime } from '../../runtime/server';
|
|
43
|
-
import {
|
|
44
|
-
renderActionReturnElement,
|
|
45
|
-
renderGlobalNotFoundResponse,
|
|
46
|
-
renderNotFoundForRoute,
|
|
47
|
-
renderPage,
|
|
48
|
-
renderPageResponse,
|
|
49
|
-
} from '../../render';
|
|
50
|
-
import { routeParamsFromPath } from '../../routing/handler';
|
|
51
|
-
import { selectRouteForRequest } from '../../routing/routes';
|
|
52
|
-
import { discoverActions, registerDiscoveredActions } from '../actions/discovery';
|
|
53
|
-
import { clearActions, lookupActionSource, registerActionModule } from '../actions/registry';
|
|
27
|
+
} from '../../extensions'
|
|
28
|
+
import { setActionModuleExtensions } from '../../render/hooks'
|
|
29
|
+
import { getRequestRuntime } from '../../routing/request-environment'
|
|
30
|
+
import { setClientActionBundler } from '../../dev/client-actions'
|
|
54
31
|
import {
|
|
55
32
|
actionClientStubPlugin,
|
|
56
33
|
actionStubSourceFor,
|
|
57
|
-
canonical,
|
|
58
|
-
canonicalActionId,
|
|
59
|
-
clearClientActionModules,
|
|
60
|
-
clientActionModuleFiles,
|
|
61
34
|
clientActionModulesRoot,
|
|
62
35
|
isClientActionModule,
|
|
63
|
-
|
|
64
|
-
} from '../actions/
|
|
65
|
-
import {
|
|
66
|
-
|
|
67
|
-
isProgressiveActionRequest,
|
|
68
|
-
serveAction,
|
|
69
|
-
type ServeActionOptions,
|
|
70
|
-
} from '../actions/serve';
|
|
36
|
+
} from '../actions/client-plugin'
|
|
37
|
+
import { validateServerActionsConfigSync } from '../actions/config'
|
|
38
|
+
import { hoistNonCapturingInlineActions } from '../actions/hoist'
|
|
39
|
+
import { rewriteAnonymousInlineActionMarks, rewriteInlineActionTags } from '../actions/rewrite'
|
|
71
40
|
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import { canonicalUrlHref, recordCanonicalUrl } from '../next/canonical-url';
|
|
82
|
-
import {
|
|
83
|
-
assignedInlineActionId,
|
|
84
|
-
createInstanceScope,
|
|
85
|
-
instanceActionRouteId,
|
|
86
|
-
isInstanceActionId,
|
|
87
|
-
isMarkedInlineAction,
|
|
88
|
-
overriddenActionId,
|
|
89
|
-
registerActionInstance,
|
|
90
|
-
type InstanceScope,
|
|
91
|
-
} from '../actions/instances';
|
|
92
|
-
import { serverActionId, tagServerAction } from '../actions/server-tag';
|
|
93
|
-
import { opensWithUseServerDirective } from '../actions/detect';
|
|
94
|
-
import { hoistNonCapturingInlineActions } from '../actions/hoist';
|
|
95
|
-
import {
|
|
96
|
-
inlineActionModuleKey,
|
|
97
|
-
isInlineActionId,
|
|
98
|
-
rewriteAnonymousInlineActionMarks,
|
|
99
|
-
rewriteInlineActionTags,
|
|
100
|
-
} from '../actions/rewrite';
|
|
101
|
-
import { reportActionErrorToUser } from '../lifecycle/error-funnel';
|
|
41
|
+
actionDispatchInterceptor,
|
|
42
|
+
ensureActionsRegistered,
|
|
43
|
+
redirectInterceptor,
|
|
44
|
+
rewriteInterceptor,
|
|
45
|
+
runActionBuildStep,
|
|
46
|
+
serializeServerActionProp,
|
|
47
|
+
tagActionModuleExports,
|
|
48
|
+
tagActionModulesForRender,
|
|
49
|
+
} from '../actions/extensions'
|
|
102
50
|
|
|
103
51
|
export function registerActionExtensions(_config: ResolvedConfig): void {
|
|
104
52
|
// Discovery reads config + source text only, so it runs under the route-facts
|
|
105
53
|
// scan; the client bundle is the one thing that waits on its stub set.
|
|
106
|
-
runActionBuildStep.early = true
|
|
107
|
-
registerBuildSteps(runActionBuildStep)
|
|
54
|
+
runActionBuildStep.early = true
|
|
55
|
+
registerBuildSteps(runActionBuildStep)
|
|
108
56
|
// Validate serverActions.bodySizeLimit at build + start startup (Next fails
|
|
109
57
|
// the CLI with the exact size-limit error before serving), rather than only
|
|
110
58
|
// lazily on the first action request.
|
|
111
|
-
registerInitHooks(config => validateServerActionsConfigSync(config.root))
|
|
59
|
+
registerInitHooks(config => validateServerActionsConfigSync(config.root))
|
|
112
60
|
// Ordering matches the old servers: action dispatch first (on the requested
|
|
113
61
|
// pathname), then next.config redirects (short-circuit before routing), then
|
|
114
62
|
// rewrites swap the request for downstream matching / static-lookup /
|
|
115
63
|
// staleness.
|
|
116
|
-
registerRequestInterceptors(actionDispatchInterceptor, redirectInterceptor, rewriteInterceptor)
|
|
64
|
+
registerRequestInterceptors(actionDispatchInterceptor, redirectInterceptor, rewriteInterceptor)
|
|
117
65
|
// The same two rules also serve requests from outside the basePath, where only
|
|
118
66
|
// their `basePath: false` entries apply (both interceptors partition their
|
|
119
67
|
// rules on ctx.outsideBasePath).
|
|
120
|
-
registerOutsideBasePathInterceptors(redirectInterceptor, rewriteInterceptor)
|
|
68
|
+
registerOutsideBasePathInterceptors(redirectInterceptor, rewriteInterceptor)
|
|
121
69
|
// Tag MODULE-SCOPE inline 'use server' functions with React-compatible `$$id` metadata at module-eval
|
|
122
70
|
// time. Without this, top-level inline actions that module-scope code introspects - Next's version-skew
|
|
123
71
|
// fixtures read and override `fn.$$id` - throw at import. Runs on the server graph only; module-level
|
|
@@ -126,770 +74,33 @@ export function registerActionExtensions(_config: ResolvedConfig): void {
|
|
|
126
74
|
// client handler. Hoisting runs FIRST: it lifts capture-free anonymous inline actions to module-scope
|
|
127
75
|
// declarations, which the tag pass then gives stable `if:` ids, taking the whole-route recovery render
|
|
128
76
|
// off the action's critical path for pages served from prebuilt HTML.
|
|
129
|
-
const inlineActionSniff = ['use server']
|
|
77
|
+
const inlineActionSniff = ['use server']
|
|
130
78
|
registerServerSourceTransforms(
|
|
131
79
|
withSniff(inlineActionSniff, source => hoistNonCapturingInlineActions(source)),
|
|
132
80
|
withSniff(inlineActionSniff, (source, file, root) =>
|
|
133
81
|
rewriteInlineActionTags(source, file, root),
|
|
134
82
|
),
|
|
135
83
|
withSniff(inlineActionSniff, source => rewriteAnonymousInlineActionMarks(source)),
|
|
136
|
-
)
|
|
137
|
-
setRenderExtensions({ serializeServerActionProp })
|
|
84
|
+
)
|
|
85
|
+
setRenderExtensions({ serializeServerActionProp })
|
|
138
86
|
// Tag imported action-module function exports with their stable wire id so a
|
|
139
87
|
// <form action={serverAction}> imported straight into a page (same module
|
|
140
88
|
// singleton) serializes to that module id instead of a per-render inline id.
|
|
141
|
-
setActionModuleExtensions({ tagActionModuleExports, tagActionModulesForRender })
|
|
89
|
+
setActionModuleExtensions({ tagActionModuleExports, tagActionModulesForRender })
|
|
142
90
|
// Prod/plain client builds swap 'use server' imports for the RPC stub via
|
|
143
91
|
// the standalone esbuild plugin (client/build.ts appends these). No-op for
|
|
144
92
|
// pure-core / non-next apps.
|
|
145
|
-
setBundlerExtensions({ clientEsbuildPlugins: () => [actionClientStubPlugin()] })
|
|
93
|
+
setBundlerExtensions({ clientEsbuildPlugins: () => [actionClientStubPlugin()] })
|
|
146
94
|
// The dev route-client bundle resolves modules into its own esbuild namespace
|
|
147
95
|
// that a standalone plugin can't intercept, so dev/imports reads this holder
|
|
148
96
|
// instead. ensureArmed runs the discovery dev startup/reload used to run
|
|
149
97
|
// inline, lazily per dev import version.
|
|
150
98
|
setClientActionBundler({
|
|
151
99
|
ensureArmed: async () => {
|
|
152
|
-
const runtime = getRequestRuntime()
|
|
153
|
-
if (runtime?.dev) await ensureActionsRegistered(runtime)
|
|
100
|
+
const runtime = getRequestRuntime()
|
|
101
|
+
if (runtime?.dev) await ensureActionsRegistered(runtime)
|
|
154
102
|
},
|
|
155
103
|
isClientActionModule,
|
|
156
104
|
stubSource: file => actionStubSourceFor(file, clientActionModulesRoot() ?? undefined),
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Build step: action discovery/bundling + server-reference manifest.
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
interface ActionStepState {
|
|
165
|
-
actions: ActionManifestEntry[];
|
|
166
|
-
actionSources?: string[];
|
|
167
|
-
actionImporters?: string[];
|
|
168
|
-
deferred?: Promise<void>;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const runActionBuildStep: BuildStep = async (ctx: BuildStepContext): Promise<void> => {
|
|
172
|
-
const config = ctx.config;
|
|
173
|
-
const state = ctx.manifest as ActionStepState;
|
|
174
|
-
// Discovery is what arms the client stub set and names the action source
|
|
175
|
-
// files, and it is the only part of this step the client stage waits on.
|
|
176
|
-
const discovery = await (ctx.log
|
|
177
|
-
? ctx.log.step('action discovery', () => discoverActions(config))
|
|
178
|
-
: discoverActions(config));
|
|
179
|
-
state.actionSources = actionSourceKeys(config, discovery);
|
|
180
|
-
state.actionImporters = [...discovery.actionImporters];
|
|
181
|
-
// Compiling those modules to server bundles produces manifest entries nothing
|
|
182
|
-
// upstream of the build manifest reads, so it is handed back to run under the
|
|
183
|
-
// client stage instead of ahead of it.
|
|
184
|
-
state.deferred = (async () => {
|
|
185
|
-
const actions = await compileActionModules(config, discovery, ctx.log);
|
|
186
|
-
// The step's `manifest` accumulator carries the actions array the core build
|
|
187
|
-
// folds into the build manifest (manifest.actions) verbatim.
|
|
188
|
-
state.actions = actions;
|
|
189
|
-
if (actions.length > 0) await writeServerReferenceManifest(config, actions);
|
|
190
|
-
})();
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Discover 'use server' modules, compile each to a server bundle (compat
|
|
195
|
-
* aliases + directive handling applied, like the proxy module), and produce the
|
|
196
|
-
* action manifest start serves from. Also arms the client-bundle stub set so
|
|
197
|
-
* client imports of these modules become the RPC stub during buildClientEntries.
|
|
198
|
-
*
|
|
199
|
-
* The wire id is computed from the SOURCE file path (config.root-relative), so
|
|
200
|
-
* the client stub and the server registry agree; modulePath is the COMPILED
|
|
201
|
-
* output the endpoint imports at request time.
|
|
202
|
-
*/
|
|
203
|
-
function actionSourceKeys(
|
|
204
|
-
config: ResolvedConfig,
|
|
205
|
-
discovery: Awaited<ReturnType<typeof discoverActions>>,
|
|
206
|
-
): string[] {
|
|
207
|
-
clearActions();
|
|
208
|
-
if (discovery.modules.length === 0) {
|
|
209
|
-
setClientActionModules(config.root, []);
|
|
210
|
-
return [];
|
|
211
|
-
}
|
|
212
|
-
// Client bundling replaces these source imports with the stub.
|
|
213
|
-
setClientActionModules(config.root, discovery.actionFiles);
|
|
214
|
-
// Registered from the discovery facts alone (ids come from source paths), so
|
|
215
|
-
// build-time lookupAction parity holds before the compile below finishes.
|
|
216
|
-
registerDiscoveredActions(config, discovery);
|
|
217
|
-
return discovery.modules.map(module => actionSourceKey(config, module.file));
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** The registry id's source key: canonical, root-relative, POSIX. */
|
|
221
|
-
function actionSourceKey(config: ResolvedConfig, file: string) {
|
|
222
|
-
// Must reproduce the registry id at startup: registerManifestActions calls
|
|
223
|
-
// actionId(sourceKey) with no root, so this has to equal
|
|
224
|
-
// workspaceRelative(canonical(file), canonical(root)).
|
|
225
|
-
return toPosixPath(path.relative(canonical(config.root), canonical(file)));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function compileActionModules(
|
|
229
|
-
config: ResolvedConfig,
|
|
230
|
-
discovery: Awaited<ReturnType<typeof discoverActions>>,
|
|
231
|
-
log?: BuildStepContext['log'],
|
|
232
|
-
): Promise<ActionManifestEntry[]> {
|
|
233
|
-
if (discovery.modules.length === 0) return [];
|
|
234
|
-
|
|
235
|
-
const entries: ActionManifestEntry[] = [];
|
|
236
|
-
const compile = async () => {
|
|
237
|
-
for (const module of discovery.modules) {
|
|
238
|
-
// 'build', matching render/index.tsx's prod moduleHref: the endpoint and the
|
|
239
|
-
// page render must import the SAME compiled file so a module imported in
|
|
240
|
-
// both the render and action layers is one instance (shared module state).
|
|
241
|
-
const href = await devServerModuleHref(config, module.file, 'build');
|
|
242
|
-
const compiled = fileURLToPath(href);
|
|
243
|
-
const modulePath = toPosixPath(path.relative(config.outPath, compiled));
|
|
244
|
-
const sourceKey = actionSourceKey(config, module.file);
|
|
245
|
-
for (const exportName of module.exports) {
|
|
246
|
-
entries.push({ id: module.ids[exportName]!, sourceKey, modulePath, exportName });
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
if (log) await log.step(`action modules (${discovery.modules.length})`, compile);
|
|
251
|
-
else await compile();
|
|
252
|
-
return entries;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Next-compatible server-reference manifest (.next/server/): tools and Next's
|
|
256
|
-
// own e2e suite introspect it for the registered action ids and their source
|
|
257
|
-
// modules. pnext's real registry lives in .pnext/manifest.json; this mirror
|
|
258
|
-
// exists purely for ecosystem compatibility.
|
|
259
|
-
async function writeServerReferenceManifest(
|
|
260
|
-
config: ResolvedConfig,
|
|
261
|
-
actions: ActionManifestEntry[],
|
|
262
|
-
) {
|
|
263
|
-
const node: Record<
|
|
264
|
-
string,
|
|
265
|
-
{
|
|
266
|
-
workers: Record<string, unknown>;
|
|
267
|
-
layer: Record<string, unknown>;
|
|
268
|
-
filename: string;
|
|
269
|
-
exportedName: string;
|
|
270
|
-
}
|
|
271
|
-
> = {};
|
|
272
|
-
for (const action of actions) {
|
|
273
|
-
node[action.id] = {
|
|
274
|
-
workers: {},
|
|
275
|
-
layer: {},
|
|
276
|
-
filename: action.sourceKey,
|
|
277
|
-
exportedName: action.exportName,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
const manifest = { node, edge: {}, encryptionKey: 'pnext' };
|
|
281
|
-
const outDir = path.join(config.root, '.next', 'server');
|
|
282
|
-
await mkdir(outDir, { recursive: true });
|
|
283
|
-
await writeFile(
|
|
284
|
-
path.join(outDir, 'server-reference-manifest.json'),
|
|
285
|
-
JSON.stringify(manifest, null, 2),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Request interceptor: server-action dispatch. Actions POST to the page's own URL (id in the
|
|
290
|
-
// next-action header for JS clients, or a hidden form field for no-JS progressive submissions).
|
|
291
|
-
// Route-handler POSTs pass through even with an id header - actions only dispatch on page URLs, or on
|
|
292
|
-
// unmatched paths, which still get the action error semantics.
|
|
293
|
-
|
|
294
|
-
const actionDispatchInterceptor: RequestInterceptor = async (request, ctx) => {
|
|
295
|
-
const config = ctx.config;
|
|
296
|
-
if (!nextCompatEnabled(config)) return undefined;
|
|
297
|
-
const runtime = getRequestRuntime();
|
|
298
|
-
if (!runtime) return undefined;
|
|
299
|
-
const url = new URL(request.url);
|
|
300
|
-
const actionTarget = selectRouteForRequest(runtime.routes, url.pathname, undefined);
|
|
301
|
-
// Route-handler POSTs pass through even with an id header — actions only
|
|
302
|
-
// dispatch on page URLs (or unmatched paths, which still get the action
|
|
303
|
-
// error semantics the unrecognized-action suite asserts).
|
|
304
|
-
if (actionTarget && actionTarget.route.kind !== 'page') return undefined;
|
|
305
|
-
|
|
306
|
-
// A form-encoded POST to an existing page with no recognizable action id is still an MPA action attempt
|
|
307
|
-
// in Next, which routes every page form POST through the action handler: with no action to run it
|
|
308
|
-
// answers 404 action-not-found rather than rendering the page. Only applies to real page targets - a
|
|
309
|
-
// POST to a nonexistent route stays a clean 404.
|
|
310
|
-
const isFormPost = actionTarget?.route.kind === 'page' && isFormPostRequest(request);
|
|
311
|
-
if (
|
|
312
|
-
!(isActionRequest(request) || (await isProgressiveActionRequest(request)) || isFormPost)
|
|
313
|
-
) {
|
|
314
|
-
return undefined;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
await ensureActionsRegistered(runtime);
|
|
318
|
-
|
|
319
|
-
const actionId = request.headers.get('next-action');
|
|
320
|
-
if (
|
|
321
|
-
actionId &&
|
|
322
|
-
canonicalUrlHref() !== undefined &&
|
|
323
|
-
isForwardedActionForRoute(config, actionTarget, actionId)
|
|
324
|
-
) {
|
|
325
|
-
return new Response(`Server Action "${actionId}" was not found on the server.`, {
|
|
326
|
-
status: 404,
|
|
327
|
-
headers: {
|
|
328
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
329
|
-
[NEXT_ACTION_NOT_FOUND_HEADER]: '1',
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const maxBodyBytes = runtime.maxBodyBytes ?? (await resolveMaxBodyBytes(config));
|
|
335
|
-
const options = buildServeActionOptions(runtime, request, actionTarget, maxBodyBytes);
|
|
336
|
-
return serveAction(request, options);
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
// Strip the configured basePath from a redirect-target URL so it lines up with
|
|
340
|
-
// the app-relative route table. Same matching rule as start.ts stripBasePath;
|
|
341
|
-
// a target outside the basePath (a `basePath:false` rewrite destination) is
|
|
342
|
-
// returned unchanged so it simply fails to match a page (external redirect).
|
|
343
|
-
function stripActionBasePath(target: URL, basePath: string): URL {
|
|
344
|
-
if (!basePath) return target;
|
|
345
|
-
const pathname = target.pathname;
|
|
346
|
-
if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return target;
|
|
347
|
-
const stripped = new URL(target);
|
|
348
|
-
stripped.pathname = pathname.slice(basePath.length) || '/';
|
|
349
|
-
return stripped;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Rebuild the render request against the basePath-stripped URL, preserving the
|
|
353
|
-
// action's applied cookie/header state. Returns the original request untouched
|
|
354
|
-
// when no stripping happened.
|
|
355
|
-
function strippedRequest(request: Request, target: URL, stripped: URL): Request {
|
|
356
|
-
if (stripped.href === target.href) return request;
|
|
357
|
-
return new Request(stripped.href, { headers: request.headers });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function isFormPostRequest(request: Request): boolean {
|
|
361
|
-
if (request.method.toUpperCase() !== 'POST') return false;
|
|
362
|
-
if (request.headers.has('next-action')) return false;
|
|
363
|
-
const contentType = request.headers.get('content-type') ?? '';
|
|
364
|
-
// Only multipart counts as an id-less MPA action attempt: React's progressive action forms always post
|
|
365
|
-
// multipart/form-data, so a urlencoded POST without an action id is a plain form POST to the page - a
|
|
366
|
-
// browser re-POSTing after a route handler's 307/308 redirect, say - and must render the page, not 404.
|
|
367
|
-
// urlencoded POSTs that DO carry the hidden action id field are still dispatched.
|
|
368
|
-
return contentType.includes('multipart/form-data');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// serverActions.bodySizeLimit from next.config, resolved once per project.
|
|
372
|
-
// Invalid values throw (Next's message) — surfaced on the first action request
|
|
373
|
-
// as a 500, matching the size-limit-invalid suite's expectation.
|
|
374
|
-
const bodyLimitCache = new Map<string, Promise<number | undefined>>();
|
|
375
|
-
|
|
376
|
-
function resolveMaxBodyBytes(config: ResolvedConfig): Promise<number | undefined> {
|
|
377
|
-
const key = config.root;
|
|
378
|
-
let cached = bodyLimitCache.get(key);
|
|
379
|
-
if (!cached) {
|
|
380
|
-
cached = loadServerActionsConfig(config.root).then(c => c.bodySizeLimitBytes);
|
|
381
|
-
bodyLimitCache.set(key, cached);
|
|
382
|
-
}
|
|
383
|
-
return cached;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Instance-recovery render dedup: the first dispatch against a prebuilt page
|
|
387
|
-
// re-renders the route to re-materialize its `i:` action closures
|
|
388
|
-
// (instances.ts); concurrent dispatches share that in-flight render instead of
|
|
389
|
-
// each starting a full one. Keyed route+pathname (dynamic routes recover per
|
|
390
|
-
// path), cleared on settle so evicted instances recover again.
|
|
391
|
-
const instanceRecoveries = new Map<string, Promise<void>>();
|
|
392
|
-
|
|
393
|
-
function dedupeInstanceRecovery(
|
|
394
|
-
routeId: string,
|
|
395
|
-
pathname: string,
|
|
396
|
-
render: () => Promise<void>,
|
|
397
|
-
): Promise<void> {
|
|
398
|
-
const key = `${routeId}${pathname}`;
|
|
399
|
-
const inFlight = instanceRecoveries.get(key);
|
|
400
|
-
if (inFlight) return inFlight;
|
|
401
|
-
const started = render().finally(() => instanceRecoveries.delete(key));
|
|
402
|
-
instanceRecoveries.set(key, started);
|
|
403
|
-
return started;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Reconstruct the ServeActionOptions the old start/dev handlers built inline,
|
|
408
|
-
* from the published request runtime + core render/import helpers. Dev vs prod
|
|
409
|
-
* only changes how modules resolve to hrefs.
|
|
410
|
-
*/
|
|
411
|
-
function buildServeActionOptions(
|
|
412
|
-
runtime: RequestRuntime,
|
|
413
|
-
request: Request,
|
|
414
|
-
actionTarget: ReturnType<typeof selectRouteForRequest>,
|
|
415
|
-
maxBodyBytes: number | undefined,
|
|
416
|
-
): ServeActionOptions {
|
|
417
|
-
const config = runtime.config;
|
|
418
|
-
const dev = runtime.dev;
|
|
419
|
-
const version = runtime.devImportVersion ?? 'build';
|
|
420
|
-
const url = new URL(request.url);
|
|
421
|
-
const actionConditionTarget =
|
|
422
|
-
actionTarget?.route.kind === 'page'
|
|
423
|
-
? serverBundleTargetForRuntime(actionTarget.route.segmentConfig?.runtime)
|
|
424
|
-
: 'server';
|
|
425
|
-
|
|
426
|
-
const importModule = dev
|
|
427
|
-
? async (modulePath: string) =>
|
|
428
|
-
(await import(
|
|
429
|
-
await devServerModuleHref(config, modulePath, version, {
|
|
430
|
-
conditionTarget: actionConditionTarget,
|
|
431
|
-
})
|
|
432
|
-
)) as Record<string, unknown>
|
|
433
|
-
: async (modulePath: string) =>
|
|
434
|
-
(await import(pathToFileHref(path.resolve(config.outPath, modulePath)))) as Record<
|
|
435
|
-
string,
|
|
436
|
-
unknown
|
|
437
|
-
>;
|
|
438
|
-
|
|
439
|
-
const options: ServeActionOptions = {
|
|
440
|
-
...(dev ? { dev: true } : {}),
|
|
441
|
-
importModule,
|
|
442
|
-
// skipNoindex: Next omits the fallback robots meta for an action-triggered
|
|
443
|
-
// not-found (app-render's NonIndex), keeping the page's own robots value.
|
|
444
|
-
renderNotFound: async notFoundRequest =>
|
|
445
|
-
actionTarget?.route.kind === 'page'
|
|
446
|
-
? renderNotFoundForRoute(
|
|
447
|
-
{
|
|
448
|
-
config,
|
|
449
|
-
route: actionTarget.route,
|
|
450
|
-
params: actionTarget.params,
|
|
451
|
-
url: new URL(notFoundRequest.url),
|
|
452
|
-
// GET conversion: the page renderer refuses non-GET requests.
|
|
453
|
-
request: new Request(notFoundRequest.url, { headers: notFoundRequest.headers }),
|
|
454
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
455
|
-
},
|
|
456
|
-
{ skipNoindex: true },
|
|
457
|
-
)
|
|
458
|
-
: renderGlobalNotFoundResponse(
|
|
459
|
-
{
|
|
460
|
-
config,
|
|
461
|
-
url: new URL(notFoundRequest.url),
|
|
462
|
-
request: new Request(notFoundRequest.url, { headers: notFoundRequest.headers }),
|
|
463
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
464
|
-
},
|
|
465
|
-
{ skipNoindex: true },
|
|
466
|
-
),
|
|
467
|
-
renderPageForFormState: async (formRequest, state, actionId) => {
|
|
468
|
-
if (actionTarget?.route.kind !== 'page') return null;
|
|
469
|
-
const formUrl = new URL(formRequest.url);
|
|
470
|
-
return renderWithFormStateOverride(state, actionId, () =>
|
|
471
|
-
renderPageResponse({
|
|
472
|
-
config,
|
|
473
|
-
route: actionTarget.route,
|
|
474
|
-
params: actionTarget.params,
|
|
475
|
-
url: formUrl,
|
|
476
|
-
request: new Request(formRequest.url, { headers: formRequest.headers }),
|
|
477
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
478
|
-
}),
|
|
479
|
-
);
|
|
480
|
-
},
|
|
481
|
-
renderRedirectTarget: async (redirectRequest, target) => {
|
|
482
|
-
// The redirect target arrives in basePath space (redirect() prefixes it);
|
|
483
|
-
// strip the prefix so it matches the app-relative route table and the
|
|
484
|
-
// rendered page's usePathname excludes the basePath, exactly like a
|
|
485
|
-
// freshly-routed GET (which start.ts strips before matching).
|
|
486
|
-
const stripped = stripActionBasePath(target, config.basePath);
|
|
487
|
-
const targetMatch = selectRouteForRequest(runtime.routes, stripped.pathname, undefined);
|
|
488
|
-
if (targetMatch?.route.kind !== 'page') return null;
|
|
489
|
-
return renderPageResponse({
|
|
490
|
-
config,
|
|
491
|
-
route: targetMatch.route,
|
|
492
|
-
params: targetMatch.params,
|
|
493
|
-
url: stripped,
|
|
494
|
-
request: strippedRequest(redirectRequest, target, stripped),
|
|
495
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
496
|
-
});
|
|
497
|
-
},
|
|
498
|
-
renderActionFlight: async (flightRequest, target) => {
|
|
499
|
-
const stripped = stripActionBasePath(target, config.basePath);
|
|
500
|
-
const targetMatch = selectRouteForRequest(runtime.routes, stripped.pathname, undefined);
|
|
501
|
-
if (targetMatch?.route.kind !== 'page') return null;
|
|
502
|
-
return renderPageResponse({
|
|
503
|
-
config,
|
|
504
|
-
route: targetMatch.route,
|
|
505
|
-
params: targetMatch.params,
|
|
506
|
-
url: stripped,
|
|
507
|
-
request: strippedRequest(flightRequest, target, stripped),
|
|
508
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
509
|
-
});
|
|
510
|
-
},
|
|
511
|
-
importSourceModule: async moduleKey => {
|
|
512
|
-
const absolute = path.resolve(config.root, moduleKey);
|
|
513
|
-
await import(
|
|
514
|
-
await devServerModuleHref(config, absolute, version, {
|
|
515
|
-
conditionTarget: actionConditionTarget,
|
|
516
|
-
}),
|
|
517
|
-
);
|
|
518
|
-
},
|
|
519
|
-
rerenderRoute: async routeId => {
|
|
520
|
-
const route = runtime.routes.find(entry => entry.id === routeId && entry.kind === 'page');
|
|
521
|
-
if (!route) throw new Error(`Unknown route for action re-render: ${routeId}`);
|
|
522
|
-
await dedupeInstanceRecovery(routeId, url.pathname, () =>
|
|
523
|
-
renderPage({
|
|
524
|
-
config,
|
|
525
|
-
route,
|
|
526
|
-
params: routeParamsFromPath(route, url.pathname),
|
|
527
|
-
url,
|
|
528
|
-
request: new Request(request.url, { headers: request.headers }),
|
|
529
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
530
|
-
}).then(() => undefined),
|
|
531
|
-
);
|
|
532
|
-
},
|
|
533
|
-
isForwardedAction: id => isForwardedActionForRoute(config, actionTarget, id),
|
|
534
|
-
onError: reportActionErrorToUser,
|
|
535
|
-
...(maxBodyBytes !== undefined ? { maxBodyBytes } : {}),
|
|
536
|
-
// Element-returning actions server-render the returned tree through the same
|
|
537
|
-
// machinery a page render uses (async server components, host elements),
|
|
538
|
-
// producing the island wire HTML the action client revives. Scoped to page
|
|
539
|
-
// targets — the only routes that own such actions.
|
|
540
|
-
...(actionTarget?.route.kind === 'page'
|
|
541
|
-
? {
|
|
542
|
-
renderActionElement: (element: unknown) =>
|
|
543
|
-
renderActionReturnElement(element, {
|
|
544
|
-
config,
|
|
545
|
-
route: actionTarget.route,
|
|
546
|
-
params: actionTarget.params,
|
|
547
|
-
url,
|
|
548
|
-
request: new Request(request.url, { headers: request.headers }),
|
|
549
|
-
...(dev ? { dev: true, devImportVersion: version } : {}),
|
|
550
|
-
}),
|
|
551
|
-
}
|
|
552
|
-
: {}),
|
|
553
|
-
};
|
|
554
|
-
return options;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Next "forwarded action" discrimination: the POSTed route's static source closure does not contain the
|
|
559
|
-
* action's source module, meaning the client dispatched the action after navigating to a different page.
|
|
560
|
-
* For live-instance actions the owning route is encoded in the id; for module and inline actions the
|
|
561
|
-
* registered source file is checked against the route's sourceFiles closure, realpath-canonicalized since
|
|
562
|
-
* the closure may hold /private-var vs /var aliases of the same file on macOS.
|
|
563
|
-
*/
|
|
564
|
-
function isForwardedActionForRoute(
|
|
565
|
-
config: ResolvedConfig,
|
|
566
|
-
actionTarget: ReturnType<typeof selectRouteForRequest>,
|
|
567
|
-
id: string,
|
|
568
|
-
): boolean {
|
|
569
|
-
const route = actionTarget?.route;
|
|
570
|
-
if (route?.kind !== 'page') return false;
|
|
571
|
-
if (isInstanceActionId(id)) {
|
|
572
|
-
const owner = instanceActionRouteId(id);
|
|
573
|
-
return Boolean(owner && owner !== route.id);
|
|
574
|
-
}
|
|
575
|
-
const source = isInlineActionId(id) ? inlineActionModuleKey(id) : lookupActionSource(id);
|
|
576
|
-
if (!source) return false;
|
|
577
|
-
const sources = route.sourceFiles;
|
|
578
|
-
if (!sources?.length) return false;
|
|
579
|
-
const target = canonical(path.resolve(config.root, source));
|
|
580
|
-
return !sources.some(file => canonical(file) === target);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// ---------------------------------------------------------------------------
|
|
584
|
-
// Action registry arming.
|
|
585
|
-
//
|
|
586
|
-
// prod: rebuild the registry from the built manifest once (id parity with the
|
|
587
|
-
// client stub via the stored source key).
|
|
588
|
-
// dev: re-run discovery whenever the dev import version changes (reload), so
|
|
589
|
-
// the endpoint + client-stub set stay current without core importing us.
|
|
590
|
-
// ---------------------------------------------------------------------------
|
|
591
|
-
|
|
592
|
-
// Memoized on the in-flight PROMISE, not on a "started" flag: a page can fire several actions at once,
|
|
593
|
-
// and a flag set before the await lets the second POST through against a registry that is still empty, so
|
|
594
|
-
// it 404s as an unknown action. Every caller awaits the same registration. A failed one is dropped so the
|
|
595
|
-
// next request retries instead of inheriting the rejection forever. `UNREGISTERED` keys the initial (and
|
|
596
|
-
// post-failure) state so the memo itself is never optional.
|
|
597
|
-
const UNREGISTERED = Symbol('pnext.actions.unregistered');
|
|
598
|
-
const noRegistration = { key: UNREGISTERED, version: UNREGISTERED, done: Promise.resolve() };
|
|
599
|
-
let manifestRegistration: { key: string | symbol; done: Promise<void> } = noRegistration;
|
|
600
|
-
let devRegistration: { version: string | symbol | undefined; done: Promise<void> } =
|
|
601
|
-
noRegistration;
|
|
602
|
-
|
|
603
|
-
async function ensureActionsRegistered(runtime: RequestRuntime): Promise<void> {
|
|
604
|
-
if (runtime.dev) {
|
|
605
|
-
if (devRegistration.version !== runtime.devImportVersion) {
|
|
606
|
-
const pending = { version: runtime.devImportVersion, done: Promise.resolve() };
|
|
607
|
-
pending.done = refreshDevActions(runtime.config).catch((error: unknown) => {
|
|
608
|
-
if (devRegistration === pending) devRegistration = noRegistration;
|
|
609
|
-
throw error;
|
|
610
|
-
});
|
|
611
|
-
devRegistration = pending;
|
|
612
|
-
}
|
|
613
|
-
await devRegistration.done;
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
const key = runtime.config.outPath;
|
|
617
|
-
if (manifestRegistration.key !== key) {
|
|
618
|
-
const pending = { key, done: Promise.resolve() };
|
|
619
|
-
pending.done = registerManifestActions(runtime.config).catch((error: unknown) => {
|
|
620
|
-
if (manifestRegistration === pending) manifestRegistration = noRegistration;
|
|
621
|
-
throw error;
|
|
622
|
-
});
|
|
623
|
-
manifestRegistration = pending;
|
|
624
|
-
}
|
|
625
|
-
await manifestRegistration.done;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Rebuild the action registry from the built manifest so the endpoint can
|
|
629
|
-
// resolve an incoming id to its compiled module + export. Also restore the
|
|
630
|
-
// client-action module set (normally armed by the build): render-time form/prop
|
|
631
|
-
// tagging reads it to give module-level actions their stable wire ids.
|
|
632
|
-
async function registerManifestActions(config: ResolvedConfig): Promise<void> {
|
|
633
|
-
const manifestPath = path.join(config.outPath, 'manifest.json');
|
|
634
|
-
let manifest: BuildManifest;
|
|
635
|
-
try {
|
|
636
|
-
manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest;
|
|
637
|
-
} catch {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const files = new Set<string>();
|
|
641
|
-
for (const action of manifest.actions ?? []) {
|
|
642
|
-
registerActionModule(action.sourceKey, [action.exportName], {
|
|
643
|
-
modulePath: action.modulePath,
|
|
644
|
-
});
|
|
645
|
-
files.add(path.resolve(config.root, action.sourceKey));
|
|
646
|
-
}
|
|
647
|
-
if (files.size > 0) setClientActionModules(config.root, files);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Rescan the app for 'use server' modules and refresh both the endpoint
|
|
651
|
-
// registry (id -> module) and the client-bundle stub set. In dev the action's
|
|
652
|
-
// modulePath stays the source file: devServerModuleHref compiles it on demand
|
|
653
|
-
// when the endpoint imports it. Gated on compat.next inside discoverActions.
|
|
654
|
-
async function refreshDevActions(config: ResolvedConfig): Promise<void> {
|
|
655
|
-
clearActions();
|
|
656
|
-
clearClientActionModules();
|
|
657
|
-
const discovery = await discoverActions(config);
|
|
658
|
-
if (discovery.modules.length === 0) return;
|
|
659
|
-
registerDiscoveredActions(config, discovery);
|
|
660
|
-
setClientActionModules(config.root, discovery.actionFiles);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// ---------------------------------------------------------------------------
|
|
664
|
-
// Request interceptor: next.config rewrites.
|
|
665
|
-
//
|
|
666
|
-
// Rewrites map the request pathname to its destination before static lookup and
|
|
667
|
-
// routing, so prebuilt output + revalidatePath marks work against the
|
|
668
|
-
// destination. The render keeps the requested URL canonical (usePathname); core
|
|
669
|
-
// preserves the original URL for rendering (see start/dev canonicalUrl).
|
|
670
|
-
// ---------------------------------------------------------------------------
|
|
671
|
-
|
|
672
|
-
const rewriteCache = new Map<string, Promise<CompatRewrite[]>>();
|
|
673
|
-
|
|
674
|
-
function loadRewritesFor(config: ResolvedConfig): Promise<CompatRewrite[]> {
|
|
675
|
-
const key = config.root;
|
|
676
|
-
let cached = rewriteCache.get(key);
|
|
677
|
-
if (!cached) {
|
|
678
|
-
cached = loadCompatRewrites(config.root);
|
|
679
|
-
rewriteCache.set(key, cached);
|
|
680
|
-
}
|
|
681
|
-
return cached;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const redirectCache = new Map<string, Promise<CompatRedirect[]>>();
|
|
685
|
-
|
|
686
|
-
function loadRedirectsFor(config: ResolvedConfig): Promise<CompatRedirect[]> {
|
|
687
|
-
const key = config.root;
|
|
688
|
-
let cached = redirectCache.get(key);
|
|
689
|
-
if (!cached) {
|
|
690
|
-
cached = loadCompatRedirects(config.root);
|
|
691
|
-
redirectCache.set(key, cached);
|
|
692
|
-
}
|
|
693
|
-
return cached;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
const redirectInterceptor: RequestInterceptor = async (request, ctx) => {
|
|
697
|
-
const config = ctx.config;
|
|
698
|
-
if (!nextCompatEnabled(config)) return undefined;
|
|
699
|
-
// `basePath: false` entries match the raw path core would otherwise 404;
|
|
700
|
-
// every other entry matches the in-app (basePath-stripped) path. The two
|
|
701
|
-
// passes are disjoint.
|
|
702
|
-
const redirects = (await loadRedirectsFor(config)).filter(
|
|
703
|
-
redirect => redirect.outsideBasePath === Boolean(ctx.outsideBasePath),
|
|
704
|
-
);
|
|
705
|
-
if (redirects.length === 0) return undefined;
|
|
706
|
-
const url = new URL(request.url);
|
|
707
|
-
const result = resolveCompatRedirect(redirects, url.pathname, {
|
|
708
|
-
host: request.headers.get('host') ?? '',
|
|
709
|
-
headers: request.headers,
|
|
710
|
-
cookies: parseCookieHeader(request.headers.get('cookie')),
|
|
711
|
-
query: url.searchParams,
|
|
712
|
-
});
|
|
713
|
-
if (!result) return undefined;
|
|
714
|
-
const location = new URL(result.location, url.href);
|
|
715
|
-
// Next passes the incoming query through to the redirect destination (the
|
|
716
|
-
// `_rsc` union query among it — rsc-query-routing relies on the followed
|
|
717
|
-
// request keeping it). Destination-specified params win.
|
|
718
|
-
for (const [key, value] of url.searchParams) {
|
|
719
|
-
if (!location.searchParams.has(key)) location.searchParams.append(key, value);
|
|
720
|
-
}
|
|
721
|
-
return new Response(null, { status: result.status, headers: { location: location.href } });
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
const rewriteInterceptor: RequestInterceptor = async (request, ctx) => {
|
|
725
|
-
const config = ctx.config;
|
|
726
|
-
if (!nextCompatEnabled(config)) return undefined;
|
|
727
|
-
// See redirectInterceptor: `basePath: false` rules own the outside-basePath
|
|
728
|
-
// pass, the rest own the in-app one.
|
|
729
|
-
const rewrites = (await loadRewritesFor(config)).filter(
|
|
730
|
-
rewrite => rewrite.outsideBasePath === Boolean(ctx.outsideBasePath),
|
|
731
|
-
);
|
|
732
|
-
if (rewrites.length === 0) return undefined;
|
|
733
|
-
const url = new URL(request.url);
|
|
734
|
-
const context = {
|
|
735
|
-
host: request.headers.get('host') ?? '',
|
|
736
|
-
headers: request.headers,
|
|
737
|
-
cookies: parseCookieHeader(request.headers.get('cookie')),
|
|
738
|
-
query: url.searchParams,
|
|
739
|
-
};
|
|
740
|
-
// beforeFiles/afterFiles (and the array form) apply ahead of routing; a
|
|
741
|
-
// `fallback` rewrite only gets a turn once nothing else can serve the path.
|
|
742
|
-
let result = resolveCompatRewrite(
|
|
743
|
-
rewrites.filter(rewrite => !rewrite.fallback),
|
|
744
|
-
url.pathname,
|
|
745
|
-
context,
|
|
746
|
-
);
|
|
747
|
-
if (!result && requestWouldMiss(config, url.pathname)) {
|
|
748
|
-
result = resolveCompatRewrite(
|
|
749
|
-
rewrites.filter(rewrite => rewrite.fallback),
|
|
750
|
-
url.pathname,
|
|
751
|
-
context,
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
if (!result) return undefined;
|
|
755
|
-
if (result.destination) {
|
|
756
|
-
activateTestProxy(request.headers);
|
|
757
|
-
// The incoming `host` (and its forwarded twins) name THIS server; carrying
|
|
758
|
-
// them onto an outbound request makes the TLS handshake disagree with the
|
|
759
|
-
// destination's certificate. Let fetch derive them from the destination URL.
|
|
760
|
-
const headers = new Headers(request.headers);
|
|
761
|
-
for (const header of ['host', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto']) {
|
|
762
|
-
headers.delete(header);
|
|
763
|
-
}
|
|
764
|
-
const rewrittenRequest = new Request(result.destination, {
|
|
765
|
-
method: request.method,
|
|
766
|
-
headers,
|
|
767
|
-
...(request.body ? { body: request.body, duplex: 'half' } : {}),
|
|
768
|
-
});
|
|
769
|
-
return fetch(rewrittenRequest);
|
|
770
|
-
}
|
|
771
|
-
const rewritten = new URL(url.href);
|
|
772
|
-
rewritten.pathname = result.pathname;
|
|
773
|
-
rewritten.search = result.search.toString();
|
|
774
|
-
recordCanonicalUrl(url);
|
|
775
|
-
recordRewrite(url, rewritten);
|
|
776
|
-
return { request: new Request(rewritten, request) };
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Whether a request for `pathname` would 404 - the gate Next's `fallback` rewrites sit behind. A path is
|
|
781
|
-
* servable when the route table matches it or a built/public file backs it; `/_next/*` assets are always
|
|
782
|
-
* served by core. Divergence: dev has no built output, so only the route table is consulted.
|
|
783
|
-
*/
|
|
784
|
-
function requestWouldMiss(config: ResolvedConfig, pathname: string): boolean {
|
|
785
|
-
if (pathname.startsWith('/_next/')) return false;
|
|
786
|
-
const runtime = getRequestRuntime();
|
|
787
|
-
if (runtime && selectRouteForRequest(runtime.routes, pathname)) return false;
|
|
788
|
-
return !servableFile(config, pathname);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function servableFile(config: ResolvedConfig, pathname: string): boolean {
|
|
792
|
-
const publicRoot = path.join(config.outPath, 'public');
|
|
793
|
-
const trimmed = pathname.replace(/^\/+|\/+$/g, '');
|
|
794
|
-
const file = trimmed ? path.join(publicRoot, trimmed) : publicRoot;
|
|
795
|
-
// Never let a traversal-shaped path claim to be servable (it is not).
|
|
796
|
-
const relative = path.relative(publicRoot, file);
|
|
797
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) return false;
|
|
798
|
-
return (
|
|
799
|
-
existsSync(path.join(file, 'index.html')) ||
|
|
800
|
-
(trimmed !== '' && (existsSync(file) || existsSync(`${file}.html`)))
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function parseCookieHeader(header: string | null): Record<string, string> {
|
|
805
|
-
if (!header) return {};
|
|
806
|
-
const cookies: Record<string, string> = {};
|
|
807
|
-
for (const part of header.split(';')) {
|
|
808
|
-
const eq = part.indexOf('=');
|
|
809
|
-
if (eq === -1) continue;
|
|
810
|
-
cookies[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
811
|
-
}
|
|
812
|
-
return cookies;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// ---------------------------------------------------------------------------
|
|
816
|
-
// Render extension: serialize a server-action function crossing the
|
|
817
|
-
// server->client boundary into the { [PROP_ACTION_MARKER]: id } marker.
|
|
818
|
-
//
|
|
819
|
-
// Id policy (Next-compatible), highest priority first:
|
|
820
|
-
// 1. caller-overridden $$id (version-skew simulation)
|
|
821
|
-
// 2. compile-tagged module-scope inline action ($$pnextAssignedId, 'if:')
|
|
822
|
-
// 3. tagged module-level 'use server' export (stable module id)
|
|
823
|
-
// 4. inline / .bind() / HOC closures -> a live per-render instance ('i:')
|
|
824
|
-
//
|
|
825
|
-
// Per-render instance scopes are keyed on the opaque renderKey (core's
|
|
826
|
-
// RenderOptions) so occurrence indices stay consistent within one render.
|
|
827
|
-
// ---------------------------------------------------------------------------
|
|
828
|
-
|
|
829
|
-
const instanceScopes = new WeakMap<object, InstanceScope>();
|
|
830
|
-
|
|
831
|
-
function serializeServerActionProp(
|
|
832
|
-
value: unknown,
|
|
833
|
-
context: ServerActionPropContext,
|
|
834
|
-
): ActionRef | undefined {
|
|
835
|
-
if (typeof value !== 'function') return undefined;
|
|
836
|
-
if (!nextCompatEnabled(context.config)) return undefined;
|
|
837
|
-
|
|
838
|
-
const overridden = overriddenActionId(value);
|
|
839
|
-
if (overridden) return { [PROP_ACTION_MARKER]: overridden };
|
|
840
|
-
|
|
841
|
-
const assigned = assignedInlineActionId(value);
|
|
842
|
-
if (assigned) return { [PROP_ACTION_MARKER]: assigned };
|
|
843
|
-
|
|
844
|
-
const moduleId = serverActionId(value);
|
|
845
|
-
if (moduleId) return { [PROP_ACTION_MARKER]: moduleId };
|
|
846
|
-
|
|
847
|
-
if (context.identifiedOnly && !isMarkedInlineAction(value) && !opensWithUseServerDirective(value)) {
|
|
848
|
-
return undefined;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
const scope = instanceScopeFor(context.renderKey);
|
|
852
|
-
const id = registerActionInstance(scope, value);
|
|
853
|
-
return { [PROP_ACTION_MARKER]: id };
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/** Tag each function export of a discovered action module with its wire id. */
|
|
857
|
-
function tagActionModuleExports(
|
|
858
|
-
config: ResolvedConfig,
|
|
859
|
-
file: string,
|
|
860
|
-
module: Record<string, unknown>,
|
|
861
|
-
): void {
|
|
862
|
-
if (!nextCompatEnabled(config) || !isClientActionModule(file)) return;
|
|
863
|
-
const root = clientActionModulesRoot() ?? config.root;
|
|
864
|
-
for (const [exportName, value] of Object.entries(module)) {
|
|
865
|
-
if (typeof value === 'function') {
|
|
866
|
-
tagServerAction(value, canonicalActionId(file, exportName, root));
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
/** Import + tag every discovered action module ahead of a page render. */
|
|
872
|
-
async function tagActionModulesForRender(
|
|
873
|
-
config: ResolvedConfig,
|
|
874
|
-
importModule: (file: string) => Promise<Record<string, unknown>>,
|
|
875
|
-
): Promise<void> {
|
|
876
|
-
if (!nextCompatEnabled(config)) return;
|
|
877
|
-
for (const file of clientActionModuleFiles()) {
|
|
878
|
-
try {
|
|
879
|
-
tagActionModuleExports(config, file, await importModule(file));
|
|
880
|
-
} catch {
|
|
881
|
-
// Best-effort: an action module that fails to import here still POSTs via
|
|
882
|
-
// the client stub when JS runs; the form just renders without the hidden id.
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
function instanceScopeFor(renderKey: object): InstanceScope {
|
|
888
|
-
let scope = instanceScopes.get(renderKey);
|
|
889
|
-
if (!scope) {
|
|
890
|
-
const routeId = (renderKey as { route?: RouteManifestEntry }).route?.id ?? 'unknown';
|
|
891
|
-
scope = createInstanceScope(routeId);
|
|
892
|
-
instanceScopes.set(renderKey, scope);
|
|
893
|
-
}
|
|
894
|
-
return scope;
|
|
105
|
+
})
|
|
895
106
|
}
|