@wular/pnext 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,1898 @@
|
|
|
1
|
+
// Core extension-point registry (CORE - imports ZERO compat).
|
|
2
|
+
//
|
|
3
|
+
// Core exposes a small set of mutable registries with working NO-OP defaults so a pure-core app (no
|
|
4
|
+
// next/* imports, no compat in pnext.config) runs unchanged. The compat layer populates them at the
|
|
5
|
+
// composition root through the single gated dynamic-import seam in compat-bootstrap.ts.
|
|
6
|
+
//
|
|
7
|
+
// The dependency edge is inverted: core NEVER imports compat. Instead compat imports core and
|
|
8
|
+
// registers its behavior here. Types stay intentionally structural (plain function shapes,
|
|
9
|
+
// `unknown` payloads) so core carries no type dependency on any compat module.
|
|
10
|
+
|
|
11
|
+
import type { OnLoadResult, Plugin } from 'esbuild';
|
|
12
|
+
import type { CompatAliasTarget, ResolvedConfig } from './config';
|
|
13
|
+
import type { StaticMetadataFile } from './routing/metadata';
|
|
14
|
+
import type {
|
|
15
|
+
BuildManifest,
|
|
16
|
+
ClientEntryReason,
|
|
17
|
+
RouteManifestEntry,
|
|
18
|
+
RouteParamValue,
|
|
19
|
+
} from './types';
|
|
20
|
+
import type { VerboseLogger } from './utils/verbose';
|
|
21
|
+
import { escapeRegex } from './utils/source';
|
|
22
|
+
|
|
23
|
+
/** Per-build wiring for the server plugin chain. */
|
|
24
|
+
export interface ServerEsbuildPluginOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Vendor react-server pass only: stub a `'use client'` file as a client
|
|
27
|
+
* reference. Composed into the chain's single claiming onLoad, so the scan
|
|
28
|
+
* costs no extra plugin-callback round trips.
|
|
29
|
+
*/
|
|
30
|
+
vendorClientBoundary?: (file: string) => Promise<OnLoadResult | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* The build's entries are realpath-resolved before esbuild sees them (the vendor chains), so
|
|
33
|
+
* workspace-link symlinks alone cannot put a symlinked importer in the graph - the symlink-import plugin
|
|
34
|
+
* may skip registration.
|
|
35
|
+
*/
|
|
36
|
+
realPathEntries?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The extension host - ONE instance owning every domain registry below. A normal process runs on the
|
|
40
|
+
// module-level default host, and the getX()/setX()/register* surface reads it, so core consumers
|
|
41
|
+
// never see the host at all. Anything serving more than one app in one process (the test suite, an
|
|
42
|
+
// embedder) creates an isolated host and swaps it in, so registrations cannot leak between apps.
|
|
43
|
+
|
|
44
|
+
export interface ExtensionHost {
|
|
45
|
+
compatMode: CompatModeExtensions;
|
|
46
|
+
render: RenderExtensions;
|
|
47
|
+
request: RequestExtensions;
|
|
48
|
+
runtime: RuntimeExtensions;
|
|
49
|
+
build: BuildExtensions;
|
|
50
|
+
routing: RoutingExtensions;
|
|
51
|
+
proxy: ProxyExtensions;
|
|
52
|
+
proxyResponse: ProxyResponseProtocol;
|
|
53
|
+
routerProtocol: RouterProtocolExtensions;
|
|
54
|
+
bundler: BundlerExtensions;
|
|
55
|
+
importAlias: ImportAliasExtensions;
|
|
56
|
+
asset: AssetExtensions;
|
|
57
|
+
/** Import-only loadable extensions; page extensions live on `routing`. */
|
|
58
|
+
loadable: string[];
|
|
59
|
+
/** Memoized union of the server-transform chain's sniff tokens. */
|
|
60
|
+
serverSniff?: { pattern: RegExp | undefined };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createExtensionHost(): ExtensionHost {
|
|
64
|
+
return {
|
|
65
|
+
compatMode: createCompatModeExtensions(),
|
|
66
|
+
render: createRenderExtensions(),
|
|
67
|
+
request: createRequestExtensions(),
|
|
68
|
+
runtime: createRuntimeExtensions(),
|
|
69
|
+
build: createBuildExtensions(),
|
|
70
|
+
routing: createRoutingExtensions(),
|
|
71
|
+
proxy: createProxyExtensions(),
|
|
72
|
+
proxyResponse: createProxyResponseProtocol(),
|
|
73
|
+
routerProtocol: createRouterProtocolExtensions(),
|
|
74
|
+
bundler: createBundlerExtensions(),
|
|
75
|
+
importAlias: createImportAliasExtensions(),
|
|
76
|
+
asset: createAssetExtensions(),
|
|
77
|
+
loadable: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let activeHost = createExtensionHost();
|
|
82
|
+
|
|
83
|
+
/** The host every accessor in this module reads. */
|
|
84
|
+
export function getExtensionHost(): ExtensionHost {
|
|
85
|
+
return activeHost;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Registration guards that live OUTSIDE any host — compat-bootstrap's promises,
|
|
89
|
+
// each register-* module's `registered` flag — must clear when the host is
|
|
90
|
+
// swapped, or the fresh host is left permanently empty. Module-global on
|
|
91
|
+
// purpose: the guards they clear are module-global too.
|
|
92
|
+
const resetHandlers: (() => void)[] = [];
|
|
93
|
+
|
|
94
|
+
// The detectors are regex sweeps over whole sources, and a route's layout chain is re-detected for
|
|
95
|
+
// every route below it. The answer depends only on the source and the registered detector set, so
|
|
96
|
+
// memoize on exactly that. Cleared whenever a detector registers.
|
|
97
|
+
const clientEntryReasonCache = new Map<string, ClientEntryReason[]>();
|
|
98
|
+
|
|
99
|
+
/** Run `handler` whenever the active host is swapped. */
|
|
100
|
+
export function onExtensionHostReset(handler: () => void): void {
|
|
101
|
+
resetHandlers.push(handler);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Tie a registry that lives outside the host (render/hooks, the CSS extras below) to the host's
|
|
106
|
+
* lifetime: compat populates it exactly like the host's own registries, so it must return to its
|
|
107
|
+
* core defaults with them. Restored in place rather than replaced - consumers may hold the object.
|
|
108
|
+
*/
|
|
109
|
+
export function restoreWithExtensionHost<T extends object>(registry: T): void {
|
|
110
|
+
const defaults = { ...registry };
|
|
111
|
+
onExtensionHostReset(() => {
|
|
112
|
+
for (const key of Object.keys(registry)) delete (registry as Record<string, unknown>)[key];
|
|
113
|
+
Object.assign(registry, defaults);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Install `host` as the active one; the returned function restores the previous. */
|
|
118
|
+
export function setActiveHost(host: ExtensionHost): () => void {
|
|
119
|
+
const previous = activeHost;
|
|
120
|
+
activeHost = host;
|
|
121
|
+
announceHostChange();
|
|
122
|
+
return () => {
|
|
123
|
+
activeHost = previous;
|
|
124
|
+
announceHostChange();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Swap in a fresh host, dropping every registration on the current one. */
|
|
129
|
+
export function resetExtensionHost(): ExtensionHost {
|
|
130
|
+
const host = createExtensionHost();
|
|
131
|
+
setActiveHost(host);
|
|
132
|
+
return host;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Run `callback` against `host`, restoring the previous host afterwards. */
|
|
136
|
+
export function withExtensionHost<T>(host: ExtensionHost, callback: () => T): T {
|
|
137
|
+
const restore = setActiveHost(host);
|
|
138
|
+
try {
|
|
139
|
+
const result = callback();
|
|
140
|
+
if (isThenable(result)) return Promise.resolve(result).finally(restore) as T;
|
|
141
|
+
restore();
|
|
142
|
+
return result;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
restore();
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function announceHostChange() {
|
|
150
|
+
for (const handler of resetHandlers) handler();
|
|
151
|
+
// Consumers latch the loadable-extension set (the server load plugin's filter
|
|
152
|
+
// regex); a swap changes it, so invalidate exactly as a registration would.
|
|
153
|
+
for (const listener of loadableExtensionListeners) listener();
|
|
154
|
+
// Memoized detector answers belong to the host that registered the detectors.
|
|
155
|
+
clientEntryReasonCache.clear();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Compat mode extensions — populated by compat/index.
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
export interface ReactCompilerOptions {
|
|
163
|
+
target: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface CompatModeExtensions {
|
|
167
|
+
nextEnabled: (config: ResolvedConfig) => boolean;
|
|
168
|
+
reactEnabled: (config: ResolvedConfig) => boolean;
|
|
169
|
+
reactCompilerOptions: (config: ResolvedConfig) => ReactCompilerOptions | undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createCompatModeExtensions(): CompatModeExtensions {
|
|
173
|
+
return {
|
|
174
|
+
nextEnabled: () => false,
|
|
175
|
+
reactEnabled: () => false,
|
|
176
|
+
reactCompilerOptions: () => undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function getCompatModeExtensions(): CompatModeExtensions {
|
|
181
|
+
return activeHost.compatMode;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function setCompatModeExtensions(overrides: Partial<CompatModeExtensions>): void {
|
|
185
|
+
Object.assign(activeHost.compatMode, overrides);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Render extensions — populated by compat/react + compat/actions + compat/cache
|
|
190
|
+
// (registerRenderExtensions).
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
/** Result of collecting cache metadata around a render (matches compat/cache/revalidate collectRenderCacheMeta). */
|
|
194
|
+
export interface RenderCacheMetaResult<T> {
|
|
195
|
+
value: T;
|
|
196
|
+
tags: string[];
|
|
197
|
+
revalidateSeconds?: number;
|
|
198
|
+
/** Aggregated `use cache` cacheLife expire/stale windows (min across scopes). */
|
|
199
|
+
expireSeconds?: number;
|
|
200
|
+
staleSeconds?: number;
|
|
201
|
+
noStore?: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Options threaded into a cache-meta collection scope. */
|
|
205
|
+
export interface CollectRenderMetaOptions {
|
|
206
|
+
fetchCache?: string;
|
|
207
|
+
refreshFetches?: boolean;
|
|
208
|
+
blockingStaleFetches?: boolean;
|
|
209
|
+
route?: string;
|
|
210
|
+
/** True for build-time static generation renders (prerender fetch caching). */
|
|
211
|
+
prerender?: boolean;
|
|
212
|
+
/** True when the render is a route handler (vs a page/layout render). */
|
|
213
|
+
handler?: boolean;
|
|
214
|
+
/** True when a static render must reject dynamic request APIs. */
|
|
215
|
+
dynamicError?: boolean;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface RenderExtensions {
|
|
219
|
+
/** Enrich the client route state embedded in a rendered document. */
|
|
220
|
+
clientRouteState: (
|
|
221
|
+
route: Pick<
|
|
222
|
+
RouteManifestEntry,
|
|
223
|
+
| 'route'
|
|
224
|
+
| 'file'
|
|
225
|
+
| 'hasStaticParams'
|
|
226
|
+
| 'usesRequest'
|
|
227
|
+
| 'segmentConfig'
|
|
228
|
+
| 'prerenderedParams'
|
|
229
|
+
| 'params'
|
|
230
|
+
| 'stream'
|
|
231
|
+
>,
|
|
232
|
+
state: {
|
|
233
|
+
route: string;
|
|
234
|
+
params: Record<string, RouteParamValue>;
|
|
235
|
+
catchAllOptional?: boolean;
|
|
236
|
+
pageVary?: string[];
|
|
237
|
+
},
|
|
238
|
+
) => {
|
|
239
|
+
route: string;
|
|
240
|
+
params: Record<string, RouteParamValue>;
|
|
241
|
+
catchAllOptional?: boolean;
|
|
242
|
+
/** Baked-shell vary set (see renderer `bakedShellPageVary`). */
|
|
243
|
+
pageVary?: string[];
|
|
244
|
+
staticChildren?: string[];
|
|
245
|
+
staticChildrenBySegment?: Record<string, string[]>;
|
|
246
|
+
prefetchKind?: 'shell' | 'eager';
|
|
247
|
+
/** `prefetch = 'allow-runtime'` — the shell is per-URL, not route-shared. */
|
|
248
|
+
runtimePrefetch?: boolean;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Wrap a single server-component invocation. Compat (react) installs the
|
|
252
|
+
* use()-thenable replay wrapper here (compat/react/preact withUseThenableState
|
|
253
|
+
* + render/index.tsx invokeServerComponentWithUse). Default: call through untouched.
|
|
254
|
+
*/
|
|
255
|
+
wrapServerComponentInvoke: <T>(invoke: () => T) => T;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Optional one-time render-module initialization side effect. Default: noop.
|
|
259
|
+
*/
|
|
260
|
+
onRenderInit: () => void;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Convert a value crossing the server->client boundary into a serializable
|
|
264
|
+
* action reference, or return undefined to leave it untouched. Compat
|
|
265
|
+
* (actions) returns the `{ [PROP_ACTION_MARKER]: id }` marker for tagged /
|
|
266
|
+
* inline / bound server-action functions (compat/actions server-tag +
|
|
267
|
+
* protocol + instances + client-plugin). Default: undefined (no rewrite).
|
|
268
|
+
*
|
|
269
|
+
* `context` carries the active render's config + a stable per-render key so
|
|
270
|
+
* compat can scope inline-action instance registration; core passes its
|
|
271
|
+
* RenderOptions object as the key (opaque to core).
|
|
272
|
+
*/
|
|
273
|
+
serializeServerActionProp: (
|
|
274
|
+
value: unknown,
|
|
275
|
+
context: ServerActionPropContext,
|
|
276
|
+
) => ActionRef | undefined;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Run `produce` inside a cache-meta collection scope, returning its value plus
|
|
280
|
+
* the tags/revalidate/no-store it recorded. Compat (cache) supplies the real
|
|
281
|
+
* AsyncLocalStorage collector (compat/cache/revalidate collectRenderCacheMeta).
|
|
282
|
+
* Default: run `produce`, report no tags.
|
|
283
|
+
*/
|
|
284
|
+
collectRenderMeta: <T>(
|
|
285
|
+
produce: () => Promise<T>,
|
|
286
|
+
options?: CollectRenderMetaOptions,
|
|
287
|
+
) => Promise<RenderCacheMetaResult<T>>;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The effective `use cache` stale window (seconds) recorded SO FAR by the surrounding
|
|
291
|
+
* `collectRenderMeta` scope, without consuming it. The renderer inlines this into a document's
|
|
292
|
+
* `__PNEXT_NAV_STATE__` so a HARD LOAD - which has no `x-nextjs-stale-time` header to read - seeds
|
|
293
|
+
* its caches with the route's real reuse window. The route manifest's `cacheLife` cannot serve
|
|
294
|
+
* here: it is captured FROM this very render and persisted only after the HTML exists.
|
|
295
|
+
*/
|
|
296
|
+
currentCacheStaleSeconds: () => number | undefined;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Override the generic error shell title. Compat uses this for Next's
|
|
300
|
+
* production client-error page copy; core keeps the compact default.
|
|
301
|
+
*/
|
|
302
|
+
genericErrorTitle: (context: GenericErrorTitleContext) => string | undefined;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Whether islands travel the wire as comment markers instead of elements. Core's wire format is
|
|
306
|
+
* elements-as-final: `<pnext-client>` hosts and the page slot ship as real elements the entry
|
|
307
|
+
* hydrates in place, so a page with no islands ships no inline JavaScript at all. Next asserts
|
|
308
|
+
* structural selectors against the live DOM (`body > p`), which a wrapper element would break, so
|
|
309
|
+
* compat turns the elements into comment pairs plus a bootstrap that materializes them back.
|
|
310
|
+
*/
|
|
311
|
+
islandCommentWireFormat: () => boolean;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Serialize a caught render error into the `{ name, message, digest }` shape
|
|
315
|
+
* handed to error.js / global-error.js boundaries. Compat implements Next's
|
|
316
|
+
* digest protocol: hash server errors into a stable digest, pass through a
|
|
317
|
+
* user-supplied `.digest`, redact the message in production (React #441 text),
|
|
318
|
+
* and log the original stack once to the server console. Default: return
|
|
319
|
+
* undefined so core keeps its own compact serialization.
|
|
320
|
+
*/
|
|
321
|
+
serializeError: (context: SerializeErrorContext) => SerializedError | undefined;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Next-pixel-exact default UI for an HTTP-access fallback (not-found 404,
|
|
325
|
+
* forbidden 403, unauthorized 401) when the app ships no boundary file.
|
|
326
|
+
* Returns a render tree (preact VNode, opaque to core as `unknown`) matching
|
|
327
|
+
* Next's HTTPAccessErrorFallback (`h1.next-error-h1` + `h2` message). Core
|
|
328
|
+
* keeps a compact built-in when this is absent. `status` is the HTTP status,
|
|
329
|
+
* `message` the copy shown in the `h2`.
|
|
330
|
+
*/
|
|
331
|
+
httpAccessFallbackUi: (status: number, message: string) => unknown;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Next-pixel-exact built-in global-error document ("This page couldn't load"
|
|
335
|
+
* + digest footer) rendered as a whole HTML document when an error escapes the
|
|
336
|
+
* root layout and the app ships no global-error.*. Returns a render tree
|
|
337
|
+
* (preact VNode, opaque to core) or undefined to keep core's compact fallback.
|
|
338
|
+
*/
|
|
339
|
+
defaultGlobalErrorUi: (error: SerializedError) => unknown;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Inline bootstrap markup appended to a global-error document (built-in
|
|
343
|
+
* fallback or a user global-error.*) so the client observes the escaped
|
|
344
|
+
* error as an uncaught exception, matching Next's hydration-replay signal
|
|
345
|
+
* (Playwright `pageerror`). Returns an HTML string (typically a `<script>`
|
|
346
|
+
* tag) or undefined to render the document with no bootstrap (core default).
|
|
347
|
+
*/
|
|
348
|
+
globalErrorReportScript: (error: SerializedError) => string | undefined;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Wrap a non-Error thrown value (a `throw undefined`/`null`/'msg' from a
|
|
352
|
+
* server component) into the Error the error pipeline logs and serializes.
|
|
353
|
+
* Compat mirrors Next's RSC transport wrapper: message String(value), a
|
|
354
|
+
* synthetic `at stringify (<anonymous>)` stack, and a raw-value tag so the
|
|
355
|
+
* digest gains Next's `@E394` suffix for nullish throws. Default: undefined
|
|
356
|
+
* (core wraps with a plain `new Error(String(value))`).
|
|
357
|
+
*/
|
|
358
|
+
wrapThrownValue: (value: unknown) => Error | undefined;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Format the server-console log line for a render error that carries a boundary digest. Compat
|
|
362
|
+
* emits Next's exact prod shape, plus any environment tag the error carries (an error funneled out
|
|
363
|
+
* of a 'use cache' scope inspects as `{ environmentName: 'Cache', digest }`).
|
|
364
|
+
*/
|
|
365
|
+
formatErrorLog: (trace: string, digest: string, error?: Error) => string | undefined;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* True when the app opted into the global-not-found convention: an unmatched URL then renders the
|
|
369
|
+
* global-not-found document, and with no global-not-found.* file it falls back to the BUILT-IN
|
|
370
|
+
* default 404, never the app's root not-found.* boundary (reserved for explicit notFound() calls).
|
|
371
|
+
*/
|
|
372
|
+
globalNotFoundEnabled: () => boolean;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Wrap a render's concrete `params` / `searchParams` object in a tracking view so compat can record
|
|
376
|
+
* which of them the render actually reads. Core never interprets the result - it just hands the
|
|
377
|
+
* returned object to user code in place of the original. Default: identity.
|
|
378
|
+
*/
|
|
379
|
+
trackVaryParams: <T extends Record<string, unknown>>(
|
|
380
|
+
value: T,
|
|
381
|
+
context: VaryParamsTrackingContext,
|
|
382
|
+
) => T;
|
|
383
|
+
|
|
384
|
+
/** Optional opaque state lifecycle around partial prerenders. */
|
|
385
|
+
prerenderSidecar: PrerenderSidecarExtension;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** What kind of object `trackVaryParams` is being handed, plus route shape. */
|
|
389
|
+
export interface VaryParamsTrackingContext {
|
|
390
|
+
kind: 'params' | 'searchParams';
|
|
391
|
+
/**
|
|
392
|
+
* Which route segment is about to read this object. Compat keys its
|
|
393
|
+
* per-segment vary sets on it, so a layout that reads more params than the
|
|
394
|
+
* page does not force the page's cache entry to vary on them too. Defaults
|
|
395
|
+
* to the leaf page.
|
|
396
|
+
*/
|
|
397
|
+
segment?: 'page' | 'layout';
|
|
398
|
+
/**
|
|
399
|
+
* Name of an OPTIONAL catch-all param of this route (`[[...slug]]`), when
|
|
400
|
+
* any. Its key is absent from `params` at the empty value, so tracking it
|
|
401
|
+
* needs the name up front.
|
|
402
|
+
*/
|
|
403
|
+
optionalCatchAllParam?: string;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export interface GenericErrorTitleContext {
|
|
407
|
+
error: Error;
|
|
408
|
+
dev: boolean;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Context handed to the error-serialization extension. */
|
|
412
|
+
export interface SerializeErrorContext {
|
|
413
|
+
error: Error;
|
|
414
|
+
dev: boolean;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** The `{ name, message, digest }` object passed to an error boundary component. */
|
|
418
|
+
export interface SerializedError {
|
|
419
|
+
name: string;
|
|
420
|
+
message: string;
|
|
421
|
+
digest?: string;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Opaque scope handed to serializeServerActionProp (config + per-render key). */
|
|
425
|
+
export interface ServerActionPropContext {
|
|
426
|
+
config: ResolvedConfig;
|
|
427
|
+
/** Stable per-render identity; core passes its RenderOptions object. */
|
|
428
|
+
renderKey: object;
|
|
429
|
+
/**
|
|
430
|
+
* Only answer for a function that is IDENTIFIABLY a server action (a tagged
|
|
431
|
+
* module export, a compile-tagged inline `'use server'` closure, an
|
|
432
|
+
* explicitly overridden id). An unidentifiable function must return
|
|
433
|
+
* undefined instead of being registered as a live per-render instance.
|
|
434
|
+
*
|
|
435
|
+
* Used where the call site cannot distinguish a server action from an
|
|
436
|
+
* ordinary client closure: `<button onClick={fn}>` inside a `'use client'`
|
|
437
|
+
* subtree is always a client handler, and registering it would both stamp
|
|
438
|
+
* the element with a bogus action wire and shift the occurrence indices real
|
|
439
|
+
* inline actions are keyed on.
|
|
440
|
+
*/
|
|
441
|
+
identifiedOnly?: boolean;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Serializable marker a compat client runtime revives back into an action. */
|
|
445
|
+
export type ActionRef = Record<string, unknown>;
|
|
446
|
+
|
|
447
|
+
export interface PrerenderSidecarContext {
|
|
448
|
+
outPath: string;
|
|
449
|
+
routeId: string;
|
|
450
|
+
routePath: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export interface PrerenderSidecarExtension {
|
|
454
|
+
begin: (context: PrerenderSidecarContext) => void | Promise<void>;
|
|
455
|
+
collect: (context: PrerenderSidecarContext) => Promise<unknown>;
|
|
456
|
+
persist: (context: PrerenderSidecarContext, value: unknown) => Promise<void>;
|
|
457
|
+
seed: (context: PrerenderSidecarContext) => void | Promise<void>;
|
|
458
|
+
/**
|
|
459
|
+
* Whether the persisted sidecar/shell went stale. `true` = hard staleness
|
|
460
|
+
* (block and regenerate before responding); `'soft'` = stale-while-
|
|
461
|
+
* revalidate (serve the stale shell once, regenerate in the background).
|
|
462
|
+
*/
|
|
463
|
+
isStale: (context: PrerenderSidecarContext) => boolean | 'soft' | Promise<boolean | 'soft'>;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function createRenderExtensions(): RenderExtensions {
|
|
467
|
+
return {
|
|
468
|
+
clientRouteState: (_route, state) => state,
|
|
469
|
+
wrapServerComponentInvoke: invoke => invoke(),
|
|
470
|
+
onRenderInit: () => undefined,
|
|
471
|
+
serializeServerActionProp: () => undefined,
|
|
472
|
+
collectRenderMeta: async (produce, _options) => ({ value: await produce(), tags: [] }),
|
|
473
|
+
currentCacheStaleSeconds: () => undefined,
|
|
474
|
+
genericErrorTitle: () => undefined,
|
|
475
|
+
islandCommentWireFormat: () => false,
|
|
476
|
+
serializeError: () => undefined,
|
|
477
|
+
httpAccessFallbackUi: () => undefined,
|
|
478
|
+
defaultGlobalErrorUi: () => undefined,
|
|
479
|
+
globalErrorReportScript: () => undefined,
|
|
480
|
+
wrapThrownValue: () => undefined,
|
|
481
|
+
formatErrorLog: () => undefined,
|
|
482
|
+
globalNotFoundEnabled: () => false,
|
|
483
|
+
trackVaryParams: value => value,
|
|
484
|
+
prerenderSidecar: {
|
|
485
|
+
begin: () => undefined,
|
|
486
|
+
collect: () => Promise.resolve(undefined),
|
|
487
|
+
persist: () => Promise.resolve(),
|
|
488
|
+
seed: () => undefined,
|
|
489
|
+
isStale: () => false,
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function getRenderExtensions(): RenderExtensions {
|
|
495
|
+
return activeHost.render;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function setRenderExtensions(overrides: Partial<RenderExtensions>): void {
|
|
499
|
+
Object.assign(activeHost.render, overrides);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// Request extensions — populated by compat/actions + compat/next/rewrites +
|
|
504
|
+
// compat/cache (registerActionExtensions).
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* A request interceptor runs before route matching. Return a `Response` to
|
|
509
|
+
* short-circuit, `{ request }` to swap the request (e.g. a rewrite) and
|
|
510
|
+
* continue, or undefined to pass through unchanged. Ordered; core runs them in
|
|
511
|
+
* registration order.
|
|
512
|
+
*/
|
|
513
|
+
export type RequestInterceptor = (
|
|
514
|
+
request: Request,
|
|
515
|
+
ctx: RequestInterceptorContext,
|
|
516
|
+
) => Promise<Response | { request: Request } | undefined>;
|
|
517
|
+
|
|
518
|
+
/** Context handed to each request interceptor. */
|
|
519
|
+
export interface RequestInterceptorContext {
|
|
520
|
+
config: ResolvedConfig;
|
|
521
|
+
/**
|
|
522
|
+
* The request's path falls OUTSIDE the configured basePath, so core would
|
|
523
|
+
* otherwise 404 it. Only rules that opt out of the basePath (next.config's
|
|
524
|
+
* `basePath: false` rewrites/redirects) may answer such a request; every other
|
|
525
|
+
* interceptor must decline. Absent (falsy) on every in-app request.
|
|
526
|
+
*/
|
|
527
|
+
outsideBasePath?: boolean;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Response finalizers - ordered fns run before the first flush of every response-producing call site
|
|
531
|
+
// (page renders, route handlers, static file serving, redirects, 404s). Finalizers observe the
|
|
532
|
+
// outgoing status/headers plus request info and may mutate them in place. Core registers none;
|
|
533
|
+
// compat adds RSC Vary values, x-nextjs-* headers and exact ISR cache-control strings here.
|
|
534
|
+
|
|
535
|
+
/** How the matched route was served — shapes the finalizer decisions. */
|
|
536
|
+
export type ResponseRouteKind = 'html' | 'data' | 'static-asset' | 'route-handler';
|
|
537
|
+
|
|
538
|
+
/** The route's caching disposition when known (page/handler renders). */
|
|
539
|
+
export type ResponseRouteMode = 'static' | 'isr' | 'dynamic';
|
|
540
|
+
|
|
541
|
+
/** Read-only request facts a finalizer keys its header/status decisions on. */
|
|
542
|
+
export interface ResponseFinalizerRequest {
|
|
543
|
+
method: string;
|
|
544
|
+
url: URL;
|
|
545
|
+
headers: Headers;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Mutable finalizer context handed to each finalizer before the first flush.
|
|
550
|
+
* `status` and `headers` are the outgoing response's own status/headers; a
|
|
551
|
+
* finalizer mutates them in place (set `status` to override, append/merge on
|
|
552
|
+
* `headers`). Classification fields are optional so compat can grow (e.g. RSC
|
|
553
|
+
* kind, rewrite tracking) with no core change; ad-hoc compat hints ride on
|
|
554
|
+
* `hints`.
|
|
555
|
+
*/
|
|
556
|
+
export interface ResponseFinalizerContext {
|
|
557
|
+
request: ResponseFinalizerRequest;
|
|
558
|
+
/** How the response was produced. */
|
|
559
|
+
routeKind: ResponseRouteKind;
|
|
560
|
+
/** Caching disposition of the matched route, when core knows it. */
|
|
561
|
+
routeMode?: ResponseRouteMode;
|
|
562
|
+
/** Outgoing status; assign to override before first flush. */
|
|
563
|
+
status: number;
|
|
564
|
+
/** Outgoing headers; mutate in place (merge Vary, set Cache-Control, ...). */
|
|
565
|
+
headers: Headers;
|
|
566
|
+
/** Compat-only classification hints (RSC kind, rewrite target, ...). */
|
|
567
|
+
hints?: Record<string, unknown>;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** A response finalizer runs before first flush; mutations land on the response. */
|
|
571
|
+
export type ResponseFinalizer = (ctx: ResponseFinalizerContext) => void | Promise<void>;
|
|
572
|
+
|
|
573
|
+
/** Request facts handed to the error funnel (Next's onRequestError request arg). */
|
|
574
|
+
export interface RequestErrorInfo {
|
|
575
|
+
method: string;
|
|
576
|
+
url: string;
|
|
577
|
+
headers: Headers;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Work-unit facts handed to the error funnel (phase, matched route kind). */
|
|
581
|
+
export interface RequestErrorContext {
|
|
582
|
+
/** The active work-unit phase when the error was caught, if any. */
|
|
583
|
+
phase?: string;
|
|
584
|
+
/** How the failing request was being served. */
|
|
585
|
+
routeKind?: ResponseRouteKind;
|
|
586
|
+
/** Optional compat-neutral details forwarded by the active work unit. */
|
|
587
|
+
renderSource?: string;
|
|
588
|
+
revalidateReason?: string;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export interface RequestExtensions {
|
|
592
|
+
/** Ordered interceptors run before route matching (action dispatch, rewrites). */
|
|
593
|
+
interceptors: RequestInterceptor[];
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Fixed first-request costs an extension would otherwise pay inside the first
|
|
597
|
+
* interceptor (compat registers the `@opentelemetry/api` resolve). Dev runs
|
|
598
|
+
* them in the background after Ready; they must be idempotent, cheap to skip,
|
|
599
|
+
* and never route-specific.
|
|
600
|
+
*/
|
|
601
|
+
warmHooks: ((config: ResolvedConfig) => void)[];
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Interceptors for a request whose path lies OUTSIDE the configured basePath - one core 404s before
|
|
605
|
+
* any routing. Only rules that explicitly opt out of the basePath belong here; everything else must
|
|
606
|
+
* not see such a request. Never consulted without a basePath.
|
|
607
|
+
*/
|
|
608
|
+
outsideBasePathInterceptors: RequestInterceptor[];
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Ordered response finalizers run before the first flush on every
|
|
612
|
+
* response-producing call site. Each observes request info + the outgoing
|
|
613
|
+
* status/headers and may mutate status/headers in place. Compat appends RSC
|
|
614
|
+
* Vary values, x-nextjs-* headers, and exact ISR cache-control strings.
|
|
615
|
+
*/
|
|
616
|
+
responseFinalizers: ResponseFinalizer[];
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* The request-level error funnel. Core calls this exactly once from the single place it catches a
|
|
620
|
+
* request error, with the error, request info and work-unit context. Compat classifies control-flow
|
|
621
|
+
* errors vs real errors and implements onRequestError. Core does NOT swallow the error - it still
|
|
622
|
+
* surfaces its own 500.
|
|
623
|
+
*/
|
|
624
|
+
onRequestError: (
|
|
625
|
+
error: unknown,
|
|
626
|
+
requestInfo: RequestErrorInfo,
|
|
627
|
+
context: RequestErrorContext,
|
|
628
|
+
) => void | Promise<void>;
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Whether a prebuilt static file for `pathname` (written at `mtimeMs`, with the given cache `tags`)
|
|
632
|
+
* has been invalidated since it was built - the ISR on-demand staleness check. Default: never stale.
|
|
633
|
+
*/
|
|
634
|
+
staticStaleness: (pathname: string, mtimeMs: number, tags: readonly string[]) => boolean;
|
|
635
|
+
/** Why the same static file is stale, when available. */
|
|
636
|
+
staticStalenessReason: (
|
|
637
|
+
pathname: string,
|
|
638
|
+
mtimeMs: number,
|
|
639
|
+
tags: readonly string[],
|
|
640
|
+
) => 'stale' | 'on-demand' | 'soft' | undefined;
|
|
641
|
+
|
|
642
|
+
/** Trigger immediate ISR regeneration for pages API res.revalidate(). */
|
|
643
|
+
onDemandRevalidatePath: (pathname: string) => void | Promise<void>;
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* The preview-mode id an `x-prerender-revalidate` request header must match
|
|
647
|
+
* for Next's on-demand revalidation semantics: middleware is skipped and the
|
|
648
|
+
* route re-renders fresh (`x-nextjs-cache: REVALIDATED`). Compat registers
|
|
649
|
+
* the id it writes into prerender-manifest.json. Default: none (no bypass).
|
|
650
|
+
*/
|
|
651
|
+
revalidateBypassToken: () => string | undefined;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function createRequestExtensions(): RequestExtensions {
|
|
655
|
+
return {
|
|
656
|
+
interceptors: [],
|
|
657
|
+
warmHooks: [],
|
|
658
|
+
outsideBasePathInterceptors: [],
|
|
659
|
+
responseFinalizers: [],
|
|
660
|
+
onRequestError: () => undefined,
|
|
661
|
+
staticStaleness: () => false,
|
|
662
|
+
staticStalenessReason: () => undefined,
|
|
663
|
+
onDemandRevalidatePath: () => undefined,
|
|
664
|
+
revalidateBypassToken: () => undefined,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function getRequestExtensions(): RequestExtensions {
|
|
669
|
+
return activeHost.request;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Append request interceptors in order (compat registers action dispatch + rewrites). */
|
|
673
|
+
export function registerRequestInterceptors(...interceptors: RequestInterceptor[]): void {
|
|
674
|
+
activeHost.request.interceptors.push(...interceptors);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Register a fixed first-request cost the dev warm can pay in the background. */
|
|
678
|
+
export function registerRequestWarmHooks(...hooks: ((config: ResolvedConfig) => void)[]): void {
|
|
679
|
+
activeHost.request.warmHooks.push(...hooks);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Pay every registered first-request cost up front. Failures are never fatal. */
|
|
683
|
+
export function runRequestWarmHooks(config: ResolvedConfig): void {
|
|
684
|
+
for (const hook of activeHost.request.warmHooks) {
|
|
685
|
+
try {
|
|
686
|
+
hook(config);
|
|
687
|
+
} catch {
|
|
688
|
+
// A warm that fails just leaves the cost on the first request.
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** Append interceptors that may answer a request from outside the basePath. */
|
|
694
|
+
export function registerOutsideBasePathInterceptors(...interceptors: RequestInterceptor[]): void {
|
|
695
|
+
activeHost.request.outsideBasePathInterceptors.push(...interceptors);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** Append response finalizers in order (compat registers RSC headers + ISR cache-control). */
|
|
699
|
+
export function registerResponseFinalizers(...finalizers: ResponseFinalizer[]): void {
|
|
700
|
+
activeHost.request.responseFinalizers.push(...finalizers);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Register the preview-mode id that authorizes on-demand revalidate requests. */
|
|
704
|
+
export function registerRevalidateBypassToken(token: () => string | undefined): void {
|
|
705
|
+
activeHost.request.revalidateBypassToken = token;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Run every registered response finalizer against `response` before it is
|
|
710
|
+
* flushed, then return the response to send. Finalizers see request info + the
|
|
711
|
+
* outgoing status/headers and mutate them in place; when a finalizer changes the
|
|
712
|
+
* status (immutable on a Response) the body is re-wrapped with the new status. A
|
|
713
|
+
* no-finalizer app (pure core) returns the response untouched.
|
|
714
|
+
*/
|
|
715
|
+
export async function finalizeResponse(
|
|
716
|
+
response: Response,
|
|
717
|
+
request: ResponseFinalizerRequest,
|
|
718
|
+
info: {
|
|
719
|
+
routeKind: ResponseRouteKind;
|
|
720
|
+
routeMode?: ResponseRouteMode;
|
|
721
|
+
hints?: Record<string, unknown>;
|
|
722
|
+
},
|
|
723
|
+
): Promise<Response> {
|
|
724
|
+
const finalizers = activeHost.request.responseFinalizers;
|
|
725
|
+
if (finalizers.length === 0) return response;
|
|
726
|
+
const ctx: ResponseFinalizerContext = {
|
|
727
|
+
request,
|
|
728
|
+
routeKind: info.routeKind,
|
|
729
|
+
...(info.routeMode ? { routeMode: info.routeMode } : {}),
|
|
730
|
+
...(info.hints ? { hints: info.hints } : {}),
|
|
731
|
+
status: response.status,
|
|
732
|
+
headers: response.headers,
|
|
733
|
+
};
|
|
734
|
+
for (const finalizer of finalizers) await finalizer(ctx);
|
|
735
|
+
if (ctx.status === response.status) return response;
|
|
736
|
+
return new Response(response.body, {
|
|
737
|
+
status: ctx.status,
|
|
738
|
+
statusText: response.statusText,
|
|
739
|
+
headers: response.headers,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export function setRequestExtensions(
|
|
744
|
+
overrides: Partial<Omit<RequestExtensions, 'interceptors' | 'responseFinalizers'>>,
|
|
745
|
+
): void {
|
|
746
|
+
Object.assign(activeHost.request, overrides);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Report a request-level error through the compat error funnel. Core calls this from the single place
|
|
751
|
+
* it catches request errors. Never throws - reporting failures are swallowed so the caller's own
|
|
752
|
+
* error handling proceeds.
|
|
753
|
+
*/
|
|
754
|
+
export async function reportRequestError(
|
|
755
|
+
error: unknown,
|
|
756
|
+
requestInfo: RequestErrorInfo,
|
|
757
|
+
context: RequestErrorContext,
|
|
758
|
+
): Promise<void> {
|
|
759
|
+
try {
|
|
760
|
+
await activeHost.request.onRequestError(error, requestInfo, context);
|
|
761
|
+
} catch (reportError) {
|
|
762
|
+
console.error('onRequestError funnel failed:', reportError);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// ---------------------------------------------------------------------------
|
|
767
|
+
// Runtime extensions — populated by compat/edge-runtime.
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
|
|
770
|
+
export interface RuntimeExtensions {
|
|
771
|
+
/** Run user code with Edge-runtime globals installed. Default: no-op. */
|
|
772
|
+
withEdgeRuntime: <T>(callback: () => T) => T;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function createRuntimeExtensions(): RuntimeExtensions {
|
|
776
|
+
return { withEdgeRuntime: callback => callback() };
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function getRuntimeExtensions(): RuntimeExtensions {
|
|
780
|
+
return activeHost.runtime;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export function setRuntimeExtensions(overrides: Partial<RuntimeExtensions>): void {
|
|
784
|
+
Object.assign(activeHost.runtime, overrides);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
export function withRouteRuntime<T>(runtime: string | undefined, callback: () => T): T {
|
|
788
|
+
const edge = runtime === 'edge' || runtime === 'experimental-edge';
|
|
789
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
790
|
+
const previous = process.env.NEXT_RUNTIME;
|
|
791
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
792
|
+
process.env.NEXT_RUNTIME = edge ? 'edge' : 'nodejs';
|
|
793
|
+
try {
|
|
794
|
+
const result = edge ? activeHost.runtime.withEdgeRuntime(callback) : callback();
|
|
795
|
+
if (isThenable(result)) {
|
|
796
|
+
return Promise.resolve(result).finally(() => restoreNextRuntime(previous)) as T;
|
|
797
|
+
}
|
|
798
|
+
restoreNextRuntime(previous);
|
|
799
|
+
return result;
|
|
800
|
+
} catch (error) {
|
|
801
|
+
restoreNextRuntime(previous);
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function restoreNextRuntime(value: string | undefined) {
|
|
807
|
+
if (value === undefined) {
|
|
808
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
809
|
+
delete process.env.NEXT_RUNTIME;
|
|
810
|
+
} else {
|
|
811
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
812
|
+
process.env.NEXT_RUNTIME = value;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function isThenable<T>(value: T): value is T & PromiseLike<unknown> {
|
|
817
|
+
return typeof (value as { then?: unknown } | null | undefined)?.then === 'function';
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ---------------------------------------------------------------------------
|
|
821
|
+
// Build extensions — populated by compat/actions + compat/cache/fetch-patch
|
|
822
|
+
// (registerActionExtensions + registerCacheExtensions).
|
|
823
|
+
// ---------------------------------------------------------------------------
|
|
824
|
+
|
|
825
|
+
/** Minimal build context handed to each build step (kept structural on purpose). */
|
|
826
|
+
export interface BuildStepContext {
|
|
827
|
+
config: ResolvedConfig;
|
|
828
|
+
routes: unknown[];
|
|
829
|
+
manifest: unknown;
|
|
830
|
+
log: BuildStepLogger;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* The verbose build logger's step/log surface handed to each build step. Aliased
|
|
835
|
+
* to the concrete VerboseLogger (core utility) so build.ts can pass its logger
|
|
836
|
+
* verbatim without a structural-variance mismatch on `step`.
|
|
837
|
+
*/
|
|
838
|
+
export type BuildStepLogger = VerboseLogger;
|
|
839
|
+
|
|
840
|
+
/** A build step runs during `pnext build` (action discovery/bundling, manifest writes). */
|
|
841
|
+
export type BuildStep = ((ctx: BuildStepContext) => Promise<void>) & {
|
|
842
|
+
/**
|
|
843
|
+
* Opt in to running under the route-facts scan instead of after it. An early step may read `ctx.config`
|
|
844
|
+
* and route *paths* only - the facts (imports, use-client, CSS) are still being materialized while it runs.
|
|
845
|
+
*/
|
|
846
|
+
early?: boolean;
|
|
847
|
+
/**
|
|
848
|
+
* Gate the build: run serially before any artifact-producing stage, so this
|
|
849
|
+
* step's diagnostics always precede a downstream failure it would have
|
|
850
|
+
* explained. Non-gate steps run concurrently with the client bundle stage.
|
|
851
|
+
*/
|
|
852
|
+
gate?: boolean;
|
|
853
|
+
};
|
|
854
|
+
export type BuildCompleteHook = (ctx: {
|
|
855
|
+
config: ResolvedConfig;
|
|
856
|
+
manifest: BuildManifest;
|
|
857
|
+
log: BuildStepLogger;
|
|
858
|
+
}) => Promise<void>;
|
|
859
|
+
|
|
860
|
+
export interface CacheLifeStashLike {
|
|
861
|
+
expireSeconds?: number;
|
|
862
|
+
staleSeconds?: number;
|
|
863
|
+
revalidateSeconds?: number;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export interface SegmentPrefetchTree {
|
|
867
|
+
tree: SegmentTreeNodeLike;
|
|
868
|
+
staleTime: number;
|
|
869
|
+
isStatic: boolean;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
export interface SegmentTreeNodeLike {
|
|
873
|
+
name: string;
|
|
874
|
+
param: null | { type: 'd' | 'c' | 'oc'; key: string | null; siblings: string[] | null };
|
|
875
|
+
slots: Record<string, SegmentTreeNodeLike> | null;
|
|
876
|
+
prefetchHints: number;
|
|
877
|
+
sizeBytes?: number;
|
|
878
|
+
segmentPath?: string;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export interface SegmentMetaLike {
|
|
882
|
+
status: number;
|
|
883
|
+
staleTime: number;
|
|
884
|
+
postponed: boolean;
|
|
885
|
+
segmentPaths: string[];
|
|
886
|
+
segmentSizes?: Record<string, number>;
|
|
887
|
+
inlinedSegmentPaths?: string[];
|
|
888
|
+
prefetchHints?: Record<string, number>;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Opaque vary-params result of a tracked build render. Core reads only
|
|
893
|
+
* `params`/`search` (to decide whether the set is worth persisting); the full
|
|
894
|
+
* shape lives in compat.
|
|
895
|
+
*/
|
|
896
|
+
export interface BuildResponseVary {
|
|
897
|
+
params: string[];
|
|
898
|
+
search: boolean;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export interface BuildCompatExtensions {
|
|
902
|
+
/**
|
|
903
|
+
* Run a build render inside a vary-params tracking scope. Pure-core default:
|
|
904
|
+
* no tracking (`vary` undefined).
|
|
905
|
+
*/
|
|
906
|
+
withVaryParamsTracking: <T>(
|
|
907
|
+
produce: () => Promise<T>,
|
|
908
|
+
) => Promise<{ value: T; vary?: BuildResponseVary }>;
|
|
909
|
+
/** The wire vary names for one segment kind of a tracked render. */
|
|
910
|
+
varyNamesFor: (vary: BuildResponseVary, kind: 'body' | 'head' | 'page' | 'layout') => string[];
|
|
911
|
+
warnMetadataIssues: (input: {
|
|
912
|
+
appPath: string;
|
|
913
|
+
routes: RouteManifestEntry[];
|
|
914
|
+
staticMetadataFiles: StaticMetadataFile[];
|
|
915
|
+
}) => Promise<void>;
|
|
916
|
+
nextOutputExport: () => boolean;
|
|
917
|
+
/** The configured `output` mode when it blocks `next start` ('export' | 'standalone'), else undefined. */
|
|
918
|
+
nextOutputMode: () => 'export' | 'standalone' | undefined;
|
|
919
|
+
nextScriptWorkersEnabled: () => boolean;
|
|
920
|
+
defaultExpireTimeSeconds: () => number | undefined;
|
|
921
|
+
takeCacheLifeStash: () => CacheLifeStashLike | undefined;
|
|
922
|
+
/**
|
|
923
|
+
* The `Link` header value for the font preloads stashed during the current
|
|
924
|
+
* render work unit (next/font), or undefined when none. A static prerender
|
|
925
|
+
* never runs the response finalizer that would otherwise flush these, so the
|
|
926
|
+
* build bakes the header into the manifest. Must be called inside the render
|
|
927
|
+
* work unit before it unwinds. No-op default for pure-core apps.
|
|
928
|
+
*/
|
|
929
|
+
takeFontLinkHeader: () => string | undefined;
|
|
930
|
+
normalizeStaticParamsModule: (module: Record<string, unknown>) => Record<string, unknown>;
|
|
931
|
+
defaultDynamicStaleTimeSeconds: number;
|
|
932
|
+
defaultStaticStaleTimeSeconds: number;
|
|
933
|
+
buildRootTreePrefetch: (options: {
|
|
934
|
+
pathname: string;
|
|
935
|
+
isStatic: boolean;
|
|
936
|
+
staleTimeSeconds?: number;
|
|
937
|
+
buildId?: string;
|
|
938
|
+
routeId?: string;
|
|
939
|
+
bodySizeBytes?: number;
|
|
940
|
+
inlineBudgetBytes?: number;
|
|
941
|
+
runtimePrefetch?: boolean;
|
|
942
|
+
postponed?: boolean;
|
|
943
|
+
}) => SegmentPrefetchTree;
|
|
944
|
+
rootTreePrefetchText: (payload: SegmentPrefetchTree, format?: 'json' | 'flight') => string;
|
|
945
|
+
buildSegmentMeta: (options: {
|
|
946
|
+
status: number;
|
|
947
|
+
staleTime: number;
|
|
948
|
+
postponed: boolean;
|
|
949
|
+
bodySizeBytes?: number;
|
|
950
|
+
inlineBudgetBytes?: number;
|
|
951
|
+
prefetchHints?: Record<string, number>;
|
|
952
|
+
}) => SegmentMetaLike;
|
|
953
|
+
/**
|
|
954
|
+
* `--experimental-build-mode generate` prerender diagnostics: Next's exact
|
|
955
|
+
* blocking-prerender error block for a cacheComponents route that cannot be
|
|
956
|
+
* prerendered, or undefined when the route is fine. Pure-core default: no
|
|
957
|
+
* diagnostics (generate never fails).
|
|
958
|
+
*/
|
|
959
|
+
diagnoseCacheComponentsPrerender: (input: {
|
|
960
|
+
route: string;
|
|
961
|
+
pageFile: string;
|
|
962
|
+
appPath: string;
|
|
963
|
+
debugPrerender: boolean;
|
|
964
|
+
}) => string | undefined;
|
|
965
|
+
/**
|
|
966
|
+
* The shared "Error occurred prerendering page" + export-error footer.
|
|
967
|
+
* `omitErrorLine` drops the leading error line for diagnostics that already
|
|
968
|
+
* printed it (see `diagnosticLeadsWithErrorLine`).
|
|
969
|
+
*/
|
|
970
|
+
prerenderFailureFooter: (
|
|
971
|
+
route: string,
|
|
972
|
+
debugPrerender: boolean,
|
|
973
|
+
omitErrorLine?: boolean,
|
|
974
|
+
) => string;
|
|
975
|
+
/** Whether a diagnostic block already opens with the prerender-error line. */
|
|
976
|
+
diagnosticLeadsWithErrorLine: (diagnostic: string) => boolean;
|
|
977
|
+
/**
|
|
978
|
+
* Persist build inputs the SERVING runtime needs (compat: the
|
|
979
|
+
* `--debug-prerender` flag, which selects the shape of the runtime
|
|
980
|
+
* 'use cache' error log). Pure-core default: no-op.
|
|
981
|
+
*/
|
|
982
|
+
recordBuildFlags: (outPath: string, debugPrerender: boolean) => void;
|
|
983
|
+
segmentDir: (outPath: string, routeId: string) => string;
|
|
984
|
+
treeSegmentFile: (outPath: string, routeId: string) => string;
|
|
985
|
+
bodySegmentFile: (outPath: string, routeId: string) => string;
|
|
986
|
+
segmentMetaFile: (outPath: string, routeId: string) => string;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Which entry point is running the init hooks (`pnext build` vs a server). */
|
|
990
|
+
export interface InitHookContext {
|
|
991
|
+
build: boolean;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Work a build step kicks off and does NOT await - it runs alongside bundling and prerendering, and the CLI
|
|
996
|
+
* awaits it just before the summary, reporting it as its own metric line. Compat registers the typecheck here.
|
|
997
|
+
*/
|
|
998
|
+
export interface BuildParallelPhase {
|
|
999
|
+
/** Label for the reported line ("Typecheck"). */
|
|
1000
|
+
name: string;
|
|
1001
|
+
/** Resolves with the phase's OWN elapsed ms (not the time spent awaiting it). */
|
|
1002
|
+
run: Promise<number>;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export interface BuildExtensions {
|
|
1006
|
+
/** Ordered build steps (compat registers action discovery + server-reference manifest). */
|
|
1007
|
+
steps: BuildStep[];
|
|
1008
|
+
/** Ordered hooks run after manifest.json is written. */
|
|
1009
|
+
completeHooks: BuildCompleteHook[];
|
|
1010
|
+
/** Init hooks run at the start of build + start (compat registers installCompatFetchCache). */
|
|
1011
|
+
initHooks: ((config: ResolvedConfig, context: InitHookContext) => void)[];
|
|
1012
|
+
/** Background phases started by a build step, awaited before the summary. */
|
|
1013
|
+
parallelPhases: BuildParallelPhase[];
|
|
1014
|
+
compat: BuildCompatExtensions;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function createBuildExtensions(): BuildExtensions {
|
|
1018
|
+
const compat: BuildCompatExtensions = {
|
|
1019
|
+
withVaryParamsTracking: async produce => ({ value: await produce() }),
|
|
1020
|
+
varyNamesFor: () => [],
|
|
1021
|
+
warnMetadataIssues: () => Promise.resolve(),
|
|
1022
|
+
nextOutputExport: () => false,
|
|
1023
|
+
nextOutputMode: () => undefined,
|
|
1024
|
+
nextScriptWorkersEnabled: () => false,
|
|
1025
|
+
defaultExpireTimeSeconds: () => undefined,
|
|
1026
|
+
takeCacheLifeStash: () => undefined,
|
|
1027
|
+
takeFontLinkHeader: () => undefined,
|
|
1028
|
+
normalizeStaticParamsModule: module => module,
|
|
1029
|
+
defaultDynamicStaleTimeSeconds: 30,
|
|
1030
|
+
defaultStaticStaleTimeSeconds: 300,
|
|
1031
|
+
buildRootTreePrefetch: options => {
|
|
1032
|
+
const staleTime =
|
|
1033
|
+
options.staleTimeSeconds ??
|
|
1034
|
+
(options.isStatic
|
|
1035
|
+
? compat.defaultStaticStaleTimeSeconds
|
|
1036
|
+
: compat.defaultDynamicStaleTimeSeconds);
|
|
1037
|
+
const bodySize = options.bodySizeBytes ?? 0;
|
|
1038
|
+
const budget = options.inlineBudgetBytes ?? 32 * 1024;
|
|
1039
|
+
const canInlineBody = options.isStatic && !options.postponed && bodySize <= budget;
|
|
1040
|
+
return {
|
|
1041
|
+
tree: {
|
|
1042
|
+
name: '',
|
|
1043
|
+
param: null,
|
|
1044
|
+
slots: {
|
|
1045
|
+
children: {
|
|
1046
|
+
name: '__PAGE__',
|
|
1047
|
+
param: null,
|
|
1048
|
+
slots: null,
|
|
1049
|
+
prefetchHints: canInlineBody ? 0b10000000 : 0b100000000,
|
|
1050
|
+
...(options.bodySizeBytes !== undefined ? { sizeBytes: options.bodySizeBytes } : {}),
|
|
1051
|
+
...(canInlineBody ? {} : { segmentPath: '/' }),
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
prefetchHints: 0b10000 | (canInlineBody ? 0b1000000 : 0),
|
|
1055
|
+
},
|
|
1056
|
+
staleTime,
|
|
1057
|
+
isStatic: options.isStatic,
|
|
1058
|
+
};
|
|
1059
|
+
},
|
|
1060
|
+
rootTreePrefetchText: (payload, format = 'json') => {
|
|
1061
|
+
const json = JSON.stringify(payload);
|
|
1062
|
+
return format === 'flight' ? `0:${json}` : json;
|
|
1063
|
+
},
|
|
1064
|
+
buildSegmentMeta: options => {
|
|
1065
|
+
const inlined =
|
|
1066
|
+
!options.postponed &&
|
|
1067
|
+
options.bodySizeBytes !== undefined &&
|
|
1068
|
+
options.bodySizeBytes <= (options.inlineBudgetBytes ?? 32 * 1024);
|
|
1069
|
+
return {
|
|
1070
|
+
status: options.status,
|
|
1071
|
+
staleTime: options.staleTime,
|
|
1072
|
+
postponed: options.postponed,
|
|
1073
|
+
segmentPaths: ['/_tree', '/'],
|
|
1074
|
+
...(options.bodySizeBytes !== undefined ? { segmentSizes: { '/': options.bodySizeBytes } } : {}),
|
|
1075
|
+
...(inlined ? { inlinedSegmentPaths: ['/'] } : {}),
|
|
1076
|
+
...(options.prefetchHints ? { prefetchHints: options.prefetchHints } : {}),
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
diagnoseCacheComponentsPrerender: () => undefined,
|
|
1080
|
+
prerenderFailureFooter: () => '',
|
|
1081
|
+
diagnosticLeadsWithErrorLine: () => false,
|
|
1082
|
+
recordBuildFlags: () => undefined,
|
|
1083
|
+
segmentDir: (outPath, routeId) => `${outPath}/segments/${routeId}`,
|
|
1084
|
+
treeSegmentFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/_tree.segment.rsc`,
|
|
1085
|
+
bodySegmentFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/index.segment.rsc`,
|
|
1086
|
+
segmentMetaFile: (outPath, routeId) => `${compat.segmentDir(outPath, routeId)}/route.segment.meta`,
|
|
1087
|
+
};
|
|
1088
|
+
return { steps: [], completeHooks: [], initHooks: [], parallelPhases: [], compat };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
export function getBuildExtensions(): BuildExtensions {
|
|
1092
|
+
return activeHost.build;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
export function setBuildCompatExtensions(overrides: Partial<BuildCompatExtensions>): void {
|
|
1096
|
+
Object.assign(activeHost.build.compat, overrides);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
export function registerBuildSteps(...steps: BuildStep[]): void {
|
|
1100
|
+
activeHost.build.steps.push(...steps);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export function registerBuildCompleteHooks(...hooks: BuildCompleteHook[]): void {
|
|
1104
|
+
activeHost.build.completeHooks.push(...hooks);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Register in-flight background work. The rejection is parked here so a phase
|
|
1109
|
+
* that fails long before the CLI awaits it never surfaces as an unhandled
|
|
1110
|
+
* rejection; `buildParallelPhaseError` lets the CLI prefer it over a downstream
|
|
1111
|
+
* failure it caused (a type error also breaking the prerender, say).
|
|
1112
|
+
*/
|
|
1113
|
+
export function registerBuildParallelPhases(...phases: BuildParallelPhase[]): void {
|
|
1114
|
+
for (const phase of phases) {
|
|
1115
|
+
phase.run.catch((error: unknown) => {
|
|
1116
|
+
parallelPhaseErrors.set(phase, error);
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
activeHost.build.parallelPhases.push(...phases);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const parallelPhaseErrors = new WeakMap<BuildParallelPhase, unknown>();
|
|
1123
|
+
|
|
1124
|
+
/** Drop the previous build's phases (one process can run several builds). */
|
|
1125
|
+
export function clearBuildParallelPhases(): void {
|
|
1126
|
+
activeHost.build.parallelPhases.length = 0;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** The first registered phase that has ALREADY failed, if any. */
|
|
1130
|
+
export function buildParallelPhaseError(): unknown {
|
|
1131
|
+
for (const phase of activeHost.build.parallelPhases) {
|
|
1132
|
+
if (parallelPhaseErrors.has(phase)) return parallelPhaseErrors.get(phase);
|
|
1133
|
+
}
|
|
1134
|
+
return undefined;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export function registerInitHooks(
|
|
1138
|
+
...hooks: ((config: ResolvedConfig, context: InitHookContext) => void)[]
|
|
1139
|
+
): void {
|
|
1140
|
+
activeHost.build.initHooks.push(...hooks);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** Run every registered init hook (core calls at build + start startup). */
|
|
1144
|
+
export function runInitHooks(config: ResolvedConfig, context: InitHookContext = { build: false }): void {
|
|
1145
|
+
for (const hook of activeHost.build.initHooks) hook(config, context);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Routing extensions - compat registers extra convention filenames (with their boundary semantics)
|
|
1149
|
+
// and source usage-detection predicates that mark a route as request-dependent. Core scans its own
|
|
1150
|
+
// generic conventions and reads its own simple exports; the Next-only conventions
|
|
1151
|
+
// (forbidden/unauthorized) and next/* import detection move here.
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* An extra convention filename compat wants core routing to discover alongside
|
|
1155
|
+
* its own special files. `boundary: true` marks it a render boundary the route
|
|
1156
|
+
* scanner treats like error/not-found (collected into a route's special files).
|
|
1157
|
+
*/
|
|
1158
|
+
export interface RouteConvention {
|
|
1159
|
+
name: string;
|
|
1160
|
+
boundary: boolean;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* A usage-detection predicate: given a source module's text, return true when it
|
|
1165
|
+
* makes the route request-dependent (dynamic). Compat registers the next/*
|
|
1166
|
+
* import detectors (next/headers, next/navigation request hooks, next/server
|
|
1167
|
+
* connection()). Core runs these in addition to its own generic checks.
|
|
1168
|
+
*/
|
|
1169
|
+
export type UsageDetector = (source: string) => boolean;
|
|
1170
|
+
|
|
1171
|
+
/** A route module and its rewritten source, supplied across the full import graph. */
|
|
1172
|
+
export interface RouteDependencySource {
|
|
1173
|
+
file: string;
|
|
1174
|
+
source: string;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/** Generic route facts an extension can classify from a transitive module graph. */
|
|
1178
|
+
export interface RouteDependencyContext {
|
|
1179
|
+
kind: 'page' | 'handler';
|
|
1180
|
+
files: readonly RouteDependencySource[];
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/** Extension-owned classification. Labels are opaque to core. */
|
|
1184
|
+
export interface RouteDependencyClassification {
|
|
1185
|
+
usesRequest?: boolean;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export type RouteDependencyClassifier = (
|
|
1189
|
+
context: RouteDependencyContext,
|
|
1190
|
+
) => RouteDependencyClassification | undefined;
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* A source predicate that requires a client entry, paired with the REASON it fired. Reasons are the
|
|
1194
|
+
* substrate the client build gates feature regions on - a page that ships the runtime only for
|
|
1195
|
+
* `<Link>` needs none of the action machinery - so every detector says which fact it observed.
|
|
1196
|
+
*/
|
|
1197
|
+
export interface ClientEntryDetector {
|
|
1198
|
+
reason: ClientEntryReason;
|
|
1199
|
+
detect: UsageDetector;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export interface RoutingExtensions {
|
|
1203
|
+
/** Extra convention filenames compat wants discovered (forbidden/unauthorized/...). */
|
|
1204
|
+
conventions: RouteConvention[];
|
|
1205
|
+
/** Predicates that mark a page/layout source as request-dependent. */
|
|
1206
|
+
usageDetection: UsageDetector[];
|
|
1207
|
+
/** Classifiers that inspect a route's complete module graph. */
|
|
1208
|
+
dependencyClassification: RouteDependencyClassifier[];
|
|
1209
|
+
/** Reason-tagged predicates that require a client entry for a server-only page. */
|
|
1210
|
+
clientEntryDetection: ClientEntryDetector[];
|
|
1211
|
+
/**
|
|
1212
|
+
* Extra file extensions (without the leading dot) the route scanner should
|
|
1213
|
+
* treat as page/convention files, ADDITIVE to core's built-in
|
|
1214
|
+
* tsx/ts/jsx/js/mjs. Compat (next/mdx) registers `mdx`/`md` so `page.mdx` +
|
|
1215
|
+
* top-level `.mdx` pages resolve. Consumed lazily by src/routing/routes.ts so
|
|
1216
|
+
* registration at bootstrap (before scanRoutes) is honored. Order-preserving,
|
|
1217
|
+
* de-duplicated against the base list.
|
|
1218
|
+
*/
|
|
1219
|
+
pageExtensions: string[];
|
|
1220
|
+
/**
|
|
1221
|
+
* Why every page route must ship the client router entry, even a purely
|
|
1222
|
+
* server-rendered one with no client references. Next always emits its
|
|
1223
|
+
* app-router bootstrap per page (so instrumentation-client and hydration run
|
|
1224
|
+
* on every document) and registers `compat-parity` here; a pure-core app
|
|
1225
|
+
* keeps the lean "no client code, no bundle" default with an empty set.
|
|
1226
|
+
*/
|
|
1227
|
+
alwaysClientEntryReasons: Set<ClientEntryReason>;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function createRoutingExtensions(): RoutingExtensions {
|
|
1231
|
+
return {
|
|
1232
|
+
conventions: [],
|
|
1233
|
+
usageDetection: [],
|
|
1234
|
+
dependencyClassification: [],
|
|
1235
|
+
clientEntryDetection: [],
|
|
1236
|
+
pageExtensions: [],
|
|
1237
|
+
alwaysClientEntryReasons: new Set(),
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export function getRoutingExtensions(): RoutingExtensions {
|
|
1242
|
+
return activeHost.routing;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** Register extra route convention filenames (compat: forbidden/unauthorized). */
|
|
1246
|
+
export function registerRouteConventions(...conventions: RouteConvention[]): void {
|
|
1247
|
+
activeHost.routing.conventions.push(...conventions);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/** Register request-dependency usage detectors (compat: next/* import patterns). */
|
|
1251
|
+
export function registerUsageDetectors(...detectors: UsageDetector[]): void {
|
|
1252
|
+
activeHost.routing.usageDetection.push(...detectors);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/** Register transitive route-dependency classifiers. */
|
|
1256
|
+
export function registerRouteDependencyClassifiers(...classifiers: RouteDependencyClassifier[]): void {
|
|
1257
|
+
activeHost.routing.dependencyClassification.push(...classifiers);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/** Register reason-tagged client-entry detectors (compat: server-action forms, next/form). */
|
|
1261
|
+
export function registerClientEntryDetectors(...detectors: ClientEntryDetector[]): void {
|
|
1262
|
+
activeHost.routing.clientEntryDetection.push(...detectors);
|
|
1263
|
+
clientEntryReasonCache.clear();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Register extra page/convention file extensions (without the leading dot),
|
|
1268
|
+
* additive to core's built-in tsx/ts/jsx/js/mjs (compat: next/mdx registers
|
|
1269
|
+
* `mdx`/`md`). Duplicates (including against the core base list) are ignored by
|
|
1270
|
+
* the routing consumer, so calling with an already-present extension is a no-op.
|
|
1271
|
+
*/
|
|
1272
|
+
// Loadable = compiles through the server module graph. Superset of page
|
|
1273
|
+
// extensions: an imported `.md` is loadable but not routable (Next: @next/mdx
|
|
1274
|
+
// compiles imported .md while pageExtensions may list only mdx). Listeners let
|
|
1275
|
+
// consumers that latched the set (the server load plugin's per-root filter
|
|
1276
|
+
// regex) extend it instead of leaving late extensions to Bun's file loader
|
|
1277
|
+
// (default export = the file path, rendered as a bogus JSX tag).
|
|
1278
|
+
// Listeners stay module-global: a consumer latches the set once (the server
|
|
1279
|
+
// load plugin's per-root filter regex) and must keep hearing about it across a
|
|
1280
|
+
// host swap, which changes the set exactly like a registration does.
|
|
1281
|
+
const loadableExtensionListeners: (() => void)[] = [];
|
|
1282
|
+
|
|
1283
|
+
export function onLoadableExtensionsChanged(listener: () => void): void {
|
|
1284
|
+
loadableExtensionListeners.push(listener);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export function registerLoadableExtensions(...extensions: string[]): void {
|
|
1288
|
+
const loadable = activeHost.loadable;
|
|
1289
|
+
const fresh = [...new Set(extensions)].filter(ext => !loadable.includes(ext));
|
|
1290
|
+
if (fresh.length === 0) return;
|
|
1291
|
+
loadable.push(...fresh);
|
|
1292
|
+
for (const listener of loadableExtensionListeners) listener();
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/** Page extensions plus import-only loadable extensions, deduped. */
|
|
1296
|
+
export function extraLoadableExtensions(): string[] {
|
|
1297
|
+
return [...new Set([...activeHost.routing.pageExtensions, ...activeHost.loadable])];
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function registerPageExtensions(...extensions: string[]): void {
|
|
1301
|
+
const { pageExtensions } = activeHost.routing;
|
|
1302
|
+
const fresh = [...new Set(extensions)].filter(ext => !pageExtensions.includes(ext));
|
|
1303
|
+
if (fresh.length === 0) return;
|
|
1304
|
+
pageExtensions.push(...fresh);
|
|
1305
|
+
for (const listener of loadableExtensionListeners) listener();
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/** Record a reason every page route must ship the client router entry. */
|
|
1309
|
+
export function registerAlwaysClientEntryReason(reason: ClientEntryReason): void {
|
|
1310
|
+
activeHost.routing.alwaysClientEntryReasons.add(reason);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/** Why every page ships the router entry; empty for a pure-core app. */
|
|
1314
|
+
export function alwaysClientEntryReasons(): ClientEntryReason[] {
|
|
1315
|
+
return [...activeHost.routing.alwaysClientEntryReasons];
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/** Extra page-file extensions compat registered, in registration order. */
|
|
1319
|
+
export function extraPageExtensions(): string[] {
|
|
1320
|
+
return activeHost.routing.pageExtensions;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/** The extra boundary-convention names core routing must discover as special files. */
|
|
1324
|
+
export function extraBoundaryConventionNames(): string[] {
|
|
1325
|
+
return activeHost.routing.conventions.filter(c => c.boundary).map(c => c.name);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/** True when any registered usage detector marks `source` as request-dependent. */
|
|
1329
|
+
export function sourceUsesRegisteredRequestApi(source: string): boolean {
|
|
1330
|
+
return activeHost.routing.usageDetection.some(detect => detect(source));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/** Merge extension classifications for a route's complete module graph. */
|
|
1334
|
+
export function classifyRouteDependencies(
|
|
1335
|
+
context: RouteDependencyContext,
|
|
1336
|
+
): RouteDependencyClassification {
|
|
1337
|
+
const classification: RouteDependencyClassification = {};
|
|
1338
|
+
for (const classify of activeHost.routing.dependencyClassification) {
|
|
1339
|
+
const result = classify(context);
|
|
1340
|
+
if (!result) continue;
|
|
1341
|
+
if (result.usesRequest) classification.usesRequest = true;
|
|
1342
|
+
}
|
|
1343
|
+
return classification;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/** Every reason a registered detector requires a client entry for this source. */
|
|
1347
|
+
export function sourceClientEntryReasons(source: string): ClientEntryReason[] {
|
|
1348
|
+
const key = `${source.length}\0${Bun.hash(source).toString(36)}`;
|
|
1349
|
+
const cached = clientEntryReasonCache.get(key);
|
|
1350
|
+
if (cached) return cached;
|
|
1351
|
+
const reasons: ClientEntryReason[] = [];
|
|
1352
|
+
for (const { reason, detect } of activeHost.routing.clientEntryDetection) {
|
|
1353
|
+
if (!reasons.includes(reason) && detect(source)) reasons.push(reason);
|
|
1354
|
+
}
|
|
1355
|
+
clientEntryReasonCache.set(key, reasons);
|
|
1356
|
+
return reasons;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// ---------------------------------------------------------------------------
|
|
1360
|
+
// Proxy extensions — populated by compat/next proxy support.
|
|
1361
|
+
// ---------------------------------------------------------------------------
|
|
1362
|
+
|
|
1363
|
+
export interface ProxyExtensions {
|
|
1364
|
+
/** File basenames core should consider as request proxy entrypoints. */
|
|
1365
|
+
names: readonly string[];
|
|
1366
|
+
/** Optional validation hook for framework-specific proxy diagnostics. */
|
|
1367
|
+
validateFiles: (config: ResolvedConfig) => Promise<void>;
|
|
1368
|
+
/** Pick the handler export from a loaded proxy module. */
|
|
1369
|
+
handlerExport: (module: Record<string, unknown>) => unknown;
|
|
1370
|
+
/** Preserve the original proxy URL and Flight headers for advanced routing. */
|
|
1371
|
+
skipUrlNormalize: () => boolean;
|
|
1372
|
+
/** Framework locale derived from the proxy request URL. */
|
|
1373
|
+
locale: (url: URL) => string;
|
|
1374
|
+
/** Notify compat before an external proxy rewrite is fetched. */
|
|
1375
|
+
onExternalRewrite: (request: Request) => void;
|
|
1376
|
+
/**
|
|
1377
|
+
* Max request-body bytes buffered on a proxied request before truncation
|
|
1378
|
+
* (compat maps experimental.proxyClientMaxBodySize; Next's default is 10MB).
|
|
1379
|
+
*/
|
|
1380
|
+
clientMaxBodySize: () => number;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function createProxyExtensions(): ProxyExtensions {
|
|
1384
|
+
return {
|
|
1385
|
+
names: ['proxy'],
|
|
1386
|
+
validateFiles: () => Promise.resolve(),
|
|
1387
|
+
handlerExport: module => module.proxy ?? module.default,
|
|
1388
|
+
skipUrlNormalize: () => false,
|
|
1389
|
+
locale: () => '',
|
|
1390
|
+
onExternalRewrite: request => void request,
|
|
1391
|
+
clientMaxBodySize: () => 10 * 1024 * 1024,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
export function getProxyExtensions(): ProxyExtensions {
|
|
1396
|
+
return activeHost.proxy;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
export function setProxyExtensions(overrides: Partial<ProxyExtensions>): void {
|
|
1400
|
+
Object.assign(activeHost.proxy, overrides);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Bundler extensions - populated by compat/cache/use-cache-transform, compat/actions/rewrite and
|
|
1404
|
+
// compat/actions/client-plugin.
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* A server-source transform applied after core's own transforms. Compat registers rewriteUseCacheSource and
|
|
1408
|
+
* rewriteInlineActionTags. Called with the source, its file path, and optionally the project root - several
|
|
1409
|
+
* compat transforms accept a root for id derivation, so it is threaded through.
|
|
1410
|
+
*/
|
|
1411
|
+
export interface ServerSourceTransform {
|
|
1412
|
+
(source: string, file: string, root?: string): string;
|
|
1413
|
+
/**
|
|
1414
|
+
* Cheap trigger tokens (`includes`, or a `test` for the few shapes whitespace makes non-literal). The
|
|
1415
|
+
* transform is skipped when the source matches none of them, so it must be a SUPERSET of the transform's
|
|
1416
|
+
* own exact gate - an empty array means "configured off, never fires". Attach with `withSniff`. Absent
|
|
1417
|
+
* means ungated: the transform always runs and disables the chain gate.
|
|
1418
|
+
*/
|
|
1419
|
+
sniff?: readonly SniffToken[];
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
export type SniffToken = string | RegExp;
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* A client-source transform that must await IO (compat: worker bundling emits a
|
|
1426
|
+
* chunk before it can substitute the URL). Runs ahead of the sync chain, so a
|
|
1427
|
+
* pass that consumes a token the sync passes rewrite (`import.meta.url`) still
|
|
1428
|
+
* sees the original source.
|
|
1429
|
+
*/
|
|
1430
|
+
export interface AsyncSourceTransform {
|
|
1431
|
+
(source: string, file: string, root?: string): Promise<string>;
|
|
1432
|
+
sniff?: readonly SniffToken[];
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
export interface BundlerExtensions {
|
|
1436
|
+
/** Ordered source transforms that must run before generic server transforms. */
|
|
1437
|
+
serverSourcePreTransforms: ServerSourceTransform[];
|
|
1438
|
+
/** Ordered server-source transforms (compat: use-cache + inline-action tags). */
|
|
1439
|
+
serverSourceTransforms: ServerSourceTransform[];
|
|
1440
|
+
/** Ordered client-source transforms (compat: next/font + next/root-params). */
|
|
1441
|
+
clientSourceTransforms: ServerSourceTransform[];
|
|
1442
|
+
/** Awaitable client-source transforms run before `clientSourceTransforms`. */
|
|
1443
|
+
clientSourceAsyncPreTransforms: AsyncSourceTransform[];
|
|
1444
|
+
/**
|
|
1445
|
+
* Transforms applied to already-BUNDLED server output (vendor bundles). Only the passes whose trigger
|
|
1446
|
+
* survives bundling belong here - a package can ship a `'use cache'` function, but its imports and aliases
|
|
1447
|
+
* were already resolved by the bundler and its `define` constants inlined by esbuild.
|
|
1448
|
+
*/
|
|
1449
|
+
bundledSourceTransforms: ServerSourceTransform[];
|
|
1450
|
+
/** esbuild plugins appended to every client build (compat: action client stub). */
|
|
1451
|
+
clientEsbuildPlugins: (config: ResolvedConfig) => Plugin[];
|
|
1452
|
+
/** esbuild plugins appended to every server build (compat: action client stub). */
|
|
1453
|
+
serverEsbuildPlugins: (config: ResolvedConfig, options?: ServerEsbuildPluginOptions) => Plugin[];
|
|
1454
|
+
/**
|
|
1455
|
+
* Entry-point rewrite consulted where a server bundle's entry is already
|
|
1456
|
+
* resolved (compat: the CJS default-interop marker, so its plugin never
|
|
1457
|
+
* needs a catch-all onResolve). undefined = use the specifier as-is.
|
|
1458
|
+
*/
|
|
1459
|
+
serverBundleEntry: (specifier: string, resolveDir: string, resolved?: string) => string | undefined;
|
|
1460
|
+
/**
|
|
1461
|
+
* esbuild `define` constants for every server compile (compat: compiler.define + compiler.defineServer).
|
|
1462
|
+
* Lexical by construction - unlike a textual pass it never rewrites inside string literals or comments.
|
|
1463
|
+
*/
|
|
1464
|
+
serverDefines: () => Record<string, string>;
|
|
1465
|
+
/** Same, for the client graph (compat: compiler.define; defineServer -> undefined). */
|
|
1466
|
+
clientDefines: () => Record<string, string>;
|
|
1467
|
+
/**
|
|
1468
|
+
* Files injected into every client entry graph for their side effects
|
|
1469
|
+
* (compat: core-js polyfills under experimental.swcEnvOptions).
|
|
1470
|
+
*/
|
|
1471
|
+
clientInjects: () => string[];
|
|
1472
|
+
/** Resolve compat-owned package edges while scanning route dependencies. */
|
|
1473
|
+
resolveRouteDependency: (root: string, fromFile: string, specifier: string) => string | undefined;
|
|
1474
|
+
/**
|
|
1475
|
+
* Extra `pure` targets (e.g. `console.log`) for the client esbuild `pure`
|
|
1476
|
+
* option, so calls with unused results are dropped (compat: compiler.removeConsole).
|
|
1477
|
+
*/
|
|
1478
|
+
clientPureFunctions: (config: ResolvedConfig) => string[];
|
|
1479
|
+
/**
|
|
1480
|
+
* Standalone static chunks written verbatim into the client `chunks/` dir,
|
|
1481
|
+
* independent of the esbuild entry graph (compat: the no-module polyfills
|
|
1482
|
+
* chunk). Pure-core apps emit none.
|
|
1483
|
+
*/
|
|
1484
|
+
staticClientChunks: (config: ResolvedConfig) => { name: string; contents: string }[];
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function createBundlerExtensions(): BundlerExtensions {
|
|
1488
|
+
return {
|
|
1489
|
+
serverSourcePreTransforms: [],
|
|
1490
|
+
serverSourceTransforms: [],
|
|
1491
|
+
clientSourceTransforms: [],
|
|
1492
|
+
clientSourceAsyncPreTransforms: [],
|
|
1493
|
+
bundledSourceTransforms: [],
|
|
1494
|
+
clientEsbuildPlugins: () => [],
|
|
1495
|
+
serverEsbuildPlugins: () => [],
|
|
1496
|
+
serverBundleEntry: () => undefined,
|
|
1497
|
+
serverDefines: () => ({}),
|
|
1498
|
+
clientDefines: () => ({}),
|
|
1499
|
+
clientInjects: () => [],
|
|
1500
|
+
resolveRouteDependency: () => undefined,
|
|
1501
|
+
clientPureFunctions: () => [],
|
|
1502
|
+
staticClientChunks: () => [],
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
export function getBundlerExtensions(): BundlerExtensions {
|
|
1507
|
+
return activeHost.bundler;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* esbuild `define` for a server compile (`build` or `transform`). Spread into
|
|
1512
|
+
* the options; unconfigured apps get `{}` and esbuild sees no `define` at all.
|
|
1513
|
+
*/
|
|
1514
|
+
export function serverDefineOptions(): { define?: Record<string, string> } {
|
|
1515
|
+
const define = activeHost.bundler.serverDefines();
|
|
1516
|
+
return Object.keys(define).length > 0 ? { define } : {};
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
export function registerServerSourceTransforms(...transforms: ServerSourceTransform[]): void {
|
|
1520
|
+
activeHost.bundler.serverSourceTransforms.push(...transforms);
|
|
1521
|
+
activeHost.serverSniff = undefined;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
export function registerServerSourcePreTransforms(...transforms: ServerSourceTransform[]): void {
|
|
1525
|
+
activeHost.bundler.serverSourcePreTransforms.push(...transforms);
|
|
1526
|
+
activeHost.serverSniff = undefined;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
export function registerClientSourceTransforms(...transforms: ServerSourceTransform[]): void {
|
|
1530
|
+
activeHost.bundler.clientSourceTransforms.push(...transforms);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
export function registerClientSourceAsyncPreTransforms(
|
|
1534
|
+
...transforms: AsyncSourceTransform[]
|
|
1535
|
+
): void {
|
|
1536
|
+
activeHost.bundler.clientSourceAsyncPreTransforms.push(...transforms);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
export function registerBundledSourceTransforms(...transforms: ServerSourceTransform[]): void {
|
|
1540
|
+
activeHost.bundler.bundledSourceTransforms.push(...transforms);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Tag a transform with the cheap trigger tokens that gate it. Wraps rather than
|
|
1545
|
+
* mutates, so a transform registered on both the server and client chains can
|
|
1546
|
+
* be tagged independently.
|
|
1547
|
+
*/
|
|
1548
|
+
export function withSniff<T extends ServerSourceTransform | AsyncSourceTransform>(
|
|
1549
|
+
sniff: readonly SniffToken[],
|
|
1550
|
+
transform: T,
|
|
1551
|
+
): T {
|
|
1552
|
+
return Object.assign(
|
|
1553
|
+
(source: string, file: string, root?: string) => transform(source, file, root),
|
|
1554
|
+
{ sniff },
|
|
1555
|
+
) as T;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function sniffHit(source: string, sniff: readonly SniffToken[]): boolean {
|
|
1559
|
+
for (const token of sniff) {
|
|
1560
|
+
if (typeof token === 'string' ? source.includes(token) : token.test(source)) return true;
|
|
1561
|
+
}
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* The union of every registered transform's tokens: the whole server chain can
|
|
1567
|
+
* bail on one deduped scan instead of re-testing per pass. Derived from the
|
|
1568
|
+
* registry and regenerated on every registration, so a transform registered
|
|
1569
|
+
* without a sniff (`always: true` here) can never be silently skipped. Cached
|
|
1570
|
+
* on the host, so a swap starts from the new host's own chain.
|
|
1571
|
+
*/
|
|
1572
|
+
function serverSourceSniff() {
|
|
1573
|
+
const host = activeHost;
|
|
1574
|
+
if (host.serverSniff) return host.serverSniff;
|
|
1575
|
+
const tokens = new Set<string>();
|
|
1576
|
+
for (const transform of [
|
|
1577
|
+
...host.bundler.serverSourcePreTransforms,
|
|
1578
|
+
...host.bundler.serverSourceTransforms,
|
|
1579
|
+
]) {
|
|
1580
|
+
if (!transform.sniff) return (host.serverSniff = { pattern: undefined });
|
|
1581
|
+
for (const token of transform.sniff) {
|
|
1582
|
+
tokens.add(`(?:${typeof token === 'string' ? escapeRegex(token) : token.source})`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
// One alternation scan measured cheaper than N `includes` over the same
|
|
1586
|
+
// bytes. With no tokens at all (every transform configured off) the chain can
|
|
1587
|
+
// never fire, and `new RegExp('')` would match everything.
|
|
1588
|
+
return (host.serverSniff = { pattern: new RegExp(tokens.size ? [...tokens].join('|') : '(?!)') });
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/** Whether any registered server-source transform can possibly change `source`. */
|
|
1592
|
+
export function sourceNeedsServerTransforms(source: string): boolean {
|
|
1593
|
+
const { pattern } = serverSourceSniff();
|
|
1594
|
+
return !pattern || pattern.test(source);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function applyTransforms(
|
|
1598
|
+
transforms: readonly ServerSourceTransform[],
|
|
1599
|
+
source: string,
|
|
1600
|
+
file: string,
|
|
1601
|
+
root?: string,
|
|
1602
|
+
): string {
|
|
1603
|
+
let next = source;
|
|
1604
|
+
for (const transform of transforms) {
|
|
1605
|
+
// Gate on the running source, not the original: an earlier transform can
|
|
1606
|
+
// inject a later one's trigger (e.g. next/font emits next/root-params).
|
|
1607
|
+
if (transform.sniff && !sniffHit(next, transform.sniff)) continue;
|
|
1608
|
+
next = transform(next, file, root);
|
|
1609
|
+
}
|
|
1610
|
+
return next;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export function setBundlerExtensions(
|
|
1614
|
+
overrides: Partial<
|
|
1615
|
+
Omit<
|
|
1616
|
+
BundlerExtensions,
|
|
1617
|
+
| 'serverSourcePreTransforms'
|
|
1618
|
+
| 'serverSourceTransforms'
|
|
1619
|
+
| 'clientSourceTransforms'
|
|
1620
|
+
| 'clientSourceAsyncPreTransforms'
|
|
1621
|
+
| 'bundledSourceTransforms'
|
|
1622
|
+
>
|
|
1623
|
+
>,
|
|
1624
|
+
): void {
|
|
1625
|
+
Object.assign(activeHost.bundler, overrides);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/** Apply every registered pre server-source transform in order. */
|
|
1629
|
+
export function applyServerSourcePreTransforms(source: string, file: string, root?: string): string {
|
|
1630
|
+
return applyTransforms(activeHost.bundler.serverSourcePreTransforms, source, file, root);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/** Apply every registered server-source transform in order. */
|
|
1634
|
+
export function applyServerSourceTransforms(source: string, file: string, root?: string): string {
|
|
1635
|
+
return applyTransforms(activeHost.bundler.serverSourceTransforms, source, file, root);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
export function applyClientSourceTransforms(source: string, file: string, root?: string): string {
|
|
1639
|
+
return applyTransforms(activeHost.bundler.clientSourceTransforms, source, file, root);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/** Whether any awaitable client pre-transform is registered at all. */
|
|
1643
|
+
export function hasClientSourceAsyncPreTransforms(): boolean {
|
|
1644
|
+
return activeHost.bundler.clientSourceAsyncPreTransforms.length > 0;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
export async function applyClientSourceAsyncPreTransforms(
|
|
1648
|
+
source: string,
|
|
1649
|
+
file: string,
|
|
1650
|
+
root?: string,
|
|
1651
|
+
): Promise<string> {
|
|
1652
|
+
let next = source;
|
|
1653
|
+
for (const transform of activeHost.bundler.clientSourceAsyncPreTransforms) {
|
|
1654
|
+
if (transform.sniff && !sniffHit(next, transform.sniff)) continue;
|
|
1655
|
+
next = await transform(next, file, root);
|
|
1656
|
+
}
|
|
1657
|
+
return next;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/** Apply every registered bundled-output transform in order. */
|
|
1661
|
+
export function applyBundledSourceTransforms(source: string, file: string): string {
|
|
1662
|
+
return applyTransforms(activeHost.bundler.bundledSourceTransforms, source, file);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// ---------------------------------------------------------------------------
|
|
1666
|
+
// Import alias extensions — compat registers Next/React alias maps and missing
|
|
1667
|
+
// import diagnostics. Core owns only framework aliases.
|
|
1668
|
+
// ---------------------------------------------------------------------------
|
|
1669
|
+
|
|
1670
|
+
export interface ImportAliasExtensions {
|
|
1671
|
+
aliases: (config: ResolvedConfig, target: CompatAliasTarget) => Record<string, string>;
|
|
1672
|
+
clientSsrAliases: (config: ResolvedConfig) => Record<string, string>;
|
|
1673
|
+
missingImportError: (config: ResolvedConfig, specifier: string) => string | undefined;
|
|
1674
|
+
/**
|
|
1675
|
+
* Extra alias overrides layered on top of `aliases` for modules compiled under the true `react-server`
|
|
1676
|
+
* layer (currently proxy/middleware). Distinct from the base server target because pages/api keeps the
|
|
1677
|
+
* full-hooks `react` shim for backward compatibility while proxy/middleware must not expose client hooks
|
|
1678
|
+
* at all.
|
|
1679
|
+
*/
|
|
1680
|
+
reactServerLayerAliases: (config: ResolvedConfig) => Record<string, string>;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function createImportAliasExtensions(): ImportAliasExtensions {
|
|
1684
|
+
return {
|
|
1685
|
+
aliases: () => ({}),
|
|
1686
|
+
clientSsrAliases: () => ({}),
|
|
1687
|
+
missingImportError: () => undefined,
|
|
1688
|
+
reactServerLayerAliases: () => ({}),
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
export function getImportAliasExtensions(): ImportAliasExtensions {
|
|
1693
|
+
return activeHost.importAlias;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
export function setImportAliasExtensions(overrides: Partial<ImportAliasExtensions>): void {
|
|
1697
|
+
Object.assign(activeHost.importAlias, overrides);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// ---------------------------------------------------------------------------
|
|
1701
|
+
// Proxy response protocol — core has generic control headers; compat registers
|
|
1702
|
+
// the Next middleware header names.
|
|
1703
|
+
// ---------------------------------------------------------------------------
|
|
1704
|
+
|
|
1705
|
+
export interface ProxyResponseProtocol {
|
|
1706
|
+
nextHeader: string;
|
|
1707
|
+
rewriteHeader: string;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function createProxyResponseProtocol(): ProxyResponseProtocol {
|
|
1711
|
+
return {
|
|
1712
|
+
nextHeader: 'x-pnext-middleware-next',
|
|
1713
|
+
rewriteHeader: 'x-pnext-middleware-rewrite',
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
export function getProxyResponseProtocol(): ProxyResponseProtocol {
|
|
1718
|
+
return activeHost.proxyResponse;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
export function setProxyResponseProtocol(overrides: Partial<ProxyResponseProtocol>): void {
|
|
1722
|
+
Object.assign(activeHost.proxyResponse, overrides);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
export interface RouterProtocolExtensions {
|
|
1726
|
+
prefetchRequestHeaders: () => string[];
|
|
1727
|
+
/**
|
|
1728
|
+
* True when this request should receive only the route's static shell - Suspense/loading fallbacks in
|
|
1729
|
+
* place, streamed dynamic continuation cut (Next's partial prefetch: a `next-router-prefetch: 1` request
|
|
1730
|
+
* never runs dynamic data). Core consults it in renderPageResponse; the classification policy is compat's.
|
|
1731
|
+
*/
|
|
1732
|
+
shellOnlyRequest: (request: Request, route: RouteManifestEntry) => boolean;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function createRouterProtocolExtensions(): RouterProtocolExtensions {
|
|
1736
|
+
return {
|
|
1737
|
+
prefetchRequestHeaders: () => [],
|
|
1738
|
+
shellOnlyRequest: () => false,
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
export function getRouterProtocolExtensions(): RouterProtocolExtensions {
|
|
1743
|
+
return activeHost.routerProtocol;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
export function setRouterProtocolExtensions(overrides: Partial<RouterProtocolExtensions>): void {
|
|
1747
|
+
Object.assign(activeHost.routerProtocol, overrides);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Asset extensions - compat registers framework-specific handling for imported static assets (next/image
|
|
1751
|
+
// static image imports carrying real width/height/blurDataURL and a /_next/static/media asset URL). Core
|
|
1752
|
+
// keeps a generic behavior when no override is registered.
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Context for a static-asset module override. `sourcePath` is the asset file on
|
|
1756
|
+
* disk, `bytes` its contents. `emit(relativePath, bytes)` writes the asset to
|
|
1757
|
+
* the served public tree at that POSIX-relative path and returns its public URL
|
|
1758
|
+
* (e.g. `/_next/static/media/pic.<hash>.png`). A handler returns the ESM module
|
|
1759
|
+
* text an `import asset from './pic.png'` should evaluate to, or undefined to
|
|
1760
|
+
* let core apply its generic default.
|
|
1761
|
+
*/
|
|
1762
|
+
export interface StaticAssetModuleContext {
|
|
1763
|
+
sourcePath: string;
|
|
1764
|
+
bytes: Uint8Array;
|
|
1765
|
+
emit: (relativePath: string, bytes: Uint8Array) => string;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
export interface StaticAssetPathContext {
|
|
1769
|
+
sourcePath: string;
|
|
1770
|
+
hash: string;
|
|
1771
|
+
base: string;
|
|
1772
|
+
ext: string;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
export interface AssetExtensions {
|
|
1776
|
+
/** URL path prefixes for emitted assets stored under `<out>/public`. */
|
|
1777
|
+
staticAssetPublicPrefixes: () => string[];
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* POSIX relative public path for core's generic imported-asset module. Compat
|
|
1781
|
+
* registers Next's `/_next/static/media` layout.
|
|
1782
|
+
*/
|
|
1783
|
+
staticAssetRelativePath: (context: StaticAssetPathContext) => string;
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* Produce the ESM module text for an imported static asset, or undefined to fall back to core's generic
|
|
1787
|
+
* asset module. Compat (next/image) returns the static-image descriptor module; pure-core apps register
|
|
1788
|
+
* nothing. May return a Promise - every call site already awaits this seam - so a handler can run an async
|
|
1789
|
+
* pipeline.
|
|
1790
|
+
*/
|
|
1791
|
+
staticAssetModule: (
|
|
1792
|
+
context: StaticAssetModuleContext,
|
|
1793
|
+
) => string | undefined | Promise<string | undefined>;
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* Whether a configured `turbopack.rules` loader chain claims this file. When
|
|
1797
|
+
* true, core's generic image/asset resolver (server + client static-asset
|
|
1798
|
+
* plugins) steps aside so the compat loader-rule plugin runs the chain
|
|
1799
|
+
* instead (e.g. a `*.svg` loader rule preempting the default image pipeline).
|
|
1800
|
+
* Compat registers `hasWebpackLoaderRuleFor`; pure-core apps keep the default.
|
|
1801
|
+
*/
|
|
1802
|
+
hasLoaderRuleFor: (filePath: string) => boolean;
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Run the `turbopack.rules` loader chain configured for `specifier` (which may carry a query) as imported
|
|
1806
|
+
* from `importer`, and return the path of the module its output was materialized to - or undefined when no
|
|
1807
|
+
* rule claims it. Server modules compile one at a time and route files are inlined into a route bundle, so
|
|
1808
|
+
* a rule source never reaches a bundler `onLoad` with its importer's query intact; core's resolvers ask
|
|
1809
|
+
* this instead.
|
|
1810
|
+
*/
|
|
1811
|
+
loaderRuleModule: (
|
|
1812
|
+
specifier: string,
|
|
1813
|
+
importer: string,
|
|
1814
|
+
) => string | undefined | Promise<string | undefined>;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
function createAssetExtensions(): AssetExtensions {
|
|
1818
|
+
return {
|
|
1819
|
+
staticAssetPublicPrefixes: () => ['/__pnext/static/media/'],
|
|
1820
|
+
staticAssetRelativePath: ({ base, hash, ext }) => `__pnext/static/media/${base}.${hash}${ext}`,
|
|
1821
|
+
staticAssetModule: () => undefined,
|
|
1822
|
+
hasLoaderRuleFor: () => false,
|
|
1823
|
+
loaderRuleModule: () => undefined,
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
export function getAssetExtensions(): AssetExtensions {
|
|
1828
|
+
return activeHost.asset;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
export function setAssetExtensions(overrides: Partial<AssetExtensions>): void {
|
|
1832
|
+
Object.assign(activeHost.asset, overrides);
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// CSS extras (CORE - no-op defaults; compat populates via register/css-extras).
|
|
1836
|
+
//
|
|
1837
|
+
// Lets core's CSS build sites pull in compat CSS handling without a core-to-compat import edge. Every field
|
|
1838
|
+
// ships a working no-op default, so a pure-core / non-sass app is byte-identical. The single feature today
|
|
1839
|
+
// is sass/scss.
|
|
1840
|
+
//
|
|
1841
|
+
// Unlike the domains above, this registry is a module-level singleton rather than a host field: consumers
|
|
1842
|
+
// may hold the object across a host swap, so it is restored in place via restoreWithExtensionHost. Call
|
|
1843
|
+
// getCssExtensions() lazily inside the build functions, not at module top-level, so the registry is already
|
|
1844
|
+
// populated at bootstrap.
|
|
1845
|
+
|
|
1846
|
+
export interface CssExtensions {
|
|
1847
|
+
/** Extra CSS-like file extensions, including the dot. */
|
|
1848
|
+
extraCssExtensions: () => string[];
|
|
1849
|
+
/** Resolve a bare CSS dependency that normal route scanning leaves external. */
|
|
1850
|
+
resolveCssDependency: (root: string, fromFile: string, specifier: string) => string | undefined;
|
|
1851
|
+
/** Keep root not-found CSS with its fallback response instead of every matched route. */
|
|
1852
|
+
deferRootNotFoundCss: () => boolean;
|
|
1853
|
+
/** Replace built stylesheet links with document-head markup when compat requires it. */
|
|
1854
|
+
inlineStylesheets: (
|
|
1855
|
+
config: ResolvedConfig,
|
|
1856
|
+
options: { assetNames: string[]; dev: boolean; nonce?: string; prependCss?: string },
|
|
1857
|
+
) => string[] | undefined;
|
|
1858
|
+
/** esbuild plugins for the CSS-chunk builds (compat: sass/scss loader). */
|
|
1859
|
+
cssChunkPlugins: () => Plugin[];
|
|
1860
|
+
/**
|
|
1861
|
+
* The scoped class-name map for a CSS-module file this registry handles
|
|
1862
|
+
* (`*.module.{scss,sass}`); undefined to defer to core (`*.module.css`).
|
|
1863
|
+
*/
|
|
1864
|
+
resolveCssModule: (file: string) => Record<string, string> | undefined;
|
|
1865
|
+
/**
|
|
1866
|
+
* The ESM module text a client bundle should evaluate for a compat CSS-module
|
|
1867
|
+
* import (`export default <map>`); undefined to defer to core.
|
|
1868
|
+
*/
|
|
1869
|
+
loadCssModuleForClient: (file: string) => string | undefined;
|
|
1870
|
+
/**
|
|
1871
|
+
* Partition each route's ordered `cssImports` into chunk slices (Next's CSS
|
|
1872
|
+
* chunking). Returns `routeId -> ordered segments`, each segment an ordered
|
|
1873
|
+
* slice of that route's imports; routes absent from the map (or with a single
|
|
1874
|
+
* segment) keep core's single-chunk behaviour.
|
|
1875
|
+
*/
|
|
1876
|
+
planRouteCssChunks: (routes: { id: string; cssImports: string[] }[]) => Map<string, string[][]>;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
const cssExtensions: CssExtensions = {
|
|
1880
|
+
extraCssExtensions: () => [],
|
|
1881
|
+
resolveCssDependency: () => undefined,
|
|
1882
|
+
deferRootNotFoundCss: () => false,
|
|
1883
|
+
inlineStylesheets: () => undefined,
|
|
1884
|
+
cssChunkPlugins: () => [],
|
|
1885
|
+
resolveCssModule: () => undefined,
|
|
1886
|
+
loadCssModuleForClient: () => undefined,
|
|
1887
|
+
planRouteCssChunks: () => new Map(),
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
restoreWithExtensionHost(cssExtensions);
|
|
1891
|
+
|
|
1892
|
+
export function getCssExtensions(): CssExtensions {
|
|
1893
|
+
return cssExtensions;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
export function setCssExtensions(overrides: Partial<CssExtensions>): void {
|
|
1897
|
+
Object.assign(cssExtensions, overrides);
|
|
1898
|
+
}
|