@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,396 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import {
|
|
3
|
+
cacheComponents,
|
|
4
|
+
hangingPromise,
|
|
5
|
+
isPrerendering,
|
|
6
|
+
isRuntimePrefetchPrerender,
|
|
7
|
+
markRuntimeApiOnAwait,
|
|
8
|
+
PostponeError,
|
|
9
|
+
} from '../ppr';
|
|
10
|
+
import type { NextRequest, RouteParamValue } from '../types';
|
|
11
|
+
|
|
12
|
+
// Request work-unit store (CORE seam for compat after()/onRequestError).
|
|
13
|
+
//
|
|
14
|
+
// A single request-scoped AsyncLocalStorage spanning the WHOLE response lifecycle (matching,
|
|
15
|
+
// render/handler, streaming, close). Distinct from the render-scoped requestStorage below, created per
|
|
16
|
+
// render/handler run: the work unit is created once per request in the start/dev handlers and its
|
|
17
|
+
// after-queue is flushed exactly once when the response fully closes - on every path (stream end, redirect,
|
|
18
|
+
// notFound, thrown error, client abort). Flushing runs detached so it never blocks or delays the response.
|
|
19
|
+
//
|
|
20
|
+
// Compat builds after(), onRequestError classification, cookie-mutation phase rules and testmode fetch
|
|
21
|
+
// proxying on top of these accessors. Core only owns the store and the flush.
|
|
22
|
+
|
|
23
|
+
/** The lifecycle phase the request is currently executing in. */
|
|
24
|
+
export type WorkUnitPhase = 'render' | 'action' | 'handler' | 'middleware' | 'after';
|
|
25
|
+
|
|
26
|
+
/** How the matched route is being served — set by the responding call site. */
|
|
27
|
+
export type WorkUnitRouteKind = 'html' | 'data' | 'static-asset' | 'route-handler';
|
|
28
|
+
|
|
29
|
+
/** Per-request work unit carrying phase + the after-task queue. */
|
|
30
|
+
export interface WorkUnit {
|
|
31
|
+
phase: WorkUnitPhase;
|
|
32
|
+
/** Tasks queued via queueAfterTask, drained once on response close. */
|
|
33
|
+
afterTasks: (() => unknown)[];
|
|
34
|
+
/** Guard so the after-queue flushes exactly once across all close paths. */
|
|
35
|
+
flushed: boolean;
|
|
36
|
+
/** How the response is being produced (set by the responding call site). */
|
|
37
|
+
routeKind?: WorkUnitRouteKind;
|
|
38
|
+
/** Caching disposition of the matched route, when known. */
|
|
39
|
+
routeMode?: 'static' | 'isr' | 'dynamic';
|
|
40
|
+
/** Opaque hints for response finalizers. */
|
|
41
|
+
responseHints?: Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Opaque compat scratch space keyed by well-known symbols (testmode proxy
|
|
44
|
+
* info, onRequestError dedupe flag). Core never reads these; it only carries
|
|
45
|
+
* the store across the request lifecycle. Kept off the typed surface so core
|
|
46
|
+
* grows no dependency on compat.
|
|
47
|
+
*/
|
|
48
|
+
compat?: Record<symbol, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Anchored on globalThis (like requestStorage below) so compiled compat
|
|
52
|
+
// bundles that inline their own copy of this module still observe the same
|
|
53
|
+
// per-request work unit — canonical-URL recording happens in core while its
|
|
54
|
+
// readers (pages-router SSR state, navigation hooks) run from built bundles.
|
|
55
|
+
const WORK_UNIT_STORAGE = Symbol.for('pnext.workUnitStorage');
|
|
56
|
+
const workUnitStorage = ((globalThis as Record<PropertyKey, unknown>)[WORK_UNIT_STORAGE] ??=
|
|
57
|
+
new AsyncLocalStorage<WorkUnit>()) as AsyncLocalStorage<WorkUnit>;
|
|
58
|
+
|
|
59
|
+
/** Run `callback` inside a fresh request work unit (start/dev per-request scope). */
|
|
60
|
+
export function runWithWorkUnit<T>(phase: WorkUnitPhase, callback: () => T): T {
|
|
61
|
+
const unit: WorkUnit = { phase, afterTasks: [], flushed: false };
|
|
62
|
+
return workUnitStorage.run(unit, callback);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The active request work unit, or undefined outside a request scope. */
|
|
66
|
+
export function getWorkUnit(): WorkUnit | undefined {
|
|
67
|
+
return workUnitStorage.getStore();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Set the current work-unit phase (compat toggles render/action/handler/...). */
|
|
71
|
+
export function setPhase(phase: WorkUnitPhase): void {
|
|
72
|
+
const unit = workUnitStorage.getStore();
|
|
73
|
+
if (unit) unit.phase = phase;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Record how the current request is being served (drives finalizers + funnel). */
|
|
77
|
+
export function setWorkUnitRoute(
|
|
78
|
+
routeKind: WorkUnitRouteKind,
|
|
79
|
+
routeMode?: WorkUnit['routeMode'],
|
|
80
|
+
responseHints?: Record<string, unknown>,
|
|
81
|
+
): void {
|
|
82
|
+
const unit = workUnitStorage.getStore();
|
|
83
|
+
if (!unit) return;
|
|
84
|
+
unit.routeKind = routeKind;
|
|
85
|
+
if (routeMode) unit.routeMode = routeMode;
|
|
86
|
+
if (responseHints) unit.responseHints = responseHints;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Queue a task to run after the response fully closes. Returns false when no
|
|
91
|
+
* work unit is active. Compat's after() delegates here; core never enqueues.
|
|
92
|
+
*/
|
|
93
|
+
export function queueAfterTask(task: () => unknown): boolean {
|
|
94
|
+
const unit = workUnitStorage.getStore();
|
|
95
|
+
if (!unit) return false;
|
|
96
|
+
unit.afterTasks.push(task);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Drain the active work unit's after-queue exactly once. Detached (awaited only
|
|
102
|
+
* internally) so it never blocks the response; runs inside the work unit so
|
|
103
|
+
* queued tasks can enqueue further tasks (nested after()). Idempotent across the
|
|
104
|
+
* multiple close paths (stream end / redirect / abort / error).
|
|
105
|
+
*/
|
|
106
|
+
export function flushWorkUnit(unit: WorkUnit | undefined = workUnitStorage.getStore()): void {
|
|
107
|
+
if (!unit || unit.flushed) return;
|
|
108
|
+
unit.flushed = true;
|
|
109
|
+
if (unit.afterTasks.length === 0) return;
|
|
110
|
+
// Drain under a SHALLOW COPY carrying `phase: 'after'` rather than mutating
|
|
111
|
+
// the shared unit. The flush is detached and async, so setting `phase` on the
|
|
112
|
+
// unit itself leaked: every later render seen under that unit (the build/PPR
|
|
113
|
+
// work unit is long-lived, not per-request) then tripped
|
|
114
|
+
// `assertRequestApiAllowedInAfter` and 500'd with "used connection() inside
|
|
115
|
+
// after()". Every other field stays shared by reference; only the phase and
|
|
116
|
+
// the queue diverge, and nested after() enqueues onto the copy's queue via
|
|
117
|
+
// the store, which is exactly what this loop drains.
|
|
118
|
+
const afterUnit: WorkUnit = { ...unit, phase: 'after' };
|
|
119
|
+
const flushing = workUnitStorage.run(afterUnit, async () => {
|
|
120
|
+
while (afterUnit.afterTasks.length) {
|
|
121
|
+
const tasks = afterUnit.afterTasks;
|
|
122
|
+
afterUnit.afterTasks = [];
|
|
123
|
+
for (const task of tasks) {
|
|
124
|
+
try {
|
|
125
|
+
await task();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('after() task failed:', error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
pendingFlushes.add(flushing);
|
|
133
|
+
void flushing.finally(() => pendingFlushes.delete(flushing));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const pendingFlushes = new Set<Promise<void>>();
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Await all in-flight after-queue flushes (graceful shutdown). Bounded by
|
|
140
|
+
* timeoutMs so a hung after() task can't block process exit.
|
|
141
|
+
*/
|
|
142
|
+
export async function drainWorkUnits(timeoutMs = 10_000): Promise<void> {
|
|
143
|
+
if (pendingFlushes.size === 0) return;
|
|
144
|
+
await Promise.race([
|
|
145
|
+
Promise.allSettled([...pendingFlushes]),
|
|
146
|
+
new Promise(resolve => setTimeout(resolve, timeoutMs)),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Flush the given work unit's after-queue once the response has fully closed - when its body stream ends (or
|
|
152
|
+
* immediately for a bodyless response) and when the client aborts mid-stream. The flush is detached, never
|
|
153
|
+
* awaited on the response path, so it cannot delay TTFB or stream completion. Returns a response whose body
|
|
154
|
+
* is observed for close; bodyless responses flush immediately.
|
|
155
|
+
*/
|
|
156
|
+
export function flushWorkUnitOnClose(
|
|
157
|
+
response: Response,
|
|
158
|
+
unit: WorkUnit | undefined,
|
|
159
|
+
signal?: AbortSignal,
|
|
160
|
+
): Response {
|
|
161
|
+
if (!unit) return response;
|
|
162
|
+
if (signal) signal.addEventListener('abort', () => flushWorkUnit(unit), { once: true });
|
|
163
|
+
if (!response.body) {
|
|
164
|
+
flushWorkUnit(unit);
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
// An explicit Content-Length means the body is fully buffered upstream (image
|
|
168
|
+
// optimizer, static files) — nothing can enqueue after-tasks while it drains.
|
|
169
|
+
// Re-wrapping it in a close-observing stream would switch the transfer to
|
|
170
|
+
// chunked and drop the Content-Length header; flush immediately instead.
|
|
171
|
+
if (response.headers.has('content-length')) {
|
|
172
|
+
flushWorkUnit(unit);
|
|
173
|
+
return response;
|
|
174
|
+
}
|
|
175
|
+
const observed = response.body.pipeThrough(
|
|
176
|
+
new TransformStream({
|
|
177
|
+
transform(chunk, controller) {
|
|
178
|
+
controller.enqueue(chunk);
|
|
179
|
+
},
|
|
180
|
+
flush() {
|
|
181
|
+
flushWorkUnit(unit);
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
return new Response(observed, {
|
|
186
|
+
status: response.status,
|
|
187
|
+
statusText: response.statusText,
|
|
188
|
+
headers: response.headers,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface RequestScope {
|
|
193
|
+
request: NextRequest;
|
|
194
|
+
params: Record<string, RouteParamValue>;
|
|
195
|
+
/** Work registered via after() from 'next/server', flushed once the request callback settles. */
|
|
196
|
+
afterCallbacks?: (() => unknown)[];
|
|
197
|
+
/** Optional response-headers channel render/handler paths may provide (e.g. draftMode set-cookie). */
|
|
198
|
+
responseHeaders?: Headers;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const REQUEST_STORAGE = Symbol.for('pnext.requestStorage');
|
|
202
|
+
const requestStorage = ((globalThis as Record<PropertyKey, unknown>)[REQUEST_STORAGE] ??=
|
|
203
|
+
new AsyncLocalStorage<RequestScope>()) as AsyncLocalStorage<RequestScope>;
|
|
204
|
+
|
|
205
|
+
export function currentRequest() {
|
|
206
|
+
return requestStorage.getStore()?.request;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Work-unit store bridge for the deep-internal shim
|
|
210
|
+
// `next/dist/server/app-render/work-unit-async-storage.external` (compat).
|
|
211
|
+
//
|
|
212
|
+
// That shim must be genuine CommonJS (apps `require()` it), so it cannot import TypeScript request context;
|
|
213
|
+
// it reads this getter off globalThis instead. Published from here rather than from ppr.ts because this
|
|
214
|
+
// module is where the request storage is globalThis-anchored, i.e. the copy that observes live request
|
|
215
|
+
// scope regardless of which module copy loaded.
|
|
216
|
+
//
|
|
217
|
+
// The store is NEVER undefined: callers do `getStore().type` during build prerenders as well as live
|
|
218
|
+
// requests. `prerender-runtime` mirrors Next's runtime-prefetch work unit; `prerender` covers any other
|
|
219
|
+
// prerender scope, including the fallback when no request scope exists.
|
|
220
|
+
const WORK_UNIT_EXTERNAL = Symbol.for('pnext.workUnitExternal');
|
|
221
|
+
|
|
222
|
+
interface WorkUnitExternalStore {
|
|
223
|
+
type: 'request' | 'prerender' | 'prerender-runtime';
|
|
224
|
+
cookies: unknown;
|
|
225
|
+
headers: Headers;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function workUnitExternalStore(): WorkUnitExternalStore {
|
|
229
|
+
const request = currentRequest();
|
|
230
|
+
const type = isRuntimePrefetchPrerender()
|
|
231
|
+
? 'prerender-runtime'
|
|
232
|
+
: isPrerendering() || !request
|
|
233
|
+
? 'prerender'
|
|
234
|
+
: 'request';
|
|
235
|
+
return {
|
|
236
|
+
type,
|
|
237
|
+
cookies: request?.cookies,
|
|
238
|
+
headers: request?.headers ?? new Headers(),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
(globalThis as Record<PropertyKey, unknown>)[WORK_UNIT_EXTERNAL] = workUnitExternalStore;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Capture the current request scope (if any) and return a wrapper that re-runs a function inside it.
|
|
246
|
+
* after() uses this so a callback registered during a route handler or server action still sees
|
|
247
|
+
* headers()/cookies()/connection() when it runs later during the work-unit flush, which itself carries no
|
|
248
|
+
* request scope. A callback registered during a page render captures no scope, so its request-API access
|
|
249
|
+
* throws - matching Next's phase semantics.
|
|
250
|
+
*/
|
|
251
|
+
export function captureRequestScope(): <T>(fn: () => T) => T {
|
|
252
|
+
const scope = requestStorage.getStore();
|
|
253
|
+
if (!scope) return fn => fn();
|
|
254
|
+
return fn => requestStorage.run(scope, fn);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function currentParams() {
|
|
258
|
+
return requestStorage.getStore()?.params ?? {};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function runWithRequest<T>(
|
|
262
|
+
request: NextRequest | undefined,
|
|
263
|
+
callback: () => T,
|
|
264
|
+
params: Record<string, RouteParamValue> = {},
|
|
265
|
+
extras: { responseHeaders?: Headers } = {},
|
|
266
|
+
): T {
|
|
267
|
+
if (!request) return callback();
|
|
268
|
+
const scope: RequestScope = {
|
|
269
|
+
request,
|
|
270
|
+
params,
|
|
271
|
+
afterCallbacks: [],
|
|
272
|
+
responseHeaders: extras.responseHeaders,
|
|
273
|
+
};
|
|
274
|
+
const result = requestStorage.run(scope, callback);
|
|
275
|
+
if (isThenable(result)) {
|
|
276
|
+
return (result as PromiseLike<unknown>).then(
|
|
277
|
+
value => {
|
|
278
|
+
void flushAfterCallbacks(scope);
|
|
279
|
+
return value;
|
|
280
|
+
},
|
|
281
|
+
error => {
|
|
282
|
+
void flushAfterCallbacks(scope);
|
|
283
|
+
throw error;
|
|
284
|
+
},
|
|
285
|
+
) as T;
|
|
286
|
+
}
|
|
287
|
+
void flushAfterCallbacks(scope);
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Register a callback to run once the surrounding runWithRequest callback
|
|
293
|
+
* settles. Returns false when no request scope is active.
|
|
294
|
+
*/
|
|
295
|
+
export function registerAfterCallback(callback: () => unknown): boolean {
|
|
296
|
+
const scope = requestStorage.getStore();
|
|
297
|
+
if (!scope) return false;
|
|
298
|
+
(scope.afterCallbacks ??= []).push(callback);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Response-headers channel for the active request scope, when a caller provided one. */
|
|
303
|
+
export function currentResponseHeaders(): Headers | undefined {
|
|
304
|
+
return requestStorage.getStore()?.responseHeaders;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function flushAfterCallbacks(scope: RequestScope) {
|
|
308
|
+
while (scope.afterCallbacks?.length) {
|
|
309
|
+
const callbacks = scope.afterCallbacks;
|
|
310
|
+
scope.afterCallbacks = [];
|
|
311
|
+
for (const callback of callbacks) {
|
|
312
|
+
try {
|
|
313
|
+
await requestStorage.run(scope, callback);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('after() callback failed:', error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
322
|
+
return typeof (value as { then?: unknown } | null | undefined)?.then === 'function';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Build-time bailout probe: while active, every request API throws Next's
|
|
327
|
+
* DynamicServerError instead of reading the synthetic prerender request. The
|
|
328
|
+
* build uses it to find a request API call that OUTLIVES the render (scheduled
|
|
329
|
+
* in a timer), which is the shape Next surfaces as a prerender error. Anchored
|
|
330
|
+
* on globalThis so a compiled bundle carrying its own copy of this module reads
|
|
331
|
+
* the same flag, and module-level rather than ALS-scoped because the calls it
|
|
332
|
+
* looks for run outside the render's async context by definition.
|
|
333
|
+
*/
|
|
334
|
+
const BAILOUT_PROBE = Symbol.for('pnext.dynamicBailoutProbe');
|
|
335
|
+
|
|
336
|
+
export function beginDynamicBailoutProbe(route: string): void {
|
|
337
|
+
(globalThis as Record<PropertyKey, unknown>)[BAILOUT_PROBE] = route;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function endDynamicBailoutProbe(): void {
|
|
341
|
+
delete (globalThis as Record<PropertyKey, unknown>)[BAILOUT_PROBE];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function dynamicBailoutRoute(): string | undefined {
|
|
345
|
+
const route = (globalThis as Record<PropertyKey, unknown>)[BAILOUT_PROBE];
|
|
346
|
+
return typeof route === 'string' ? route : undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function requireRequest(api: string) {
|
|
350
|
+
const probeRoute = dynamicBailoutRoute();
|
|
351
|
+
if (probeRoute !== undefined) {
|
|
352
|
+
const error = new Error(
|
|
353
|
+
`Route ${probeRoute} couldn't be rendered statically because it used \`${api.replace(/\(\)$/, '')}\`. ` +
|
|
354
|
+
`See more info here: https://nextjs.org/docs/messages/dynamic-server-error`,
|
|
355
|
+
);
|
|
356
|
+
error.name = 'DynamicServerError';
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
const request = currentRequest();
|
|
360
|
+
if (!request) {
|
|
361
|
+
// In a partial prerender, request access marks the surrounding Suspense
|
|
362
|
+
// boundary as a dynamic hole rather than failing the build.
|
|
363
|
+
if (isPrerendering()) throw new PostponeError(api);
|
|
364
|
+
throw new Error(`${api} can only be used while rendering a dynamic PNext request.`);
|
|
365
|
+
}
|
|
366
|
+
return request;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Produce the value of an async request API (cookies/headers/params/searchParams). At request time, or under
|
|
371
|
+
* a legacy prerender, it resolves to `produce()`. Under a cacheComponents prerender it returns a HANGING
|
|
372
|
+
* PROMISE so that creating the promise does not postpone - only awaiting it inside a Suspense subtree does
|
|
373
|
+
* (dynamic-at-await-site).
|
|
374
|
+
*/
|
|
375
|
+
export function requestApiPromise<T>(api: string, produce: () => T): Promise<T> {
|
|
376
|
+
// Under a cacheComponents prerender the value hangs: awaiting it postpones the
|
|
377
|
+
// boundary, while merely observing its rejection (the request-apis stash-and-
|
|
378
|
+
// observe pattern) yields the "rejects when the prerender is complete" message
|
|
379
|
+
// — see hangingPromise.
|
|
380
|
+
if (isPrerendering() && cacheComponents()) {
|
|
381
|
+
// A runtime-prefetch prerender (`unstable_instant`) samples a real request:
|
|
382
|
+
// cookies()/headers()/params resolve from it, so their content lands in the
|
|
383
|
+
// prefetch response. Only connection() stays a hanging promise — it marks
|
|
384
|
+
// content that must NEVER be served from a prefetch.
|
|
385
|
+
if (isRuntimePrefetchPrerender() && api !== 'connection()') {
|
|
386
|
+
// Awaiting the resolved value arms the sync-IO abort: anything the render
|
|
387
|
+
// computes with Date.now() & co. AFTER a runtime API resolved belongs to
|
|
388
|
+
// the navigation, not the prefetch (see runWithRuntimePrefetchSyncIoAbort).
|
|
389
|
+
return markRuntimeApiOnAwait(Promise.resolve(produce()));
|
|
390
|
+
}
|
|
391
|
+
return hangingPromise<T>(api);
|
|
392
|
+
}
|
|
393
|
+
// Legacy PPR (experimental_ppr, cacheComponents off): keep the call-site
|
|
394
|
+
// throw so the existing shell path postpones exactly as before.
|
|
395
|
+
return Promise.resolve(produce());
|
|
396
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ResolverFactory, type NapiResolveOptions } from 'oxc-resolver';
|
|
4
|
+
import { clearNodeModulesFsCache } from '../utils/fs-cache';
|
|
5
|
+
|
|
6
|
+
const sourceExtensions = ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.mts', '.cts', '.module.css', '.css'];
|
|
7
|
+
|
|
8
|
+
// An ESM `import './x.js'` is authored against the source file that compiles to
|
|
9
|
+
// it — a source convention only (see isSourceRequest / probePackageTarget).
|
|
10
|
+
const extensionAlias = {
|
|
11
|
+
'.js': ['.ts', '.tsx', '.js'],
|
|
12
|
+
'.jsx': ['.tsx', '.jsx'],
|
|
13
|
+
'.mjs': ['.mts', '.mjs'],
|
|
14
|
+
'.cjs': ['.cts', '.cjs'],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface ResolvedModule {
|
|
18
|
+
path: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The package answered "no" for this subpath (Node's ERR_PACKAGE_PATH_NOT_EXPORTED). */
|
|
22
|
+
export interface UnexportedModule {
|
|
23
|
+
notExported: true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ResolveOutcome = ResolvedModule | UnexportedModule | undefined;
|
|
27
|
+
|
|
28
|
+
let configuredExtensions: string[] | undefined;
|
|
29
|
+
let configuredTsConfigPath: string | undefined;
|
|
30
|
+
|
|
31
|
+
let baseFactory: ResolverFactory | undefined;
|
|
32
|
+
const factories = new Map<string, ResolverFactory>();
|
|
33
|
+
const tsConfigFiles = new Map<string, string | undefined>();
|
|
34
|
+
|
|
35
|
+
export function setEngineExtensions(extensions: readonly string[] | undefined): void {
|
|
36
|
+
configuredExtensions = extensions && extensions.length > 0 ? [...extensions] : undefined;
|
|
37
|
+
resetEngine();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function setEngineTsConfigPath(file: string | undefined): void {
|
|
41
|
+
configuredTsConfigPath = file;
|
|
42
|
+
resetEngine();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resetEngine() {
|
|
46
|
+
baseFactory = undefined;
|
|
47
|
+
factories.clear();
|
|
48
|
+
tsConfigFiles.clear();
|
|
49
|
+
nodeModulesOutcomes.clear();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Drop oxc's cached filesystem lookups (positive AND negative) so a file
|
|
54
|
+
* created/deleted since the last resolve is seen. Dev-only: the factories stay
|
|
55
|
+
* constructed, only their fs cache is emptied.
|
|
56
|
+
*/
|
|
57
|
+
export function clearResolverFsCache(): void {
|
|
58
|
+
baseFactory?.clearCache();
|
|
59
|
+
for (const factory of factories.values()) factory.clearCache();
|
|
60
|
+
nodeModulesOutcomes.clear();
|
|
61
|
+
clearNodeModulesFsCache();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The tsconfig (or jsconfig) driving `paths`/`baseUrl` for a resolution root. */
|
|
65
|
+
export function tsConfigFileFor(root: string): string | undefined {
|
|
66
|
+
const key = path.resolve(root);
|
|
67
|
+
if (tsConfigFiles.has(key)) return tsConfigFiles.get(key);
|
|
68
|
+
const candidates = [
|
|
69
|
+
...(configuredTsConfigPath ? [path.resolve(key, configuredTsConfigPath)] : []),
|
|
70
|
+
path.join(key, 'tsconfig.json'),
|
|
71
|
+
path.join(key, 'jsconfig.json'),
|
|
72
|
+
];
|
|
73
|
+
const file = candidates.find(candidate => existsSync(candidate));
|
|
74
|
+
tsConfigFiles.set(key, file);
|
|
75
|
+
return file;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Split a webpack resource specifier into its file request and its `?query#fragment` suffix - loader
|
|
80
|
+
* plumbing (turbopack rules, `.wasm?module`) that takes no part in the on-disk lookup. A leading `#` is a
|
|
81
|
+
* package-imports specifier, not a fragment.
|
|
82
|
+
*/
|
|
83
|
+
export function splitResourceQuery(specifier: string): { path: string; query: string } {
|
|
84
|
+
const offset = specifier.startsWith('#') ? 1 : 0;
|
|
85
|
+
const match = /[?#]/.exec(specifier.slice(offset));
|
|
86
|
+
if (!match) return { path: specifier, query: '' };
|
|
87
|
+
const index = offset + match.index;
|
|
88
|
+
return { path: specifier.slice(0, index), query: specifier.slice(index) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolve `specifier` from `dir`. `tsConfigRoot` opts the lookup into that
|
|
93
|
+
* root's tsconfig `paths`; package lookups deliberately pass none.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveFrom(
|
|
96
|
+
dir: string,
|
|
97
|
+
specifier: string,
|
|
98
|
+
conditions: readonly string[],
|
|
99
|
+
tsConfigRoot?: string,
|
|
100
|
+
): ResolveOutcome {
|
|
101
|
+
const { path: request } = splitResourceQuery(specifier);
|
|
102
|
+
const tsconfig = tsConfigRoot ? tsConfigFileFor(tsConfigRoot) : undefined;
|
|
103
|
+
return resolveRequest(dir, request, conditions, tsconfig, isSourceRequest(request));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Probe a literal on-disk path with the resolver's extension/index rules. */
|
|
107
|
+
export function probeFile(target: string, conditions: readonly string[]): string | undefined {
|
|
108
|
+
return probe(target, conditions, true);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Probe a package's `exports`/`main` TARGET (or an on-disk subpath standing in
|
|
113
|
+
* for one). Same extension/index rules as probeFile, minus the `.js` -> `.ts`
|
|
114
|
+
* source alias: Node and webpack read a package's stated target literally, so a
|
|
115
|
+
* stale `x.ts` next to the built `x.js` must never win.
|
|
116
|
+
*/
|
|
117
|
+
export function probePackageTarget(target: string, conditions: readonly string[]): string | undefined {
|
|
118
|
+
return probe(target, conditions, false);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function probe(target: string, conditions: readonly string[], sourceAlias: boolean) {
|
|
122
|
+
const { path: file } = splitResourceQuery(target);
|
|
123
|
+
const request = `./${path.basename(file)}`;
|
|
124
|
+
const outcome = resolveRequest(path.dirname(file), request, conditions, undefined, sourceAlias);
|
|
125
|
+
return outcome && 'path' in outcome ? outcome.path : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Runtime-cache reset (bench/tests): drop memoized node_modules resolutions. */
|
|
129
|
+
export function clearNodeModulesResolutionCache(): void {
|
|
130
|
+
nodeModulesOutcomes.clear();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Lookups FROM node_modules only: installed layouts are process-immutable,
|
|
134
|
+
// app-source resolution must stay live under watch (see clearResolverFsCache).
|
|
135
|
+
const nodeModulesOutcomes = new Map<string, ResolveOutcome>();
|
|
136
|
+
const NODE_MODULES_SEGMENT = `${path.sep}node_modules${path.sep}`;
|
|
137
|
+
|
|
138
|
+
function resolveRequest(
|
|
139
|
+
dir: string,
|
|
140
|
+
request: string,
|
|
141
|
+
conditions: readonly string[],
|
|
142
|
+
tsconfig: string | undefined,
|
|
143
|
+
sourceAlias: boolean,
|
|
144
|
+
): ResolveOutcome {
|
|
145
|
+
const key = dir.includes(NODE_MODULES_SEGMENT)
|
|
146
|
+
? `${dir}\0${request}\0${conditions.join(',')}\0${tsconfig ?? ''}\0${sourceAlias}`
|
|
147
|
+
: undefined;
|
|
148
|
+
if (key !== undefined && nodeModulesOutcomes.has(key)) return nodeModulesOutcomes.get(key);
|
|
149
|
+
const { path: file, error } = resolverFor(conditions, tsconfig, sourceAlias).sync(dir, request);
|
|
150
|
+
// Declaration files carry no runtime and are never a valid resolution.
|
|
151
|
+
const outcome: ResolveOutcome = file
|
|
152
|
+
? isDeclarationFile(file)
|
|
153
|
+
? undefined
|
|
154
|
+
: { path: file }
|
|
155
|
+
: error?.includes('is not exported')
|
|
156
|
+
? { notExported: true }
|
|
157
|
+
: undefined;
|
|
158
|
+
if (key !== undefined) nodeModulesOutcomes.set(key, outcome);
|
|
159
|
+
return outcome;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// oxc applies `extensionAlias` to the target it reads out of a package's
|
|
163
|
+
// `exports`/`main` too, where the target is literal — so only a source request
|
|
164
|
+
// (relative or absolute, authored against the file its own build emits) gets
|
|
165
|
+
// the `.js` -> `.ts` rewrite. Bare specifiers are package lookups.
|
|
166
|
+
function isSourceRequest(request: string) {
|
|
167
|
+
return request.startsWith('.') || path.isAbsolute(request);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resolverFor(
|
|
171
|
+
conditions: readonly string[],
|
|
172
|
+
tsconfig: string | undefined,
|
|
173
|
+
sourceExtensionAlias: boolean,
|
|
174
|
+
) {
|
|
175
|
+
const key = `${conditions.join(',')}\0${tsconfig ?? ''}\0${sourceExtensionAlias}`;
|
|
176
|
+
let resolver = factories.get(key);
|
|
177
|
+
if (!resolver) {
|
|
178
|
+
// cloneWithOptions replaces the option set rather than merging it, so the
|
|
179
|
+
// base options are repeated here; the clone shares the base file-system cache.
|
|
180
|
+
const options: NapiResolveOptions = {
|
|
181
|
+
...baseOptions(),
|
|
182
|
+
extensionAlias: sourceExtensionAlias ? extensionAlias : {},
|
|
183
|
+
conditionNames: [...conditions],
|
|
184
|
+
mainFields: mainFieldsFor(conditions),
|
|
185
|
+
};
|
|
186
|
+
if (tsconfig) options.tsconfig = { configFile: tsconfig };
|
|
187
|
+
resolver = baseResolver().cloneWithOptions(options);
|
|
188
|
+
factories.set(key, resolver);
|
|
189
|
+
}
|
|
190
|
+
return resolver;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function baseOptions(): NapiResolveOptions {
|
|
194
|
+
return {
|
|
195
|
+
extensions: configuredExtensions ?? sourceExtensions,
|
|
196
|
+
// Symlink policy is the caller's (see realLinkedPackageFile in imports.ts):
|
|
197
|
+
// pnpm virtual-store paths carry version identity that must not collapse.
|
|
198
|
+
symlinks: false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function baseResolver() {
|
|
203
|
+
baseFactory ??= new ResolverFactory(baseOptions());
|
|
204
|
+
return baseFactory;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// `edge-light`/`browser` are plain package.json main fields in Next/webpack, not
|
|
208
|
+
// only `exports` conditions — a package shipping just `main`/`module`/`edge-light`
|
|
209
|
+
// must still prefer `edge-light` on the edge layer.
|
|
210
|
+
function mainFieldsFor(conditions: readonly string[]) {
|
|
211
|
+
const fields = ['module', 'main'];
|
|
212
|
+
if (conditions.includes('browser')) fields.unshift('browser');
|
|
213
|
+
if (conditions.includes('edge-light')) fields.unshift('edge-light');
|
|
214
|
+
return fields;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isDeclarationFile(file: string) {
|
|
218
|
+
return /\.d\.[cm]?ts$/.test(file);
|
|
219
|
+
}
|