@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
|
@@ -1,105 +1,102 @@
|
|
|
1
|
-
import type { MetadataLink } from '../../types'
|
|
2
|
-
import { getWorkUnit } from '../../request/context'
|
|
3
|
-
import { registerResponseFinalizers } from '../../extensions'
|
|
4
|
-
import type { ResponseFinalizerContext } from '../../extensions'
|
|
5
|
-
import { setResourceHintListener } from '../../render/resource-hints'
|
|
6
|
-
import { getNextConfig } from './config-loader'
|
|
7
|
-
|
|
8
|
-
const RESOURCE_HINTS_KEY = Symbol.for('pnext.compat.resourceHints')
|
|
9
|
-
const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6000
|
|
1
|
+
import type { MetadataLink } from '../../types'
|
|
2
|
+
import { getWorkUnit } from '../../request/context'
|
|
3
|
+
import { registerResponseFinalizers } from '../../extensions'
|
|
4
|
+
import type { ResponseFinalizerContext } from '../../extensions'
|
|
5
|
+
import { setResourceHintListener } from '../../render/resource-hints'
|
|
6
|
+
import { getNextConfig } from './config-loader'
|
|
7
|
+
|
|
8
|
+
const RESOURCE_HINTS_KEY = Symbol.for('pnext.compat.resourceHints')
|
|
9
|
+
const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6000
|
|
10
10
|
// Next emits react-dom preload()/preconnect() hints as a `Link` response
|
|
11
11
|
// header by default in production; nothing in the compat runtime ever called
|
|
12
12
|
// enableResourceHintHeaders, so the finalizer was dead code. The finalizer is
|
|
13
13
|
// only registered under next-compat, so defaulting on is scoped already.
|
|
14
|
-
let resourceHintHeadersEnabled = true
|
|
14
|
+
let resourceHintHeadersEnabled = true
|
|
15
15
|
|
|
16
16
|
export type ResourceHint = MetadataLink & {
|
|
17
|
-
rel: string
|
|
18
|
-
url: string
|
|
19
|
-
}
|
|
17
|
+
rel: string
|
|
18
|
+
url: string
|
|
19
|
+
}
|
|
20
20
|
|
|
21
21
|
export function enableResourceHintHeaders(enabled: boolean): void {
|
|
22
|
-
resourceHintHeadersEnabled = enabled
|
|
22
|
+
resourceHintHeadersEnabled = enabled
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function registerResourceHintFinalizer(): void {
|
|
26
|
-
registerResponseFinalizers(resourceHintResponseFinalizer)
|
|
26
|
+
registerResponseFinalizers(resourceHintResponseFinalizer)
|
|
27
27
|
// Every react-dom preload()/preconnect()/preinit() during a render lands on
|
|
28
28
|
// the active work unit so the finalizer (and the build prerender) can emit
|
|
29
29
|
// Next's `Link` response header alongside the in-document <link> tags.
|
|
30
|
-
setResourceHintListener(hint => recordResourceHint(hint as ResourceHint))
|
|
30
|
+
setResourceHintListener(hint => recordResourceHint(hint as ResourceHint))
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function recordResourceHint(hint: ResourceHint): void {
|
|
34
|
-
const unit = getWorkUnit()
|
|
35
|
-
if (!unit) return
|
|
34
|
+
const unit = getWorkUnit()
|
|
35
|
+
if (!unit) return
|
|
36
36
|
|
|
37
|
-
const existing = (unit.compat?.[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? []
|
|
38
|
-
if (!unit.compat) unit.compat = {}
|
|
39
|
-
unit.compat[RESOURCE_HINTS_KEY] = dedupeResourceHints([...existing, hint])
|
|
37
|
+
const existing = (unit.compat?.[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? []
|
|
38
|
+
if (!unit.compat) unit.compat = {}
|
|
39
|
+
unit.compat[RESOURCE_HINTS_KEY] = dedupeResourceHints([...existing, hint])
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function takeResourceHintsForResponse(): ResourceHint[] {
|
|
43
|
-
const unit = getWorkUnit()
|
|
44
|
-
const compat = unit?.compat
|
|
45
|
-
if (!compat) return []
|
|
46
|
-
const hints = (compat[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? []
|
|
47
|
-
compat[RESOURCE_HINTS_KEY] = undefined
|
|
48
|
-
return hints
|
|
43
|
+
const unit = getWorkUnit()
|
|
44
|
+
const compat = unit?.compat
|
|
45
|
+
if (!compat) return []
|
|
46
|
+
const hints = (compat[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? []
|
|
47
|
+
compat[RESOURCE_HINTS_KEY] = undefined
|
|
48
|
+
return hints
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const ALLOWED_ROUTE_KINDS = new Set<ResponseFinalizerContext['routeKind']>([
|
|
52
|
-
'html',
|
|
53
|
-
'data',
|
|
54
|
-
]);
|
|
51
|
+
const ALLOWED_ROUTE_KINDS = new Set<ResponseFinalizerContext['routeKind']>(['html', 'data'])
|
|
55
52
|
|
|
56
53
|
function resourceHintResponseFinalizer(ctx: ResponseFinalizerContext): void {
|
|
57
|
-
if (!shouldEmitResourceHintsHeader()) return
|
|
58
|
-
if (ctx.hints?.runtime === 'edge') return
|
|
59
|
-
if (!ALLOWED_ROUTE_KINDS.has(ctx.routeKind)) return
|
|
60
|
-
|
|
61
|
-
const value = takeResourceHintHeader()
|
|
62
|
-
if (value === undefined) return
|
|
63
|
-
const existing = ctx.headers.get('link')
|
|
64
|
-
ctx.headers.set('link', existing ? `${existing}, ${value}` : value)
|
|
54
|
+
if (!shouldEmitResourceHintsHeader()) return
|
|
55
|
+
if (ctx.hints?.runtime === 'edge') return
|
|
56
|
+
if (!ALLOWED_ROUTE_KINDS.has(ctx.routeKind)) return
|
|
57
|
+
|
|
58
|
+
const value = takeResourceHintHeader()
|
|
59
|
+
if (value === undefined) return
|
|
60
|
+
const existing = ctx.headers.get('link')
|
|
61
|
+
ctx.headers.set('link', existing ? `${existing}, ${value}` : value)
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
function shouldEmitResourceHintsHeader(): boolean {
|
|
68
|
-
return resourceHintHeadersEnabled
|
|
65
|
+
return resourceHintHeadersEnabled
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
function getReactMaxHeadersLength(): number {
|
|
72
|
-
const configValue = getNextConfig().reactMaxHeadersLength
|
|
69
|
+
const configValue = getNextConfig().reactMaxHeadersLength
|
|
73
70
|
if (typeof configValue === 'number' && Number.isFinite(configValue) && configValue >= 0) {
|
|
74
|
-
return Math.floor(configValue)
|
|
71
|
+
return Math.floor(configValue)
|
|
75
72
|
}
|
|
76
73
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
77
|
-
const envValue = process.env.TEST_REACT_MAX_HEADERS_LENGTH
|
|
74
|
+
const envValue = process.env.TEST_REACT_MAX_HEADERS_LENGTH
|
|
78
75
|
if (typeof envValue === 'string' && envValue.trim() !== '') {
|
|
79
|
-
const parsed = Number.parseInt(envValue, 10)
|
|
80
|
-
if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed)
|
|
76
|
+
const parsed = Number.parseInt(envValue, 10)
|
|
77
|
+
if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed)
|
|
81
78
|
}
|
|
82
|
-
return DEFAULT_REACT_MAX_HEADERS_LENGTH
|
|
79
|
+
return DEFAULT_REACT_MAX_HEADERS_LENGTH
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
export function takeResourceHintHeader(): string | undefined {
|
|
86
|
-
const hints = takeResourceHintsForResponse()
|
|
87
|
-
if (hints.length === 0) return undefined
|
|
83
|
+
const hints = takeResourceHintsForResponse()
|
|
84
|
+
if (hints.length === 0) return undefined
|
|
88
85
|
|
|
89
|
-
const maxLength = getReactMaxHeadersLength()
|
|
90
|
-
if (!(maxLength > 0)) return undefined
|
|
86
|
+
const maxLength = getReactMaxHeadersLength()
|
|
87
|
+
if (!(maxLength > 0)) return undefined
|
|
91
88
|
|
|
92
|
-
const entries = hints.map(hint => formatLinkHeaderEntry(hint)).filter(Boolean)
|
|
93
|
-
if (entries.length === 0) return undefined
|
|
89
|
+
const entries = hints.map(hint => formatLinkHeaderEntry(hint)).filter(Boolean)
|
|
90
|
+
if (entries.length === 0) return undefined
|
|
94
91
|
|
|
95
|
-
const values: string[] = []
|
|
92
|
+
const values: string[] = []
|
|
96
93
|
for (const entry of entries) {
|
|
97
|
-
const candidate = values.length > 0 ? `${values.join(', ')}, ${entry}` : entry
|
|
98
|
-
if (candidate.length > maxLength) break
|
|
99
|
-
values.push(entry)
|
|
94
|
+
const candidate = values.length > 0 ? `${values.join(', ')}, ${entry}` : entry
|
|
95
|
+
if (candidate.length > maxLength) break
|
|
96
|
+
values.push(entry)
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
return values.length === 0 ? undefined : values.join(', ')
|
|
99
|
+
return values.length === 0 ? undefined : values.join(', ')
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
// React's Link-header entry shape (ReactFizz getPreloadAsHeader): the rel is
|
|
@@ -108,29 +105,29 @@ export function takeResourceHintHeader(): string | undefined {
|
|
|
108
105
|
// expected header lengths from React's exact per-entry size, so the format
|
|
109
106
|
// must match byte-for-byte.
|
|
110
107
|
function formatLinkHeaderEntry(hint: ResourceHint): string {
|
|
111
|
-
const parts = [`<${hint.url}>`, `rel=${hint.rel}`]
|
|
112
|
-
if (hint.as) parts.push(`as="${hint.as}"`)
|
|
108
|
+
const parts = [`<${hint.url}>`, `rel=${hint.rel}`]
|
|
109
|
+
if (hint.as) parts.push(`as="${hint.as}"`)
|
|
113
110
|
if (hint.crossOrigin !== undefined)
|
|
114
|
-
parts.push(`crossorigin="${hint.crossOrigin === 'anonymous' ? '' : hint.crossOrigin}"`)
|
|
115
|
-
if (hint.type) parts.push(`type="${hint.type}"`)
|
|
116
|
-
if (hint.media) parts.push(`media="${hint.media}"`)
|
|
117
|
-
if (hint.sizes) parts.push(`sizes="${hint.sizes}"`)
|
|
118
|
-
if (hint.title) parts.push(`title="${hint.title}"`)
|
|
119
|
-
if (hint.hrefLang) parts.push(`hreflang="${hint.hrefLang}"`)
|
|
120
|
-
if (hint.fetchPriority) parts.push(`fetchpriority="${hint.fetchPriority}"`)
|
|
121
|
-
if (hint.nonce) parts.push(`nonce="${hint.nonce}"`)
|
|
122
|
-
return parts.join('; ')
|
|
111
|
+
parts.push(`crossorigin="${hint.crossOrigin === 'anonymous' ? '' : hint.crossOrigin}"`)
|
|
112
|
+
if (hint.type) parts.push(`type="${hint.type}"`)
|
|
113
|
+
if (hint.media) parts.push(`media="${hint.media}"`)
|
|
114
|
+
if (hint.sizes) parts.push(`sizes="${hint.sizes}"`)
|
|
115
|
+
if (hint.title) parts.push(`title="${hint.title}"`)
|
|
116
|
+
if (hint.hrefLang) parts.push(`hreflang="${hint.hrefLang}"`)
|
|
117
|
+
if (hint.fetchPriority) parts.push(`fetchpriority="${hint.fetchPriority}"`)
|
|
118
|
+
if (hint.nonce) parts.push(`nonce="${hint.nonce}"`)
|
|
119
|
+
return parts.join('; ')
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
function dedupeResourceHints(hints: ResourceHint[]): ResourceHint[] {
|
|
126
|
-
if (hints.length <= 1) return hints
|
|
127
|
-
const seen = new Set<string>()
|
|
128
|
-
const next: ResourceHint[] = []
|
|
123
|
+
if (hints.length <= 1) return hints
|
|
124
|
+
const seen = new Set<string>()
|
|
125
|
+
const next: ResourceHint[] = []
|
|
129
126
|
for (const hint of hints) {
|
|
130
|
-
const key = formatLinkHeaderEntry(hint)
|
|
131
|
-
if (seen.has(key)) continue
|
|
132
|
-
seen.add(key)
|
|
133
|
-
next.push(hint)
|
|
127
|
+
const key = formatLinkHeaderEntry(hint)
|
|
128
|
+
if (seen.has(key)) continue
|
|
129
|
+
seen.add(key)
|
|
130
|
+
next.push(hint)
|
|
134
131
|
}
|
|
135
|
-
return next
|
|
132
|
+
return next
|
|
136
133
|
}
|
|
@@ -17,83 +17,83 @@
|
|
|
17
17
|
* Divergence: the render sees the destination URL (Next keeps the requested URL as canonical for
|
|
18
18
|
* usePathname; recordCanonicalUrl handles that separately).
|
|
19
19
|
*/
|
|
20
|
-
import { loadNextConfig, loadResolvedNextConfig } from '../actions/config'
|
|
20
|
+
import { loadNextConfig, loadResolvedNextConfig } from '../actions/config'
|
|
21
21
|
|
|
22
22
|
export interface CompatRewrite {
|
|
23
|
-
regex: RegExp
|
|
24
|
-
paramNames: string[]
|
|
25
|
-
destination: string
|
|
26
|
-
has: RewriteCondition[]
|
|
27
|
-
missing: RewriteCondition[]
|
|
23
|
+
regex: RegExp
|
|
24
|
+
paramNames: string[]
|
|
25
|
+
destination: string
|
|
26
|
+
has: RewriteCondition[]
|
|
27
|
+
missing: RewriteCondition[]
|
|
28
28
|
/**
|
|
29
29
|
* A `fallback` rewrite: Next applies it only after filesystem AND dynamic
|
|
30
30
|
* routes have all missed, i.e. only on a request that would otherwise 404.
|
|
31
31
|
* `beforeFiles`/`afterFiles` (and the array form, which is `afterFiles`)
|
|
32
32
|
* apply ahead of routing and carry this false.
|
|
33
33
|
*/
|
|
34
|
-
fallback: boolean
|
|
34
|
+
fallback: boolean
|
|
35
35
|
/**
|
|
36
36
|
* `basePath: false` on the entry: the source is matched against the RAW
|
|
37
37
|
* request path, outside the configured basePath. Core 404s such paths before
|
|
38
38
|
* routing, so these rules get their turn from the outside-basePath pass
|
|
39
39
|
* instead of the normal one (and never both).
|
|
40
40
|
*/
|
|
41
|
-
outsideBasePath: boolean
|
|
41
|
+
outsideBasePath: boolean
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface RewriteCondition {
|
|
45
|
-
type: 'host' | 'header' | 'query' | 'cookie'
|
|
46
|
-
key?: string
|
|
47
|
-
value?: string
|
|
45
|
+
type: 'host' | 'header' | 'query' | 'cookie'
|
|
46
|
+
key?: string
|
|
47
|
+
value?: string
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
interface RewriteEntry {
|
|
51
|
-
source?: string
|
|
52
|
-
destination?: string
|
|
53
|
-
has?: unknown
|
|
54
|
-
missing?: unknown
|
|
55
|
-
basePath?: unknown
|
|
51
|
+
source?: string
|
|
52
|
+
destination?: string
|
|
53
|
+
has?: unknown
|
|
54
|
+
missing?: unknown
|
|
55
|
+
basePath?: unknown
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** Request facts a rewrite's `has`/`missing` conditions are evaluated against. */
|
|
59
59
|
export interface RewriteRequestContext {
|
|
60
|
-
host: string
|
|
61
|
-
headers: Headers
|
|
62
|
-
cookies: Record<string, string
|
|
63
|
-
query: URLSearchParams
|
|
60
|
+
host: string
|
|
61
|
+
headers: Headers
|
|
62
|
+
cookies: Record<string, string>
|
|
63
|
+
query: URLSearchParams
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const VALID_CONDITION_TYPES = new Set(['host', 'header', 'query', 'cookie'])
|
|
66
|
+
const VALID_CONDITION_TYPES = new Set(['host', 'header', 'query', 'cookie'])
|
|
67
67
|
|
|
68
68
|
export async function loadCompatRewrites(root: string): Promise<CompatRewrite[]> {
|
|
69
|
-
let config = await loadResolvedNextConfig(root)
|
|
70
|
-
if (typeof config?.rewrites !== 'function') config = await loadNextConfig(root)
|
|
71
|
-
const producer = config?.rewrites
|
|
72
|
-
if (typeof producer !== 'function') return []
|
|
73
|
-
let value: unknown
|
|
69
|
+
let config = await loadResolvedNextConfig(root)
|
|
70
|
+
if (typeof config?.rewrites !== 'function') config = await loadNextConfig(root)
|
|
71
|
+
const producer = config?.rewrites
|
|
72
|
+
if (typeof producer !== 'function') return []
|
|
73
|
+
let value: unknown
|
|
74
74
|
try {
|
|
75
|
-
value = await producer()
|
|
75
|
+
value = await producer()
|
|
76
76
|
} catch {
|
|
77
|
-
return []
|
|
77
|
+
return []
|
|
78
78
|
}
|
|
79
79
|
const entries: (RewriteEntry & { fallback: boolean })[] = Array.isArray(value)
|
|
80
80
|
? (value as RewriteEntry[]).map(entry => ({ ...entry, fallback: false }))
|
|
81
81
|
: value && typeof value === 'object'
|
|
82
82
|
? (['beforeFiles', 'afterFiles', 'fallback'] as const).flatMap(key => {
|
|
83
|
-
const list = (value as Record<string, unknown>)[key]
|
|
83
|
+
const list = (value as Record<string, unknown>)[key]
|
|
84
84
|
return Array.isArray(list)
|
|
85
85
|
? (list as RewriteEntry[]).map(entry => ({ ...entry, fallback: key === 'fallback' }))
|
|
86
|
-
: []
|
|
86
|
+
: []
|
|
87
87
|
})
|
|
88
|
-
: []
|
|
88
|
+
: []
|
|
89
89
|
|
|
90
|
-
const rewrites: CompatRewrite[] = []
|
|
90
|
+
const rewrites: CompatRewrite[] = []
|
|
91
91
|
for (const entry of entries) {
|
|
92
|
-
if (!entry?.source || !entry.destination) continue
|
|
93
|
-
const has = parseConditions(entry.has)
|
|
94
|
-
const missing = parseConditions(entry.missing)
|
|
95
|
-
if (has === null || missing === null) continue
|
|
96
|
-
const compiled = compileSource(entry.source)
|
|
92
|
+
if (!entry?.source || !entry.destination) continue
|
|
93
|
+
const has = parseConditions(entry.has)
|
|
94
|
+
const missing = parseConditions(entry.missing)
|
|
95
|
+
if (has === null || missing === null) continue // unsupported condition shape
|
|
96
|
+
const compiled = compileSource(entry.source)
|
|
97
97
|
if (compiled) {
|
|
98
98
|
rewrites.push({
|
|
99
99
|
...compiled,
|
|
@@ -102,78 +102,78 @@ export async function loadCompatRewrites(root: string): Promise<CompatRewrite[]>
|
|
|
102
102
|
missing,
|
|
103
103
|
fallback: entry.fallback,
|
|
104
104
|
outsideBasePath: entry.basePath === false,
|
|
105
|
-
})
|
|
105
|
+
})
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
return rewrites
|
|
108
|
+
return rewrites
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/** Parse a has/missing list into typed conditions; null if any entry is unsupported. */
|
|
112
112
|
export function parseConditions(value: unknown): RewriteCondition[] | null {
|
|
113
|
-
if (value === undefined) return []
|
|
114
|
-
if (!Array.isArray(value)) return null
|
|
115
|
-
const conditions: RewriteCondition[] = []
|
|
113
|
+
if (value === undefined) return []
|
|
114
|
+
if (!Array.isArray(value)) return null
|
|
115
|
+
const conditions: RewriteCondition[] = []
|
|
116
116
|
for (const raw of value as unknown[]) {
|
|
117
|
-
if (!raw || typeof raw !== 'object') return null
|
|
118
|
-
const { type, key, value: condValue } = raw as Record<string, unknown
|
|
119
|
-
if (typeof type !== 'string' || !VALID_CONDITION_TYPES.has(type)) return null
|
|
120
|
-
if (key !== undefined && typeof key !== 'string') return null
|
|
121
|
-
if (condValue !== undefined && typeof condValue !== 'string') return null
|
|
117
|
+
if (!raw || typeof raw !== 'object') return null
|
|
118
|
+
const { type, key, value: condValue } = raw as Record<string, unknown>
|
|
119
|
+
if (typeof type !== 'string' || !VALID_CONDITION_TYPES.has(type)) return null
|
|
120
|
+
if (key !== undefined && typeof key !== 'string') return null
|
|
121
|
+
if (condValue !== undefined && typeof condValue !== 'string') return null
|
|
122
122
|
conditions.push({
|
|
123
123
|
type: type as RewriteCondition['type'],
|
|
124
124
|
...(typeof key === 'string' ? { key } : {}),
|
|
125
125
|
...(typeof condValue === 'string' ? { value: condValue } : {}),
|
|
126
|
-
})
|
|
126
|
+
})
|
|
127
127
|
}
|
|
128
|
-
return conditions
|
|
128
|
+
return conditions
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export function compileSource(source: string): { regex: RegExp; paramNames: string[] } | null {
|
|
132
|
-
const paramNames: string[] = []
|
|
132
|
+
const paramNames: string[] = []
|
|
133
133
|
// Match against the pathname. Note: the source is a path-to-regexp pattern
|
|
134
134
|
// (query matching is done via `has` conditions), so it never carries a
|
|
135
135
|
// literal `?query`. We must NOT split on `?` — a `?` may legitimately appear
|
|
136
136
|
// inside a regex token such as `:path((?!sitemap.xml$).*)` or `(?<name>...)`.
|
|
137
|
-
const segments = source.split('/').filter(Boolean)
|
|
138
|
-
const parts: string[] = []
|
|
137
|
+
const segments = source.split('/').filter(Boolean)
|
|
138
|
+
const parts: string[] = []
|
|
139
139
|
for (const segment of segments) {
|
|
140
140
|
// :param(customRegex) — a named param with an explicit regex body (may span
|
|
141
141
|
// the whole segment, e.g. `:path((?!sitemap.xml$).*)`).
|
|
142
|
-
const custom = /^:([A-Za-z_$][\w$]*)\((.*)\)$/.exec(segment)
|
|
142
|
+
const custom = /^:([A-Za-z_$][\w$]*)\((.*)\)$/.exec(segment)
|
|
143
143
|
if (custom?.[1] !== undefined && custom[2] !== undefined) {
|
|
144
|
-
paramNames.push(custom[1])
|
|
145
|
-
parts.push(`(${custom[2]})`)
|
|
146
|
-
continue
|
|
144
|
+
paramNames.push(custom[1])
|
|
145
|
+
parts.push(`(${custom[2]})`)
|
|
146
|
+
continue
|
|
147
147
|
}
|
|
148
|
-
const catchAll = /^:([A-Za-z_$][\w$]*)[*+]$/.exec(segment)
|
|
148
|
+
const catchAll = /^:([A-Za-z_$][\w$]*)[*+]$/.exec(segment)
|
|
149
149
|
if (catchAll?.[1]) {
|
|
150
|
-
paramNames.push(catchAll[1])
|
|
151
|
-
parts.push('(.*)')
|
|
152
|
-
continue
|
|
150
|
+
paramNames.push(catchAll[1])
|
|
151
|
+
parts.push('(.*)')
|
|
152
|
+
continue
|
|
153
153
|
}
|
|
154
|
-
const optional = /^:([A-Za-z_$][\w$]*)\?$/.exec(segment)
|
|
154
|
+
const optional = /^:([A-Za-z_$][\w$]*)\?$/.exec(segment)
|
|
155
155
|
if (optional?.[1]) {
|
|
156
|
-
paramNames.push(optional[1])
|
|
157
|
-
parts.push('([^/]*)')
|
|
158
|
-
continue
|
|
156
|
+
paramNames.push(optional[1])
|
|
157
|
+
parts.push('([^/]*)')
|
|
158
|
+
continue
|
|
159
159
|
}
|
|
160
|
-
const param = /^:([A-Za-z_$][\w$]*)$/.exec(segment)
|
|
160
|
+
const param = /^:([A-Za-z_$][\w$]*)$/.exec(segment)
|
|
161
161
|
if (param?.[1]) {
|
|
162
|
-
paramNames.push(param[1])
|
|
163
|
-
parts.push('([^/]+)')
|
|
164
|
-
continue
|
|
162
|
+
paramNames.push(param[1])
|
|
163
|
+
parts.push('([^/]+)')
|
|
164
|
+
continue
|
|
165
165
|
}
|
|
166
166
|
// Plain literal segments may carry regex-significant filename characters
|
|
167
167
|
// (`favicon.ico`, `sitemap.xml`) — escape them. Anything with actual
|
|
168
168
|
// pattern syntax beyond that is unsupported.
|
|
169
169
|
if (/^[\w.\- ]+$/.test(segment)) {
|
|
170
|
-
parts.push(segment.replace(/\./g, '\\.'))
|
|
171
|
-
continue
|
|
170
|
+
parts.push(segment.replace(/\./g, '\\.'))
|
|
171
|
+
continue
|
|
172
172
|
}
|
|
173
|
-
if (/[.*+?^${}()|[\]\\]/.test(segment)) return null
|
|
174
|
-
parts.push(segment.replace(/[/\\]/g, ''))
|
|
173
|
+
if (/[.*+?^${}()|[\]\\]/.test(segment)) return null // unsupported literal syntax
|
|
174
|
+
parts.push(segment.replace(/[/\\]/g, ''))
|
|
175
175
|
}
|
|
176
|
-
return { regex: new RegExp(`^/${parts.join('/')}/?$`), paramNames }
|
|
176
|
+
return { regex: new RegExp(`^/${parts.join('/')}/?$`), paramNames }
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/** Evaluate a single has/missing condition, returning captured params or null (no match). */
|
|
@@ -181,14 +181,14 @@ function evaluateCondition(
|
|
|
181
181
|
condition: RewriteCondition,
|
|
182
182
|
ctx: RewriteRequestContext,
|
|
183
183
|
): Record<string, string> | null {
|
|
184
|
-
const target = conditionTarget(condition, ctx)
|
|
185
|
-
if (target === undefined) return null
|
|
186
|
-
if (condition.value === undefined) return {}
|
|
184
|
+
const target = conditionTarget(condition, ctx)
|
|
185
|
+
if (target === undefined) return null
|
|
186
|
+
if (condition.value === undefined) return {} // presence-only match
|
|
187
187
|
// Anchor the value regex the way Next does (full-value match).
|
|
188
|
-
const regex = new RegExp(`^(?:${condition.value})$`)
|
|
189
|
-
const match = regex.exec(target)
|
|
190
|
-
if (!match) return null
|
|
191
|
-
return match.groups ? { ...match.groups } : {}
|
|
188
|
+
const regex = new RegExp(`^(?:${condition.value})$`)
|
|
189
|
+
const match = regex.exec(target)
|
|
190
|
+
if (!match) return null
|
|
191
|
+
return match.groups ? { ...match.groups } : {}
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
function conditionTarget(
|
|
@@ -197,24 +197,24 @@ function conditionTarget(
|
|
|
197
197
|
): string | undefined {
|
|
198
198
|
switch (condition.type) {
|
|
199
199
|
case 'host':
|
|
200
|
-
return ctx.host
|
|
200
|
+
return ctx.host
|
|
201
201
|
case 'header':
|
|
202
|
-
return condition.key ? (ctx.headers.get(condition.key) ?? undefined) : undefined
|
|
202
|
+
return condition.key ? (ctx.headers.get(condition.key) ?? undefined) : undefined
|
|
203
203
|
case 'cookie':
|
|
204
|
-
return condition.key ? ctx.cookies[condition.key] : undefined
|
|
204
|
+
return condition.key ? ctx.cookies[condition.key] : undefined
|
|
205
205
|
case 'query':
|
|
206
|
-
return condition.key ? (ctx.query.get(condition.key) ?? undefined) : undefined
|
|
206
|
+
return condition.key ? (ctx.query.get(condition.key) ?? undefined) : undefined
|
|
207
207
|
default:
|
|
208
|
-
return undefined
|
|
208
|
+
return undefined
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
export interface RewriteResult {
|
|
213
|
-
pathname: string
|
|
213
|
+
pathname: string
|
|
214
214
|
/** Search params after the rewrite (original query + any auto-appended params). */
|
|
215
|
-
search: URLSearchParams
|
|
215
|
+
search: URLSearchParams
|
|
216
216
|
/** Fully-qualified destination for external rewrites (`https://...`). */
|
|
217
|
-
destination?: string
|
|
217
|
+
destination?: string
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
@@ -228,7 +228,7 @@ export function applyCompatRewrites(
|
|
|
228
228
|
pathname: string,
|
|
229
229
|
ctx?: RewriteRequestContext,
|
|
230
230
|
): string | undefined {
|
|
231
|
-
return resolveCompatRewrite(rewrites, pathname, ctx)?.pathname
|
|
231
|
+
return resolveCompatRewrite(rewrites, pathname, ctx)?.pathname
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/**
|
|
@@ -246,42 +246,42 @@ export function resolveCompatRewrite(
|
|
|
246
246
|
headers: new Headers(),
|
|
247
247
|
cookies: {},
|
|
248
248
|
query: new URLSearchParams(),
|
|
249
|
-
}
|
|
249
|
+
}
|
|
250
250
|
for (const rewrite of rewrites) {
|
|
251
|
-
const match = rewrite.regex.exec(pathname)
|
|
252
|
-
if (!match) continue
|
|
251
|
+
const match = rewrite.regex.exec(pathname)
|
|
252
|
+
if (!match) continue
|
|
253
253
|
|
|
254
254
|
// Collect all params: source captures first, then has/missing captures.
|
|
255
|
-
const params: Record<string, string> = {}
|
|
255
|
+
const params: Record<string, string> = {}
|
|
256
256
|
rewrite.paramNames.forEach((name, index) => {
|
|
257
|
-
params[name] = match[index + 1] ?? ''
|
|
258
|
-
})
|
|
257
|
+
params[name] = match[index + 1] ?? ''
|
|
258
|
+
})
|
|
259
259
|
|
|
260
|
-
const conditionParams = evaluateConditions(rewrite, context)
|
|
261
|
-
if (conditionParams === null) continue
|
|
262
|
-
Object.assign(params, conditionParams)
|
|
260
|
+
const conditionParams = evaluateConditions(rewrite, context)
|
|
261
|
+
if (conditionParams === null) continue // has/missing gate failed
|
|
262
|
+
Object.assign(params, conditionParams)
|
|
263
263
|
|
|
264
|
-
const [destPath, destQuery] = splitDestination(rewrite.destination)
|
|
265
|
-
const { pathname: newPath, used: usedInPath } = substitute(destPath, params)
|
|
266
|
-
const search = new URLSearchParams(context.query)
|
|
264
|
+
const [destPath, destQuery] = splitDestination(rewrite.destination)
|
|
265
|
+
const { pathname: newPath, used: usedInPath } = substitute(destPath, params)
|
|
266
|
+
const search = new URLSearchParams(context.query)
|
|
267
267
|
|
|
268
268
|
// Destination-declared query params (?key=:val) are always applied.
|
|
269
|
-
let usedInQuery = false
|
|
269
|
+
let usedInQuery = false
|
|
270
270
|
if (destQuery) {
|
|
271
|
-
const destParams = new URLSearchParams(destQuery)
|
|
271
|
+
const destParams = new URLSearchParams(destQuery)
|
|
272
272
|
for (const [key, rawValue] of destParams.entries()) {
|
|
273
|
-
const { pathname: value, used } = substitute(rawValue, params)
|
|
274
|
-
if (used) usedInQuery = true
|
|
275
|
-
search.set(key, value)
|
|
273
|
+
const { pathname: value, used } = substitute(rawValue, params)
|
|
274
|
+
if (used) usedInQuery = true
|
|
275
|
+
search.set(key, value)
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
// Next rule: only auto-append unused params to the query when NONE of the
|
|
280
280
|
// params were used in the destination. If any param is used, nothing leaks.
|
|
281
|
-
const anyUsed = usedInPath.size > 0 || usedInQuery
|
|
281
|
+
const anyUsed = usedInPath.size > 0 || usedInQuery
|
|
282
282
|
if (!anyUsed) {
|
|
283
283
|
for (const [name, value] of Object.entries(params)) {
|
|
284
|
-
if (!search.has(name)) search.append(name, value)
|
|
284
|
+
if (!search.has(name)) search.append(name, value)
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -289,23 +289,20 @@ export function resolveCompatRewrite(
|
|
|
289
289
|
pathname: newPath,
|
|
290
290
|
search,
|
|
291
291
|
destination: /^https?:/.test(newPath) ? normalizeDestination(newPath, search) : undefined,
|
|
292
|
-
}
|
|
292
|
+
}
|
|
293
293
|
}
|
|
294
|
-
return undefined
|
|
294
|
+
return undefined
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
function normalizeDestination(
|
|
298
|
-
pathname: string,
|
|
299
|
-
search: URLSearchParams,
|
|
300
|
-
): string | undefined {
|
|
297
|
+
function normalizeDestination(pathname: string, search: URLSearchParams): string | undefined {
|
|
301
298
|
try {
|
|
302
|
-
const destination = new URL(pathname)
|
|
299
|
+
const destination = new URL(pathname)
|
|
303
300
|
for (const [key, value] of search.entries()) {
|
|
304
|
-
destination.searchParams.set(key, value)
|
|
301
|
+
destination.searchParams.set(key, value)
|
|
305
302
|
}
|
|
306
|
-
return destination.toString()
|
|
303
|
+
return destination.toString()
|
|
307
304
|
} catch {
|
|
308
|
-
return undefined
|
|
305
|
+
return undefined
|
|
309
306
|
}
|
|
310
307
|
}
|
|
311
308
|
|
|
@@ -314,22 +311,22 @@ export function evaluateConditions(
|
|
|
314
311
|
rule: { has: RewriteCondition[]; missing: RewriteCondition[] },
|
|
315
312
|
ctx: RewriteRequestContext,
|
|
316
313
|
): Record<string, string> | null {
|
|
317
|
-
const params: Record<string, string> = {}
|
|
314
|
+
const params: Record<string, string> = {}
|
|
318
315
|
for (const condition of rule.has) {
|
|
319
|
-
const captured = evaluateCondition(condition, ctx)
|
|
320
|
-
if (captured === null) return null
|
|
321
|
-
Object.assign(params, captured)
|
|
316
|
+
const captured = evaluateCondition(condition, ctx)
|
|
317
|
+
if (captured === null) return null
|
|
318
|
+
Object.assign(params, captured)
|
|
322
319
|
}
|
|
323
320
|
for (const condition of rule.missing) {
|
|
324
|
-
if (evaluateCondition(condition, ctx) !== null) return null
|
|
321
|
+
if (evaluateCondition(condition, ctx) !== null) return null
|
|
325
322
|
}
|
|
326
|
-
return params
|
|
323
|
+
return params
|
|
327
324
|
}
|
|
328
325
|
|
|
329
326
|
export function splitDestination(destination: string): [string, string | undefined] {
|
|
330
|
-
const index = destination.indexOf('?')
|
|
331
|
-
if (index === -1) return [destination, undefined]
|
|
332
|
-
return [destination.slice(0, index), destination.slice(index + 1)]
|
|
327
|
+
const index = destination.indexOf('?')
|
|
328
|
+
if (index === -1) return [destination, undefined]
|
|
329
|
+
return [destination.slice(0, index), destination.slice(index + 1)]
|
|
333
330
|
}
|
|
334
331
|
|
|
335
332
|
/**
|
|
@@ -340,11 +337,11 @@ export function substitute(
|
|
|
340
337
|
template: string,
|
|
341
338
|
params: Record<string, string>,
|
|
342
339
|
): { pathname: string; used: Set<string> } {
|
|
343
|
-
const used = new Set<string>()
|
|
340
|
+
const used = new Set<string>()
|
|
344
341
|
const result = template.replace(/:([A-Za-z_$][\w$]*)(\*|\+|\?)?/g, (whole, name: string) => {
|
|
345
|
-
if (!(name in params)) return whole
|
|
346
|
-
used.add(name)
|
|
347
|
-
return params[name] ?? ''
|
|
348
|
-
})
|
|
349
|
-
return { pathname: result, used }
|
|
342
|
+
if (!(name in params)) return whole
|
|
343
|
+
used.add(name)
|
|
344
|
+
return params[name] ?? ''
|
|
345
|
+
})
|
|
346
|
+
return { pathname: result, used }
|
|
350
347
|
}
|