@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,278 @@
|
|
|
1
|
+
// Web Worker bundling (COMPAT - `new Worker(new URL('./w', import.meta.url))`).
|
|
2
|
+
//
|
|
3
|
+
// Next/webpack/turbopack detect this pattern (and SharedWorker) in client code, bundle the referenced
|
|
4
|
+
// module as its own entry chunk, and swap the URL expression for the emitted chunk URL. esbuild has no
|
|
5
|
+
// native support, so this pass:
|
|
6
|
+
// 1. runs as the client-source chain's async PRE-transform, ahead of the sync passes, one of which
|
|
7
|
+
// inlines the `import.meta.url` this pattern needs,
|
|
8
|
+
// 2. resolves each worker specifier (honoring TS/JSX extensions and any configured resolveExtensions),
|
|
9
|
+
// 3. bundles the worker file with its own esbuild pass - IIFE output so classic AND module workers
|
|
10
|
+
// both execute it, with internal dynamic imports inlined,
|
|
11
|
+
// 4. rewrites the `new URL(...)` argument to the emitted chunk's public URL string literal.
|
|
12
|
+
//
|
|
13
|
+
// Worker graphs get their own static-asset handling, reusing the compat staticAssetModule extension for
|
|
14
|
+
// images and a generic `{ src }` module for other assets.
|
|
15
|
+
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { existsSync, statSync } from 'node:fs';
|
|
18
|
+
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { build, type Plugin } from 'esbuild';
|
|
21
|
+
import { publicEnvDefines, type ResolvedConfig } from '../../config';
|
|
22
|
+
import { getAssetExtensions } from '../../extensions';
|
|
23
|
+
|
|
24
|
+
// `new Worker(new URL('<spec>', import.meta.url)` / SharedWorker — the URL
|
|
25
|
+
// argument must be a string literal and the base exactly `import.meta.url`.
|
|
26
|
+
const workerPattern = () =>
|
|
27
|
+
/\bnew\s+(Worker|SharedWorker)\(\s*new\s+URL\(\s*(['"])([^'"]+)\2\s*,\s*import\.meta\.url\s*\)/g;
|
|
28
|
+
|
|
29
|
+
/** Cheap gate: does this source construct a Worker from a `new URL(...)`? */
|
|
30
|
+
export function sourceHasWorkerNewUrl(source: string): boolean {
|
|
31
|
+
return (
|
|
32
|
+
(source.includes('new Worker(') || source.includes('new SharedWorker(')) &&
|
|
33
|
+
workerPattern().test(source)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const workerSpecifierExtensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.mts'];
|
|
38
|
+
|
|
39
|
+
function isFile(file: string): boolean {
|
|
40
|
+
try {
|
|
41
|
+
return statSync(file).isFile();
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveWorkerEntry(
|
|
48
|
+
importerDir: string,
|
|
49
|
+
specifier: string,
|
|
50
|
+
configuredExtensions: readonly string[] | undefined,
|
|
51
|
+
): string | undefined {
|
|
52
|
+
if (!specifier.startsWith('.')) return undefined;
|
|
53
|
+
const base = path.resolve(importerDir, specifier);
|
|
54
|
+
if (path.extname(base) && isFile(base)) return base;
|
|
55
|
+
for (const extension of configuredExtensions ?? workerSpecifierExtensions) {
|
|
56
|
+
if (extension === '') {
|
|
57
|
+
if (isFile(base)) return base;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const candidate = `${base}${extension}`;
|
|
61
|
+
if (isFile(candidate)) return candidate;
|
|
62
|
+
}
|
|
63
|
+
// A configured extension list that misses the worker's real extension must
|
|
64
|
+
// not break the build — fall back to the built-in source order.
|
|
65
|
+
if (configuredExtensions) {
|
|
66
|
+
for (const extension of workerSpecifierExtensions) {
|
|
67
|
+
const candidate = `${base}${extension}`;
|
|
68
|
+
if (isFile(candidate)) return candidate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return isFile(base) ? base : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Any importable asset a worker may reference: static images (compat descriptor
|
|
75
|
+
// module) plus generic binary assets (.wasm) exposed as `{ src }`.
|
|
76
|
+
const workerAssetFilter = /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp|wasm)(?:$|[?#])/;
|
|
77
|
+
|
|
78
|
+
// Workers fetch `.wasm` via `new URL('./x.wasm', import.meta.url)`; rewrite it
|
|
79
|
+
// (and image URLs) onto the asset import pipeline exactly like the main-graph
|
|
80
|
+
// new-url-asset transform, but with `.wasm` included.
|
|
81
|
+
const workerNewUrlAssetPattern = () =>
|
|
82
|
+
/new\s+URL\(\s*(['"])([^'"]+?\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp|wasm)(?:[?#][^'"]*)?)\1\s*,\s*import\.meta\.url\s*\)/g;
|
|
83
|
+
|
|
84
|
+
function rewriteWorkerNewUrlAssets(source: string): string {
|
|
85
|
+
if (!source.includes('import.meta.url')) return source;
|
|
86
|
+
const bindings = new Map<string, string>();
|
|
87
|
+
const replaced = source.replace(workerNewUrlAssetPattern(), (_match, _quote, spec: string) => {
|
|
88
|
+
let binding = bindings.get(spec);
|
|
89
|
+
if (!binding) {
|
|
90
|
+
binding = `__pnext_worker_url_${bindings.size}`;
|
|
91
|
+
bindings.set(spec, binding);
|
|
92
|
+
}
|
|
93
|
+
return `(${binding}.src)`;
|
|
94
|
+
});
|
|
95
|
+
if (bindings.size === 0) return source;
|
|
96
|
+
const imports = [...bindings]
|
|
97
|
+
.map(([spec, binding]) => `import ${binding} from ${JSON.stringify(spec)};`)
|
|
98
|
+
.join('\n');
|
|
99
|
+
return `${imports}\n${replaced}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function splitAssetHash(specifier: string) {
|
|
103
|
+
const index = specifier.search(/[?#]/);
|
|
104
|
+
return index === -1
|
|
105
|
+
? { sourcePath: specifier, hash: '' }
|
|
106
|
+
: { sourcePath: specifier.slice(0, index), hash: specifier.slice(index) };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function workerAssetModuleSource(config: ResolvedConfig, file: string): Promise<string> {
|
|
110
|
+
const { sourcePath } = splitAssetHash(file);
|
|
111
|
+
const bytes = new Uint8Array(await readFile(sourcePath));
|
|
112
|
+
const emitted: string[] = [];
|
|
113
|
+
const emit = (relative: string) => {
|
|
114
|
+
emitted.push(relative);
|
|
115
|
+
return `/${relative}`;
|
|
116
|
+
};
|
|
117
|
+
let source = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit });
|
|
118
|
+
if (!source) {
|
|
119
|
+
const ext = path.extname(sourcePath).toLowerCase() || '.bin';
|
|
120
|
+
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
|
|
121
|
+
const base = path
|
|
122
|
+
.basename(sourcePath, path.extname(sourcePath))
|
|
123
|
+
.replace(/[^A-Za-z0-9_-]+/g, '-');
|
|
124
|
+
const relative = getAssetExtensions().staticAssetRelativePath({ sourcePath, base, hash, ext });
|
|
125
|
+
const src = emit(relative);
|
|
126
|
+
// `.src` (worker new-URL rewrite) and default-as-string both observable.
|
|
127
|
+
source = `const src = ${JSON.stringify(src)};\nexport default { src };\nexport { src };\n`;
|
|
128
|
+
}
|
|
129
|
+
for (const relative of emitted) {
|
|
130
|
+
const target = path.join(config.outPath, 'public', ...relative.split('/'));
|
|
131
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
132
|
+
if (!existsSync(target)) await copyFile(sourcePath, target);
|
|
133
|
+
}
|
|
134
|
+
return source;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function workerAssetPlugin(config: ResolvedConfig): Plugin {
|
|
138
|
+
return {
|
|
139
|
+
name: 'pnext-worker-static-assets',
|
|
140
|
+
setup(build) {
|
|
141
|
+
// Worker-graph sources referencing assets via `new URL(..., import.meta.url)`
|
|
142
|
+
// (esbuild would otherwise mangle `import.meta.url` in the iife output) are
|
|
143
|
+
// rewritten onto the asset import pipeline below.
|
|
144
|
+
build.onLoad({ filter: /\.(?:js|mjs|jsx|ts|tsx|mts)$/, namespace: 'file' }, async args => {
|
|
145
|
+
const source = await readFile(args.path, 'utf8');
|
|
146
|
+
if (!workerNewUrlAssetPattern().test(source)) return undefined;
|
|
147
|
+
const loader = args.path.endsWith('.ts')
|
|
148
|
+
? ('ts' as const)
|
|
149
|
+
: args.path.endsWith('.mts')
|
|
150
|
+
? ('ts' as const)
|
|
151
|
+
: args.path.endsWith('.tsx')
|
|
152
|
+
? ('tsx' as const)
|
|
153
|
+
: ('jsx' as const);
|
|
154
|
+
return { contents: rewriteWorkerNewUrlAssets(source), loader };
|
|
155
|
+
});
|
|
156
|
+
build.onResolve({ filter: workerAssetFilter }, args => {
|
|
157
|
+
const { sourcePath, hash } = splitAssetHash(args.path);
|
|
158
|
+
const resolved = path.isAbsolute(sourcePath)
|
|
159
|
+
? sourcePath
|
|
160
|
+
: path.resolve(args.resolveDir, sourcePath);
|
|
161
|
+
return { path: `${resolved}${hash}`, namespace: 'pnext-worker-asset' };
|
|
162
|
+
});
|
|
163
|
+
build.onLoad({ filter: /.*/, namespace: 'pnext-worker-asset' }, async args => ({
|
|
164
|
+
contents: await workerAssetModuleSource(config, args.path),
|
|
165
|
+
loader: 'js',
|
|
166
|
+
}));
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function workerDefines(): Record<string, string> {
|
|
172
|
+
const defines: Record<string, string> = {
|
|
173
|
+
...publicEnvDefines(),
|
|
174
|
+
'process.browser': 'true',
|
|
175
|
+
};
|
|
176
|
+
// Next inlines the deployment id into every graph, workers included.
|
|
177
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
178
|
+
const deploymentId = process.env.NEXT_DEPLOYMENT_ID;
|
|
179
|
+
if (deploymentId !== undefined) {
|
|
180
|
+
defines['process.env.NEXT_DEPLOYMENT_ID'] = JSON.stringify(deploymentId);
|
|
181
|
+
}
|
|
182
|
+
return defines;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Bundle one worker entry into `<out>/public/assets/workers/` and return its
|
|
187
|
+
* public URL path. Deduplicated per resolved entry file across the whole
|
|
188
|
+
* client build via the caller's cache.
|
|
189
|
+
*/
|
|
190
|
+
async function bundleWorkerEntry(config: ResolvedConfig, entry: string): Promise<string> {
|
|
191
|
+
const result = await build({
|
|
192
|
+
entryPoints: [entry],
|
|
193
|
+
bundle: true,
|
|
194
|
+
write: false,
|
|
195
|
+
// IIFE runs under both `new Worker(url)` (classic) and `{ type: 'module' }`
|
|
196
|
+
// workers; internal dynamic imports are inlined (no splitting), so no
|
|
197
|
+
// runtime chunk loader is needed.
|
|
198
|
+
format: 'iife',
|
|
199
|
+
platform: 'browser',
|
|
200
|
+
minify: true,
|
|
201
|
+
define: workerDefines(),
|
|
202
|
+
plugins: [workerAssetPlugin(config)],
|
|
203
|
+
logLevel: 'silent',
|
|
204
|
+
});
|
|
205
|
+
const output = result.outputFiles[0];
|
|
206
|
+
if (!output) throw new Error(`pnext: worker bundle produced no output for ${entry}`);
|
|
207
|
+
const hash = createHash('sha256').update(output.contents).digest('hex').slice(0, 8);
|
|
208
|
+
const base = path.basename(entry, path.extname(entry)).replace(/[^A-Za-z0-9_-]+/g, '-');
|
|
209
|
+
const relative = path.posix.join('assets', 'workers', `${base}.${hash}.js`);
|
|
210
|
+
const target = path.join(config.outPath, 'public', ...relative.split('/'));
|
|
211
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
212
|
+
await writeFile(target, output.contents);
|
|
213
|
+
return `/${relative}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* CLIENT-graph source transform: bundle each worker entry a source constructs
|
|
218
|
+
* from `new URL(..., import.meta.url)` and substitute the emitted chunk URL.
|
|
219
|
+
* The bundle cache is per-transform (one per registration), so a worker shared
|
|
220
|
+
* by many modules is built once per build.
|
|
221
|
+
*/
|
|
222
|
+
export function createClientWorkerTransform(
|
|
223
|
+
config: ResolvedConfig,
|
|
224
|
+
configuredExtensions: readonly string[] | undefined,
|
|
225
|
+
): (source: string, file: string) => Promise<string> {
|
|
226
|
+
const bundleCache = new Map<string, Promise<string>>();
|
|
227
|
+
return async (source, file) => {
|
|
228
|
+
if (!sourceHasWorkerNewUrl(source)) return source;
|
|
229
|
+
const rewritten = await rewriteWorkerSource(
|
|
230
|
+
config,
|
|
231
|
+
source,
|
|
232
|
+
file,
|
|
233
|
+
bundleCache,
|
|
234
|
+
configuredExtensions,
|
|
235
|
+
);
|
|
236
|
+
return rewritten ?? source;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Rewrite every worker construction in `source` (resolving + bundling each
|
|
242
|
+
* referenced worker module) and return the transformed source, or undefined
|
|
243
|
+
* when nothing matched/resolved (caller falls through to the normal loader).
|
|
244
|
+
*/
|
|
245
|
+
export async function rewriteWorkerSource(
|
|
246
|
+
config: ResolvedConfig,
|
|
247
|
+
source: string,
|
|
248
|
+
file: string,
|
|
249
|
+
bundleCache: Map<string, Promise<string>>,
|
|
250
|
+
configuredExtensions: readonly string[] | undefined,
|
|
251
|
+
): Promise<string | undefined> {
|
|
252
|
+
const importerDir = path.dirname(file);
|
|
253
|
+
const matches = [...source.matchAll(workerPattern())];
|
|
254
|
+
if (matches.length === 0) return undefined;
|
|
255
|
+
|
|
256
|
+
const urls = new Map<string, string>();
|
|
257
|
+
for (const match of matches) {
|
|
258
|
+
const spec = match[3]!;
|
|
259
|
+
if (urls.has(spec)) continue;
|
|
260
|
+
const entry = resolveWorkerEntry(importerDir, spec, configuredExtensions);
|
|
261
|
+
if (!entry) continue; // unresolvable: leave the construction untouched
|
|
262
|
+
let pending = bundleCache.get(entry);
|
|
263
|
+
if (!pending) {
|
|
264
|
+
pending = bundleWorkerEntry(config, entry);
|
|
265
|
+
bundleCache.set(entry, pending);
|
|
266
|
+
}
|
|
267
|
+
urls.set(spec, await pending);
|
|
268
|
+
}
|
|
269
|
+
if (urls.size === 0) return undefined;
|
|
270
|
+
|
|
271
|
+
return source.replace(
|
|
272
|
+
workerPattern(),
|
|
273
|
+
(match, ctor: string, _quote: string, spec: string) => {
|
|
274
|
+
const url = urls.get(spec);
|
|
275
|
+
return url ? `new ${ctor}(${JSON.stringify(url)}` : match;
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Build flags the SERVING runtime needs (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// `next build --debug-prerender` changes what a production server logs for an error that crosses the
|
|
4
|
+
// 'use cache' boundary (real frames + environmentName vs minified frames + a redacted error). The flag
|
|
5
|
+
// is a build-time input, so the build records it next to the other server artifacts and start reads it
|
|
6
|
+
// back.
|
|
7
|
+
//
|
|
8
|
+
// State is anchored on globalThis: the built compat-server bundle and the original module can both be
|
|
9
|
+
// loaded in one process, and a module-local cache would then be filled in one copy and empty in the other.
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
const BUILD_FLAG_GLOBALS = Symbol.for('pnext.compat.buildFlags');
|
|
15
|
+
|
|
16
|
+
export interface BuildFlags {
|
|
17
|
+
debugPrerender: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface BuildFlagState {
|
|
21
|
+
/** The serving root's output dir, recorded by the init hook. */
|
|
22
|
+
outPath?: string;
|
|
23
|
+
/** Lazily read flags: `null` once a read found no build to serve. */
|
|
24
|
+
flags?: BuildFlags | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function state(): BuildFlagState {
|
|
28
|
+
const root = globalThis as unknown as Record<PropertyKey, BuildFlagState | undefined>;
|
|
29
|
+
return (root[BUILD_FLAG_GLOBALS] ??= {});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function flagsFile(outPath: string): string {
|
|
33
|
+
return path.join(outPath, 'server', 'pnext-build-flags.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Record the output dir at build/start init so the runtime can find the file. */
|
|
37
|
+
export function setBuildFlagsOutPath(outPath: string): void {
|
|
38
|
+
const current = state();
|
|
39
|
+
if (current.outPath !== outPath) {
|
|
40
|
+
current.outPath = outPath;
|
|
41
|
+
current.flags = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Persist the build's `--debug-prerender` flag for the serving runtime. */
|
|
46
|
+
export function writeBuildFlags(outPath: string, debugPrerender: boolean): void {
|
|
47
|
+
const file = flagsFile(outPath);
|
|
48
|
+
try {
|
|
49
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
50
|
+
writeFileSync(file, `${JSON.stringify({ debugPrerender })}\n`);
|
|
51
|
+
} catch {
|
|
52
|
+
// A read-only/absent output dir only costs the runtime this hint.
|
|
53
|
+
}
|
|
54
|
+
const current = state();
|
|
55
|
+
current.outPath = outPath;
|
|
56
|
+
current.flags = { debugPrerender };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The flags of the build being served, or undefined when there is none to read
|
|
61
|
+
* (a dev server, or a start against an output dir this process never built).
|
|
62
|
+
* Callers use the absence to stay out of production-only behavior.
|
|
63
|
+
*/
|
|
64
|
+
export function servedBuildFlags(): BuildFlags | undefined {
|
|
65
|
+
const current = state();
|
|
66
|
+
if (current.flags !== undefined) return current.flags ?? undefined;
|
|
67
|
+
const outPath = current.outPath;
|
|
68
|
+
if (outPath === undefined) return undefined;
|
|
69
|
+
let flags: BuildFlags | null = null;
|
|
70
|
+
try {
|
|
71
|
+
const file = flagsFile(outPath);
|
|
72
|
+
if (existsSync(file)) {
|
|
73
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8')) as { debugPrerender?: unknown };
|
|
74
|
+
flags = { debugPrerender: parsed.debugPrerender === true };
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
flags = null;
|
|
78
|
+
}
|
|
79
|
+
current.flags = flags;
|
|
80
|
+
return flags ?? undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Build-time `use cache` validation error collection (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Three classes of `use cache` misuse must FAIL the build with Next's exact error text (the e2e suites
|
|
4
|
+
// substring-match on the build output): a cache fill that never resolves during a prerender (E236
|
|
5
|
+
// timeout), a `use cache` closure capturing a non-serializable function, and awaiting `searchParams`
|
|
6
|
+
// inside a `use cache` scope (E842).
|
|
7
|
+
//
|
|
8
|
+
// The core build wraps each page prerender in try/catch and DEGRADES a failing page to dynamic, so a
|
|
9
|
+
// thrown render error alone does not fail the build, and the caught search-params variant swallows the
|
|
10
|
+
// throw entirely. Both cases are handled by RECORDING the violation here at the moment of detection
|
|
11
|
+
// (inside the cache runtime, during a prerender) and surfacing them from a build-complete hook that
|
|
12
|
+
// prints Next's messages and aborts. Collection is armed only for a compat build's prerender pass, so
|
|
13
|
+
// the serving runtime and pure-core builds are unaffected.
|
|
14
|
+
|
|
15
|
+
export type PrerenderErrorKind =
|
|
16
|
+
| 'hanging'
|
|
17
|
+
| 'close-over'
|
|
18
|
+
| 'search-params'
|
|
19
|
+
| 'use-cache-dynamic'
|
|
20
|
+
| 'unstable-cache-dynamic';
|
|
21
|
+
|
|
22
|
+
export interface PrerenderError {
|
|
23
|
+
kind: PrerenderErrorKind;
|
|
24
|
+
route: string;
|
|
25
|
+
/** The error detail printed to stderr (Next's `Error: …` block). */
|
|
26
|
+
consoleBlock: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The build process (which calls begin/end) and the built compat-server bundle (which runs the
|
|
30
|
+
// prerender and records violations) each load their OWN copy of this module, so a module-local
|
|
31
|
+
// armed/collected set by one copy is invisible to the other and detection would silently never fire.
|
|
32
|
+
// Anchor the state on globalThis so every copy shares one collection window.
|
|
33
|
+
const PRERENDER_ERROR_GLOBALS = Symbol.for('pnext.compat.prerenderErrorGlobals');
|
|
34
|
+
|
|
35
|
+
interface PrerenderErrorState {
|
|
36
|
+
collected: PrerenderError[];
|
|
37
|
+
armed: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function state(): PrerenderErrorState {
|
|
41
|
+
const root = globalThis as Record<PropertyKey, unknown>;
|
|
42
|
+
return (root[PRERENDER_ERROR_GLOBALS] ??= { collected: [], armed: false }) as PrerenderErrorState;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Arm collection at the start of a compat build's prerender pass. */
|
|
46
|
+
export function beginPrerenderErrorCollection(): void {
|
|
47
|
+
const s = state();
|
|
48
|
+
s.collected.length = 0;
|
|
49
|
+
s.armed = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Disarm collection and drain the recorded violations (build-complete hook). */
|
|
53
|
+
export function endPrerenderErrorCollection(): PrerenderError[] {
|
|
54
|
+
const s = state();
|
|
55
|
+
s.armed = false;
|
|
56
|
+
const out = s.collected.slice();
|
|
57
|
+
s.collected.length = 0;
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* True while a compat build prerender is in progress. The cache-runtime
|
|
63
|
+
* detectors gate on this so their throwing/timeout behavior is confined to
|
|
64
|
+
* `pnext build` and never affects `pnext start`/dev or pure-core builds.
|
|
65
|
+
*/
|
|
66
|
+
export function prerenderErrorCollectionActive(): boolean {
|
|
67
|
+
return state().armed;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function recordPrerenderError(error: PrerenderError): void {
|
|
71
|
+
const s = state();
|
|
72
|
+
if (!s.armed) return;
|
|
73
|
+
const collected = s.collected;
|
|
74
|
+
if (collected.some(e => e.kind === error.kind && e.route === error.route)) return;
|
|
75
|
+
collected.push(error);
|
|
76
|
+
// Print at detection time (not only from the build-complete hook): a
|
|
77
|
+
// close-over violation rethrows out of the cache-components shell render and
|
|
78
|
+
// aborts the build BEFORE the complete hook runs, so the hook would never get
|
|
79
|
+
// to print it. Printing here guarantees the message reaches the build output
|
|
80
|
+
// (stderr) in every abort path; the hook only needs to force the non-zero
|
|
81
|
+
// exit for the swallowed cases (hanging / caught search-params).
|
|
82
|
+
console.error(error.consoleBlock);
|
|
83
|
+
if (error.route) {
|
|
84
|
+
console.error(`Error occurred prerendering page "${error.route}".`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Derive the app-router pathname from a `use cache` function id (`<relative-file>#<name>`). Route
|
|
90
|
+
* groups and the trailing `page`/`route` segment are stripped. Returns '' when the id is not a
|
|
91
|
+
* recognizable app-router file.
|
|
92
|
+
*/
|
|
93
|
+
export function routeFromCacheId(id: string): string {
|
|
94
|
+
const file = id.split('#', 1)[0] ?? '';
|
|
95
|
+
const withoutExt = file.replace(/\.[jt]sx?$/, '');
|
|
96
|
+
const segments = withoutExt.split('/');
|
|
97
|
+
if (segments[0] === 'app' || segments[0] === 'src') segments.shift();
|
|
98
|
+
if (segments[0] === 'app') segments.shift();
|
|
99
|
+
const last = segments[segments.length - 1];
|
|
100
|
+
if (last === 'page' || last === 'route' || last === 'default') segments.pop();
|
|
101
|
+
const routeSegments = segments.filter(seg => !/^\(.*\)$/.test(seg));
|
|
102
|
+
return `/${routeSegments.join('/')}`.replace(/\/+$/, '') || '/';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Next's exact error strings -------------------------------------------
|
|
106
|
+
|
|
107
|
+
export const USE_CACHE_HANGING_MESSAGE =
|
|
108
|
+
'Filling a cache during prerender timed out, likely because request-specific ' +
|
|
109
|
+
'arguments such as params, searchParams, cookies() or dynamic data were used ' +
|
|
110
|
+
'inside "use cache".';
|
|
111
|
+
|
|
112
|
+
export const USE_CACHE_CLOSE_OVER_FUNCTION_MESSAGE =
|
|
113
|
+
'Functions cannot be passed directly to Client Components unless you ' +
|
|
114
|
+
'explicitly expose it by marking it with "use server". Or maybe you meant to ' +
|
|
115
|
+
'call this function rather than return it.';
|
|
116
|
+
|
|
117
|
+
/** The `[function]` / caret block Next prints under the close-over error. */
|
|
118
|
+
export const USE_CACHE_CLOSE_OVER_FUNCTION_BLOCK =
|
|
119
|
+
`\nError: ${USE_CACHE_CLOSE_OVER_FUNCTION_MESSAGE}` + '\n [function]' + '\n ^^^^^^^^';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Next's E838 message: a dynamic request API (`cookies()`/`headers()`/
|
|
123
|
+
* `connection()`) invoked inside a function cached with `unstable_cache()`. The
|
|
124
|
+
* e2e build suite substring-matches the first sentence
|
|
125
|
+
* (`Route <route> used \`<api>\` inside a function cached with \`unstable_cache()\`.`);
|
|
126
|
+
* the remainder mirrors Next's full runtime text.
|
|
127
|
+
*/
|
|
128
|
+
export function unstableCacheDynamicApiMessage(
|
|
129
|
+
route: string,
|
|
130
|
+
api: 'cookies()' | 'headers()' | 'connection()',
|
|
131
|
+
): string {
|
|
132
|
+
return (
|
|
133
|
+
`Route ${route} used \`${api}\` inside a function cached with ` +
|
|
134
|
+
'`unstable_cache()`. Accessing Dynamic data sources inside a cache scope is ' +
|
|
135
|
+
`not supported. If you need this data inside a cached function use \`${api}\` ` +
|
|
136
|
+
'outside of the cached function and pass the required dynamic data in as an ' +
|
|
137
|
+
'argument. See more info here: ' +
|
|
138
|
+
'https://nextjs.org/docs/app/api-reference/functions/unstable_cache'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function useCacheDynamicApiMessage(
|
|
143
|
+
route: string,
|
|
144
|
+
api: 'cookies()' | 'headers()' | 'connection()',
|
|
145
|
+
): string {
|
|
146
|
+
return (
|
|
147
|
+
`Route ${route} used \`${api}\` inside "use cache". Accessing Dynamic data ` +
|
|
148
|
+
'sources inside a cache scope is not supported. If you need this data inside ' +
|
|
149
|
+
`a cached function use \`${api}\` outside of the cached function and pass the ` +
|
|
150
|
+
'required dynamic data in as an argument. See more info here: ' +
|
|
151
|
+
'https://nextjs.org/docs/messages/next-request-in-use-cache'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function useCacheSearchParamsMessage(route: string): string {
|
|
156
|
+
return (
|
|
157
|
+
`Route ${route} used \`searchParams\` inside "use cache". Accessing dynamic ` +
|
|
158
|
+
'request data inside a cache scope is not supported. If you need some search ' +
|
|
159
|
+
'params inside a cached function await `searchParams` outside of the cached ' +
|
|
160
|
+
'function and pass only the required search params as arguments to the cached ' +
|
|
161
|
+
'function. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache'
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Custom cache-handler loader (COMPAT).
|
|
3
|
+
//
|
|
4
|
+
// Reads the `cacheHandler` path from next.config, loads it in every supported
|
|
5
|
+
// module shape, instantiates the class once, and installs the instance into the
|
|
6
|
+
// core cache seam (src/cache/handler.ts). Runs from a build/start init hook.
|
|
7
|
+
//
|
|
8
|
+
// The config value is one of:
|
|
9
|
+
// - an absolute filesystem path (`process.cwd() + '/' + env`)
|
|
10
|
+
// - a `require.resolve(...)` path (absolute)
|
|
11
|
+
// - a `file://` URL (`import.meta.resolve('./x.js')`)
|
|
12
|
+
// and the module exports the handler class as `module.exports`, `.default`, or
|
|
13
|
+
// an ESM `export default`. On Bun, `require()` loads ESM synchronously, so a
|
|
14
|
+
// single require covers every shape.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
|
+
import { getNextConfig } from '../next/config-loader';
|
|
22
|
+
import { setCacheHandler, type CacheHandler } from './handler';
|
|
23
|
+
|
|
24
|
+
type CacheHandlerCtor = new (options: { maxMemoryCacheSize?: number }) => CacheHandler;
|
|
25
|
+
|
|
26
|
+
let installedFor: string | undefined;
|
|
27
|
+
let installedRoot: string | undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load + instantiate the configured `cacheHandler` and install it into core.
|
|
31
|
+
* Idempotent per resolved path; no-op when unconfigured. Instantiation logs
|
|
32
|
+
* ("initialized custom cache-handler") are what the suites assert.
|
|
33
|
+
*/
|
|
34
|
+
export function installCustomCacheHandler(root: string): void {
|
|
35
|
+
const configured = getNextConfig().cacheHandler;
|
|
36
|
+
if (typeof configured !== 'string' || configured.length === 0) {
|
|
37
|
+
if (installedFor !== undefined) {
|
|
38
|
+
setCacheHandler(undefined);
|
|
39
|
+
installedFor = undefined;
|
|
40
|
+
installedRoot = undefined;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const resolved = configured.startsWith('file:') ? fileURLToPath(configured) : configured;
|
|
45
|
+
// `import.meta.resolve('./x.js')` in next.config resolves against the bundled
|
|
46
|
+
// config location (`.pnext/config/`), not the project — fall back to the same
|
|
47
|
+
// basename at the project root so the handler still loads.
|
|
48
|
+
const filePath =
|
|
49
|
+
existsSync(resolved) || !path.isAbsolute(resolved)
|
|
50
|
+
? resolved
|
|
51
|
+
: existsSync(path.join(root, path.basename(resolved)))
|
|
52
|
+
? path.join(root, path.basename(resolved))
|
|
53
|
+
: resolved;
|
|
54
|
+
if (installedFor === filePath) return;
|
|
55
|
+
|
|
56
|
+
const Ctor = loadHandlerCtor(filePath, root);
|
|
57
|
+
if (!Ctor) return;
|
|
58
|
+
const instance = new Ctor({});
|
|
59
|
+
setCacheHandler(instance);
|
|
60
|
+
installedFor = filePath;
|
|
61
|
+
installedRoot = root;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function reinstallCustomCacheHandlerForEdge(): void {
|
|
65
|
+
if (!installedRoot) return;
|
|
66
|
+
installedFor = undefined;
|
|
67
|
+
installCustomCacheHandler(installedRoot);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function loadHandlerCtor(filePath: string, root: string): CacheHandlerCtor | undefined {
|
|
71
|
+
try {
|
|
72
|
+
const require = createRequire(pathToFileURL(`${root}/index.js`).href);
|
|
73
|
+
try {
|
|
74
|
+
return pickCtor(require(filePath));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// Handlers commonly extend Next internals (e.g. `require('next/dist/
|
|
77
|
+
// server/lib/incremental-cache/file-system-cache')`) that don't exist
|
|
78
|
+
// without a real `next` install. Re-evaluate the module with those
|
|
79
|
+
// requires stubbed to an inert base class so the subclass (whose own
|
|
80
|
+
// get/set overrides carry the observable behavior) still loads.
|
|
81
|
+
if (!isNextDistMissing(error)) return undefined;
|
|
82
|
+
return loadCommonJsCtorWithNextStubs(filePath, require);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadCommonJsCtorWithNextStubs(
|
|
90
|
+
filePath: string,
|
|
91
|
+
rootRequire: NodeRequire,
|
|
92
|
+
): CacheHandlerCtor | undefined {
|
|
93
|
+
try {
|
|
94
|
+
const module = { exports: {} as unknown };
|
|
95
|
+
const localRequire = (specifier: string): unknown => {
|
|
96
|
+
if (specifier.startsWith('next/dist/')) return nextDistStubModule();
|
|
97
|
+
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
98
|
+
return rootRequire(path.resolve(path.dirname(filePath), specifier));
|
|
99
|
+
}
|
|
100
|
+
return rootRequire(specifier);
|
|
101
|
+
};
|
|
102
|
+
const source = readFileSync(filePath, 'utf8');
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
104
|
+
const evaluate = new Function('require', 'module', 'exports', '__filename', '__dirname', source) as (
|
|
105
|
+
require: (specifier: string) => unknown,
|
|
106
|
+
module: { exports: unknown },
|
|
107
|
+
exports: unknown,
|
|
108
|
+
filename: string,
|
|
109
|
+
dirname: string,
|
|
110
|
+
) => void;
|
|
111
|
+
evaluate(localRequire, module, module.exports, filePath, path.dirname(filePath));
|
|
112
|
+
return pickCtor(module.exports);
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isNextDistMissing(error: unknown): boolean {
|
|
119
|
+
const message = (error as { message?: unknown }).message;
|
|
120
|
+
return typeof message === 'string' && message.includes('next/dist/');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Inert stand-in for a `next/dist/*` module a classic cache handler extends.
|
|
125
|
+
* The base class carries tolerant no-op cache methods so a subclass's
|
|
126
|
+
* `super.get/set(...)` calls work; the Proxy serves it for both
|
|
127
|
+
* `module.exports` and `.default` interop shapes.
|
|
128
|
+
*/
|
|
129
|
+
function nextDistStubModule(): unknown {
|
|
130
|
+
class NextDistStub {
|
|
131
|
+
get(): Promise<null> {
|
|
132
|
+
return Promise.resolve(null);
|
|
133
|
+
}
|
|
134
|
+
set(): Promise<void> {
|
|
135
|
+
return Promise.resolve();
|
|
136
|
+
}
|
|
137
|
+
revalidateTag(): Promise<void> {
|
|
138
|
+
return Promise.resolve();
|
|
139
|
+
}
|
|
140
|
+
resetRequestCache(): null {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return new Proxy(NextDistStub, {
|
|
145
|
+
get: (target, key) =>
|
|
146
|
+
key === 'default'
|
|
147
|
+
? NextDistStub
|
|
148
|
+
: ((target as unknown as Record<string | symbol, unknown>)[key] ?? NextDistStub),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function pickCtor(mod: unknown): CacheHandlerCtor | undefined {
|
|
153
|
+
if (typeof mod === 'function') return mod as CacheHandlerCtor;
|
|
154
|
+
if (mod && typeof mod === 'object') {
|
|
155
|
+
const record = mod as Record<string, unknown>;
|
|
156
|
+
if (typeof record.default === 'function') return record.default as CacheHandlerCtor;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|