@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,108 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { build as esbuild, type Loader, type OnLoadResult, type Plugin } from 'esbuild';
|
|
5
|
+
import type { ResolvedConfig } from '../../config';
|
|
6
|
+
import { applyClientSourceTransforms, getBundlerExtensions } from '../../extensions';
|
|
7
|
+
|
|
8
|
+
const pageClientFacade = 'pnext-page-client.js';
|
|
9
|
+
const sideEffectImportPattern = /(?:^|\n)\s*import\s*(['"])([^'"]+)\1\s*;?/g;
|
|
10
|
+
const styleImportPattern = /\.(?:css|scss|sass|less|styl)(?:\?|$)/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compile generated Pages facades from only the page component export. The relative-import hook is
|
|
14
|
+
* a catch-all that only ever fires from a facade, so an app with no `pages/` dir skips it.
|
|
15
|
+
*/
|
|
16
|
+
export function pagesClientModulePlugin(config: ResolvedConfig): Plugin {
|
|
17
|
+
const compiled = new Map<string, Promise<OnLoadResult>>();
|
|
18
|
+
const hasPages = existsSync(path.join(config.root, 'pages'));
|
|
19
|
+
return {
|
|
20
|
+
name: 'pnext-pages-client-module',
|
|
21
|
+
setup(build) {
|
|
22
|
+
if (!hasPages) return;
|
|
23
|
+
build.onResolve({ filter: /^\.{1,2}\// }, args => {
|
|
24
|
+
if (path.basename(args.importer) !== pageClientFacade) return undefined;
|
|
25
|
+
return {
|
|
26
|
+
path: path.resolve(args.resolveDir, args.path),
|
|
27
|
+
namespace: 'pnext-pages-client-module',
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
build.onLoad({ filter: /.*/, namespace: 'pnext-pages-client-module' }, args => {
|
|
31
|
+
let output = compiled.get(args.path);
|
|
32
|
+
if (!output) {
|
|
33
|
+
output = compilePageClientModule(config, args.path);
|
|
34
|
+
compiled.set(args.path, output);
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function compilePageClientModule(
|
|
43
|
+
config: ResolvedConfig,
|
|
44
|
+
file: string,
|
|
45
|
+
): Promise<OnLoadResult> {
|
|
46
|
+
const rawSource = await readFile(file, 'utf8');
|
|
47
|
+
const source = applyClientSourceTransforms(rawSource, file, config.root);
|
|
48
|
+
const sideEffectImports = new Set(
|
|
49
|
+
[...source.matchAll(sideEffectImportPattern)]
|
|
50
|
+
.map(match => match[2])
|
|
51
|
+
.filter((specifier): specifier is string => Boolean(specifier)),
|
|
52
|
+
);
|
|
53
|
+
const result = await esbuild({
|
|
54
|
+
stdin: {
|
|
55
|
+
contents: `export { default } from ${JSON.stringify(file)};`,
|
|
56
|
+
loader: 'js',
|
|
57
|
+
resolveDir: path.dirname(file),
|
|
58
|
+
sourcefile: `${file}.pnext-client-entry.js`,
|
|
59
|
+
},
|
|
60
|
+
bundle: true,
|
|
61
|
+
write: false,
|
|
62
|
+
format: 'esm',
|
|
63
|
+
platform: 'browser',
|
|
64
|
+
target: 'es2022',
|
|
65
|
+
treeShaking: true,
|
|
66
|
+
// This facade build is its own esbuild pass, so it needs compiler.define
|
|
67
|
+
// (the main client build applies it through baseClientBuildOptions).
|
|
68
|
+
define: getBundlerExtensions().clientDefines(),
|
|
69
|
+
jsx: 'automatic',
|
|
70
|
+
jsxImportSource: 'preact',
|
|
71
|
+
logLevel: 'silent',
|
|
72
|
+
plugins: [
|
|
73
|
+
{
|
|
74
|
+
name: 'pnext-pages-client-module-source',
|
|
75
|
+
setup(build) {
|
|
76
|
+
build.onResolve({ filter: /.*/ }, args => {
|
|
77
|
+
if (args.path === file) {
|
|
78
|
+
return { path: file, namespace: 'pnext-pages-client-source' };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
path: args.path,
|
|
82
|
+
external: true,
|
|
83
|
+
sideEffects: sideEffectImports.has(args.path) || styleImportPattern.test(args.path),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
build.onLoad({ filter: /.*/, namespace: 'pnext-pages-client-source' }, () => ({
|
|
87
|
+
contents: source,
|
|
88
|
+
loader: pageLoader(file),
|
|
89
|
+
resolveDir: path.dirname(file),
|
|
90
|
+
}));
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
const output = result.outputFiles[0];
|
|
96
|
+
if (!output) throw new Error(`Failed to compile Pages client module ${file}`);
|
|
97
|
+
return {
|
|
98
|
+
contents: output.text,
|
|
99
|
+
loader: 'js',
|
|
100
|
+
resolveDir: path.dirname(file),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function pageLoader(file: string): Loader {
|
|
105
|
+
if (file.endsWith('.tsx')) return 'tsx';
|
|
106
|
+
if (file.endsWith('.ts')) return 'ts';
|
|
107
|
+
return 'jsx';
|
|
108
|
+
}
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { copyFile, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { hasUseClientDirective } from '../../client/reference-stub';
|
|
7
|
+
import { listFiles, toPosixPath } from '../../utils/fs';
|
|
8
|
+
import { getNextConfig } from '../next/config-loader';
|
|
9
|
+
|
|
10
|
+
const basePageExtensions = ['tsx', 'ts', 'jsx', 'js', 'mjs'];
|
|
11
|
+
|
|
12
|
+
// The app's configured pageExtensions (e.g. @next/mdx appending mdx/md) must
|
|
13
|
+
// apply to materialization too, or hybrid apps lose their .mdx routes — this
|
|
14
|
+
// runs during config resolution, before registerMdxExtensions.
|
|
15
|
+
function materializeExtensions(): string[] {
|
|
16
|
+
const configured = getNextConfig().pageExtensions;
|
|
17
|
+
const extras = Array.isArray(configured)
|
|
18
|
+
? configured.filter((ext): ext is string => typeof ext === 'string' && ext.length > 0)
|
|
19
|
+
: [];
|
|
20
|
+
return [...new Set([...basePageExtensions, ...extras])];
|
|
21
|
+
}
|
|
22
|
+
const appConventions = new Set([
|
|
23
|
+
'default',
|
|
24
|
+
'error',
|
|
25
|
+
'forbidden',
|
|
26
|
+
'global-error',
|
|
27
|
+
'layout',
|
|
28
|
+
'loading',
|
|
29
|
+
'not-found',
|
|
30
|
+
'page',
|
|
31
|
+
'route',
|
|
32
|
+
'template',
|
|
33
|
+
'unauthorized',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// Metadata code routes (manifest.js, opengraph-image.tsx, icon2.tsx, ...) are
|
|
37
|
+
// app conventions too and must survive materialization in hybrid apps.
|
|
38
|
+
const metadataConventionPattern =
|
|
39
|
+
/^(robots|sitemap|manifest|icon\d*|apple-icon\d*|opengraph-image\d*|twitter-image\d*)$/;
|
|
40
|
+
|
|
41
|
+
function isAppConvention(name: string): boolean {
|
|
42
|
+
return appConventions.has(name) || metadataConventionPattern.test(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build a compat-owned App Router view for basic Pages files. Keeping this
|
|
47
|
+
* outside the source app preserves native App routes and avoids hybrid-route
|
|
48
|
+
* conflict validation; linked sources and node_modules keep normal resolution.
|
|
49
|
+
*/
|
|
50
|
+
export async function materializePagesApp(root: string): Promise<string | undefined> {
|
|
51
|
+
const pagesPath = path.join(root, 'pages');
|
|
52
|
+
if (!existsSync(pagesPath)) return undefined;
|
|
53
|
+
|
|
54
|
+
const generatedRoot = path.join(
|
|
55
|
+
tmpdir(),
|
|
56
|
+
'pnext-pages-compat',
|
|
57
|
+
createHash('sha256').update(root).digest('hex').slice(0, 16),
|
|
58
|
+
);
|
|
59
|
+
const appPath = path.join(generatedRoot, 'app');
|
|
60
|
+
const sourcePages = path.join(generatedRoot, 'source-pages');
|
|
61
|
+
const sourceApp = path.join(generatedRoot, 'source-app');
|
|
62
|
+
const pages = await discoverPages(pagesPath);
|
|
63
|
+
const apiRoutes = await discoverPagesApi(pagesPath);
|
|
64
|
+
if (pages.length === 0 && apiRoutes.length === 0) return undefined;
|
|
65
|
+
|
|
66
|
+
await rm(generatedRoot, { recursive: true, force: true });
|
|
67
|
+
await mkdir(appPath, { recursive: true });
|
|
68
|
+
await symlink(pagesPath, sourcePages, 'dir');
|
|
69
|
+
const nodeModules = path.join(root, 'node_modules');
|
|
70
|
+
if (existsSync(nodeModules))
|
|
71
|
+
await symlink(nodeModules, path.join(generatedRoot, 'node_modules'), 'dir');
|
|
72
|
+
|
|
73
|
+
// Static assets referenced from materialized routes (e.g. an app route's
|
|
74
|
+
// `new URL('../../public/vercel.png', import.meta.url)` or a `public/*` asset
|
|
75
|
+
// import) resolve relative to the generated root; without the symlink the
|
|
76
|
+
// bundler can't find them and the route 500s.
|
|
77
|
+
const publicDir = path.join(root, 'public');
|
|
78
|
+
if (existsSync(publicDir))
|
|
79
|
+
await symlink(publicDir, path.join(generatedRoot, 'public'), 'dir');
|
|
80
|
+
|
|
81
|
+
const nativeApp = path.join(root, 'app');
|
|
82
|
+
if (existsSync(nativeApp)) {
|
|
83
|
+
await symlink(nativeApp, sourceApp, 'dir');
|
|
84
|
+
await materializeNativeApp(sourceApp, appPath);
|
|
85
|
+
}
|
|
86
|
+
if (!existsSync(path.join(appPath, 'layout.js'))) {
|
|
87
|
+
await writeFile(
|
|
88
|
+
path.join(appPath, 'layout.js'),
|
|
89
|
+
'export default function PagesLayout({ children }) {\n return <html><body>{children}</body></html>\n}\n',
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const page of pages) {
|
|
94
|
+
const destination = path.join(appPath, ...encodePrivateSegments(page.segments), 'page.js');
|
|
95
|
+
if (existsSync(destination)) continue;
|
|
96
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
97
|
+
const pagesPattern = `/${page.segments.join('/')}`.replace(/\/+$/, '') || '/';
|
|
98
|
+
const sourceSpecifier = relativeImport(destination, path.join(sourcePages, page.relative));
|
|
99
|
+
let clientSpecifier: string | undefined;
|
|
100
|
+
if (page.hasStaticProps || page.hasServerSideProps) {
|
|
101
|
+
const clientDestination = path.join(path.dirname(destination), 'pnext-page-client.js');
|
|
102
|
+
await writeFile(
|
|
103
|
+
clientDestination,
|
|
104
|
+
`'use client';\nexport { default } from ${JSON.stringify(sourceSpecifier)};\n`,
|
|
105
|
+
);
|
|
106
|
+
clientSpecifier = './pnext-page-client.js';
|
|
107
|
+
}
|
|
108
|
+
await writeFile(
|
|
109
|
+
destination,
|
|
110
|
+
wrapperSource(
|
|
111
|
+
sourceSpecifier,
|
|
112
|
+
// A pages-router component is implicitly a client module: it SSRs AND
|
|
113
|
+
// hydrates (hooks, event handlers, next/router). Without the client
|
|
114
|
+
// wrapper it renders as a server component and never becomes
|
|
115
|
+
// interactive. Data functions keep a server wrapper and pass their
|
|
116
|
+
// props into the page's client facade.
|
|
117
|
+
!page.hasServerSideProps && !page.hasStaticProps,
|
|
118
|
+
page.hasServerSideProps,
|
|
119
|
+
page.hasStaticProps,
|
|
120
|
+
clientSpecifier,
|
|
121
|
+
page.runtime,
|
|
122
|
+
page.maxDuration,
|
|
123
|
+
pagesPattern,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
for (const apiRoute of apiRoutes) {
|
|
128
|
+
const destination = path.join(appPath, 'api', ...apiRoute.segments, 'route.js');
|
|
129
|
+
if (existsSync(destination)) continue;
|
|
130
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
131
|
+
await writeFile(
|
|
132
|
+
destination,
|
|
133
|
+
pagesApiWrapperSource(
|
|
134
|
+
relativeImport(destination, path.join(sourcePages, apiRoute.relative)),
|
|
135
|
+
apiRoute.runtime,
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return appPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function materializeNativeApp(sourceApp: string, appPath: string): Promise<void> {
|
|
144
|
+
for (const file of await listFiles(sourceApp)) {
|
|
145
|
+
const relative = toPosixPath(path.relative(sourceApp, file));
|
|
146
|
+
const parsed = path.parse(relative);
|
|
147
|
+
// Non-code files (favicon.ico, robots.txt, sitemap.xml, og images, ...)
|
|
148
|
+
// are static metadata routes discovered by scanning the app dir — carry
|
|
149
|
+
// them into the merged view or they 404 in hybrid apps. Copied, not
|
|
150
|
+
// symlinked: the file listers skip symlink dirents.
|
|
151
|
+
if (!materializeExtensions().includes(parsed.ext.slice(1))) {
|
|
152
|
+
const destination = path.join(appPath, relative);
|
|
153
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
154
|
+
await copyFile(file, destination);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!isAppConvention(parsed.name)) continue;
|
|
158
|
+
const destination = path.join(appPath, parsed.dir, `${parsed.name}.js`);
|
|
159
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
160
|
+
const source = await readFile(file, 'utf8');
|
|
161
|
+
const client =
|
|
162
|
+
hasUseClientDirective(source) || (await defaultExportImportsClientModule(file, source));
|
|
163
|
+
await writeFile(
|
|
164
|
+
destination,
|
|
165
|
+
wrapperSource(
|
|
166
|
+
relativeImport(destination, file),
|
|
167
|
+
client,
|
|
168
|
+
false,
|
|
169
|
+
false,
|
|
170
|
+
undefined,
|
|
171
|
+
runtimeFromSource(source),
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function defaultExportImportsClientModule(file: string, source: string): Promise<boolean> {
|
|
178
|
+
const defaultExport = /export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/.exec(source);
|
|
179
|
+
if (!defaultExport?.[1]) return false;
|
|
180
|
+
|
|
181
|
+
const binding = defaultExport[1];
|
|
182
|
+
const imports = /import\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
183
|
+
let match: RegExpExecArray | null;
|
|
184
|
+
while ((match = imports.exec(source))) {
|
|
185
|
+
const importsBinding = (match[1] ?? '').split(',').some(value => {
|
|
186
|
+
const names = /^\s*([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*$/.exec(value);
|
|
187
|
+
return (names?.[2] ?? names?.[1]) === binding;
|
|
188
|
+
});
|
|
189
|
+
if (!importsBinding || !match[2]?.startsWith('.')) continue;
|
|
190
|
+
const imported = resolveSourceImport(file, match[2]);
|
|
191
|
+
if (imported && hasUseClientDirective(await readFile(imported, 'utf8'))) return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function resolveSourceImport(file: string, specifier: string): string | undefined {
|
|
197
|
+
const base = path.resolve(path.dirname(file), specifier);
|
|
198
|
+
for (const candidate of [
|
|
199
|
+
base,
|
|
200
|
+
...materializeExtensions().flatMap(extension => [
|
|
201
|
+
`${base}.${extension}`,
|
|
202
|
+
path.join(base, `index.${extension}`),
|
|
203
|
+
]),
|
|
204
|
+
]) {
|
|
205
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface PagesRoute {
|
|
211
|
+
relative: string;
|
|
212
|
+
segments: string[];
|
|
213
|
+
hasServerSideProps: boolean;
|
|
214
|
+
hasStaticProps: boolean;
|
|
215
|
+
runtime?: 'edge' | 'nodejs';
|
|
216
|
+
maxDuration?: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
interface PagesApiRoute {
|
|
220
|
+
relative: string;
|
|
221
|
+
segments: string[];
|
|
222
|
+
runtime?: 'edge' | 'nodejs';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// A pages-router segment beginning with `_` is a real route (pages has no private-folder convention),
|
|
226
|
+
// but the app-router scanner that consumes the materialized app would drop a literal `_segment` as
|
|
227
|
+
// private. Encoding the leading underscore to `%5F` keeps the materialized directory routable - the
|
|
228
|
+
// scanner decodes it back.
|
|
229
|
+
function encodePrivateSegments(segments: string[]): string[] {
|
|
230
|
+
return segments.map(segment =>
|
|
231
|
+
segment.startsWith('_') ? `%5F${segment.slice(1)}` : segment,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function discoverPages(pagesPath: string): Promise<PagesRoute[]> {
|
|
236
|
+
const routes = new Map<string, PagesRoute>();
|
|
237
|
+
const files = await listFiles(pagesPath);
|
|
238
|
+
for (const extension of materializeExtensions()) {
|
|
239
|
+
for (const file of files) {
|
|
240
|
+
if (!file.endsWith(`.${extension}`)) continue;
|
|
241
|
+
const relative = toPosixPath(path.relative(pagesPath, file));
|
|
242
|
+
const withoutExtension = relative.slice(0, -extension.length - 1);
|
|
243
|
+
const sourceSegments = withoutExtension.split('/');
|
|
244
|
+
// The pages router has no private-folder convention (that is app-router
|
|
245
|
+
// only): a nested `_dashboard/index.tsx` routes at `/_dashboard`. Only the
|
|
246
|
+
// reserved root components (`_app`/`_document`/`_error`) and the `api`
|
|
247
|
+
// segment are excluded from route materialization.
|
|
248
|
+
if (sourceSegments[0] === 'api') continue;
|
|
249
|
+
if (
|
|
250
|
+
sourceSegments.length === 1 &&
|
|
251
|
+
(sourceSegments[0] === '_app' ||
|
|
252
|
+
sourceSegments[0] === '_document' ||
|
|
253
|
+
sourceSegments[0] === '_error')
|
|
254
|
+
) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const segments =
|
|
258
|
+
sourceSegments.at(-1) === 'index' ? sourceSegments.slice(0, -1) : sourceSegments;
|
|
259
|
+
const key = segments.join('/');
|
|
260
|
+
if (!routes.has(key)) {
|
|
261
|
+
const source = await readFile(file, 'utf8');
|
|
262
|
+
routes.set(key, {
|
|
263
|
+
relative,
|
|
264
|
+
segments,
|
|
265
|
+
hasServerSideProps: /\bgetServerSideProps\b/.test(source),
|
|
266
|
+
hasStaticProps: /\bgetStaticProps\b/.test(source),
|
|
267
|
+
runtime: runtimeFromSource(source),
|
|
268
|
+
maxDuration: maxDurationFromSource(source),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return [...routes.values()];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function discoverPagesApi(pagesPath: string): Promise<PagesApiRoute[]> {
|
|
277
|
+
const routes = new Map<string, PagesApiRoute>();
|
|
278
|
+
const files = await listFiles(path.join(pagesPath, 'api'));
|
|
279
|
+
for (const extension of materializeExtensions()) {
|
|
280
|
+
for (const file of files) {
|
|
281
|
+
if (!file.endsWith(`.${extension}`)) continue;
|
|
282
|
+
const relative = toPosixPath(path.relative(pagesPath, file));
|
|
283
|
+
const segments = relative
|
|
284
|
+
.slice(0, -extension.length - 1)
|
|
285
|
+
.split('/')
|
|
286
|
+
.slice(1);
|
|
287
|
+
if (segments.some(segment => segment.startsWith('_'))) continue;
|
|
288
|
+
const key = segments.join('/');
|
|
289
|
+
if (routes.has(key)) continue;
|
|
290
|
+
routes.set(key, {
|
|
291
|
+
relative,
|
|
292
|
+
segments,
|
|
293
|
+
runtime: runtimeFromSource(await readFile(file, 'utf8')),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return [...routes.values()];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The compat runtime module the data wrappers import (by absolute path — the
|
|
301
|
+
// materialized app lives outside the source tree, so bare/relative specifiers
|
|
302
|
+
// cannot reach it). It computes + stashes the SSR useRouter() state and
|
|
303
|
+
// serializes it for the inline __PNEXT_PAGES_DATA__ JSON node.
|
|
304
|
+
function routerStateModulePath(): string {
|
|
305
|
+
return path.join(import.meta.dirname, 'router-state.ts');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// The shared prologue/epilogue for data wrappers: emit the pages-router state
|
|
309
|
+
// node alongside the page so the next/router shim (SSR and browser) sees the
|
|
310
|
+
// pages-route pattern, canonical asPath, and merged query.
|
|
311
|
+
function routerStateSnippet(pagesPattern: string | undefined): {
|
|
312
|
+
imports: string;
|
|
313
|
+
render: string;
|
|
314
|
+
} {
|
|
315
|
+
if (!pagesPattern) {
|
|
316
|
+
return { imports: '', render: 'return createElement(Page, result?.props ?? {});' };
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
imports: `import { pagesRouterSsrState, serializePagesRouterState } from ${JSON.stringify(
|
|
320
|
+
routerStateModulePath(),
|
|
321
|
+
)};\n`,
|
|
322
|
+
render: `const routerState = pagesRouterSsrState(${JSON.stringify(pagesPattern)}, resolvedParams);
|
|
323
|
+
return createElement(
|
|
324
|
+
Fragment,
|
|
325
|
+
null,
|
|
326
|
+
createElement('script', {
|
|
327
|
+
type: 'application/json',
|
|
328
|
+
id: '__PNEXT_PAGES_DATA__',
|
|
329
|
+
dangerouslySetInnerHTML: { __html: serializePagesRouterState(routerState) },
|
|
330
|
+
}),
|
|
331
|
+
createElement(Page, result?.props ?? {}),
|
|
332
|
+
);`,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function wrapperSource(
|
|
337
|
+
specifier: string,
|
|
338
|
+
client = false,
|
|
339
|
+
hasServerSideProps = false,
|
|
340
|
+
hasStaticProps = false,
|
|
341
|
+
clientSpecifier?: string,
|
|
342
|
+
runtime?: 'edge' | 'nodejs',
|
|
343
|
+
maxDuration?: number,
|
|
344
|
+
pagesPattern?: string,
|
|
345
|
+
): string {
|
|
346
|
+
const runtimeExport = runtime ? `export const runtime = ${JSON.stringify(runtime)};\n` : '';
|
|
347
|
+
const maxDurationExport =
|
|
348
|
+
maxDuration === undefined ? '' : `export const maxDuration = ${maxDuration};\n`;
|
|
349
|
+
const routerState = routerStateSnippet(pagesPattern);
|
|
350
|
+
if (hasStaticProps && clientSpecifier) {
|
|
351
|
+
return `import { createElement, Fragment } from 'react';
|
|
352
|
+
import { notFound, redirect } from 'next/navigation';
|
|
353
|
+
import Page from ${JSON.stringify(clientSpecifier)};
|
|
354
|
+
import * as pageModule from ${JSON.stringify(specifier)};
|
|
355
|
+
${routerState.imports}export * from ${JSON.stringify(specifier)};
|
|
356
|
+
${runtimeExport}${maxDurationExport}export default async function PagesCompatStaticPage({ params }) {
|
|
357
|
+
// The otel compat exposes a span wrapper (Render.getStaticProps) at runtime.
|
|
358
|
+
const withSpan = globalThis.__PNEXT_PAGES_DATA_SPAN__ ?? ((kind, fn) => fn());
|
|
359
|
+
const resolvedParams = await params;
|
|
360
|
+
const invoke = async () => pageModule.getStaticProps?.({ params: resolvedParams });
|
|
361
|
+
const result = await withSpan('getStaticProps', invoke);
|
|
362
|
+
if (result?.notFound) notFound();
|
|
363
|
+
if (result?.redirect) redirect(result.redirect.destination);
|
|
364
|
+
${routerState.render}
|
|
365
|
+
}
|
|
366
|
+
`;
|
|
367
|
+
}
|
|
368
|
+
if (!hasServerSideProps) {
|
|
369
|
+
if (client) {
|
|
370
|
+
return `'use client';
|
|
371
|
+
export { default } from ${JSON.stringify(specifier)};
|
|
372
|
+
${runtimeExport}${maxDurationExport}`;
|
|
373
|
+
}
|
|
374
|
+
return `import * as __pnext_page_ns from ${JSON.stringify(specifier)};
|
|
375
|
+
export default __pnext_page_ns.default;
|
|
376
|
+
export * from ${JSON.stringify(specifier)};
|
|
377
|
+
${runtimeExport}${maxDurationExport}`;
|
|
378
|
+
}
|
|
379
|
+
return `import { createElement, Fragment } from 'react';
|
|
380
|
+
import { notFound, redirect } from 'next/navigation';
|
|
381
|
+
import { cookies, headers } from 'next/headers';
|
|
382
|
+
import Page from ${JSON.stringify(clientSpecifier)};
|
|
383
|
+
import * as pageModule from ${JSON.stringify(specifier)};
|
|
384
|
+
${routerState.imports}export * from ${JSON.stringify(specifier)};
|
|
385
|
+
export const dynamic = 'force-dynamic';
|
|
386
|
+
${runtimeExport}${maxDurationExport}export default async function PagesCompatPage({ params, searchParams }) {
|
|
387
|
+
// The otel compat exposes a span wrapper (Render.getServerSideProps) at runtime.
|
|
388
|
+
const withSpan = globalThis.__PNEXT_PAGES_DATA_SPAN__ ?? ((kind, fn) => fn());
|
|
389
|
+
const resolvedParams = await params;
|
|
390
|
+
// getServerSideProps reads a node-shaped \`req\`/\`res\`. This page is
|
|
391
|
+
// force-dynamic, so headers()/cookies() are available; \`method\` is always
|
|
392
|
+
// 'GET' and \`res\` is inert (writes are dropped) — a workable baseline until
|
|
393
|
+
// the pages layer threads the real request through.
|
|
394
|
+
const requestHeaders = Object.fromEntries((await headers()).entries());
|
|
395
|
+
const requestCookies = Object.fromEntries(
|
|
396
|
+
(await cookies()).getAll().map(cookie => [cookie.name, cookie.value]),
|
|
397
|
+
);
|
|
398
|
+
const invoke = async () => pageModule.getServerSideProps?.({
|
|
399
|
+
params: resolvedParams,
|
|
400
|
+
query: (await searchParams) ?? {},
|
|
401
|
+
req: {
|
|
402
|
+
headers: requestHeaders,
|
|
403
|
+
cookies: requestCookies,
|
|
404
|
+
method: 'GET',
|
|
405
|
+
url: ${JSON.stringify(pagesPattern ?? '/')},
|
|
406
|
+
},
|
|
407
|
+
res: {
|
|
408
|
+
statusCode: 200,
|
|
409
|
+
setHeader() {},
|
|
410
|
+
getHeader() {},
|
|
411
|
+
getHeaders() { return {}; },
|
|
412
|
+
removeHeader() {},
|
|
413
|
+
end() {},
|
|
414
|
+
write() { return true; },
|
|
415
|
+
},
|
|
416
|
+
resolvedUrl: ${JSON.stringify(pagesPattern ?? '/')},
|
|
417
|
+
preview: false,
|
|
418
|
+
previewData: undefined,
|
|
419
|
+
draftMode: false,
|
|
420
|
+
});
|
|
421
|
+
const result = await withSpan('getServerSideProps', invoke);
|
|
422
|
+
if (result?.notFound) notFound();
|
|
423
|
+
if (result?.redirect) redirect(result.redirect.destination);
|
|
424
|
+
${routerState.render}
|
|
425
|
+
}
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function runtimeFromSource(source: string): 'edge' | 'nodejs' | undefined {
|
|
430
|
+
const runtime = /\bruntime\b\s*[:=]\s*(['"`])(edge|nodejs|experimental-edge)\1/.exec(
|
|
431
|
+
source,
|
|
432
|
+
)?.[2];
|
|
433
|
+
return runtime === 'experimental-edge' ? 'edge' : (runtime as 'edge' | 'nodejs' | undefined);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function maxDurationFromSource(source: string): number | undefined {
|
|
437
|
+
const value = /\bmaxDuration\s*:\s*(\d+)\b/.exec(source)?.[1];
|
|
438
|
+
return value === undefined ? undefined : Number(value);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function pagesApiWrapperSource(specifier: string, runtime?: 'edge' | 'nodejs'): string {
|
|
442
|
+
const runtimeExport = runtime ? `export const runtime = ${JSON.stringify(runtime)};\n` : '';
|
|
443
|
+
return `import handler from ${JSON.stringify(specifier)};
|
|
444
|
+
import { NextRequest } from 'next/server';
|
|
445
|
+
import { Writable } from 'node:stream';
|
|
446
|
+
${runtimeExport}export async function GET(request) {
|
|
447
|
+
return run(handler, request);
|
|
448
|
+
}
|
|
449
|
+
export async function POST(request) {
|
|
450
|
+
return run(handler, request);
|
|
451
|
+
}
|
|
452
|
+
export async function PUT(request) {
|
|
453
|
+
return run(handler, request);
|
|
454
|
+
}
|
|
455
|
+
export async function PATCH(request) {
|
|
456
|
+
return run(handler, request);
|
|
457
|
+
}
|
|
458
|
+
export async function DELETE(request) {
|
|
459
|
+
return run(handler, request);
|
|
460
|
+
}
|
|
461
|
+
// Bridge a pages-API handler onto the app-route Response contract. Edge-style
|
|
462
|
+
// handlers (request-only, return a Response) pass through; node-style handlers
|
|
463
|
+
// get a real Writable \`res\` whose bytes stream into the Response body, with
|
|
464
|
+
// client disconnects surfaced as res 'close'/destroy (stream cancellation).
|
|
465
|
+
async function run(handler, request) {
|
|
466
|
+
const nextRequest = request instanceof NextRequest ? request : new NextRequest(request);
|
|
467
|
+
const state = { status: 200, headers: new Headers() };
|
|
468
|
+
let controller;
|
|
469
|
+
let started = false;
|
|
470
|
+
let notifyStarted = () => {};
|
|
471
|
+
const startedPromise = new Promise(resolve => { notifyStarted = resolve; });
|
|
472
|
+
const start = () => {
|
|
473
|
+
if (!started) {
|
|
474
|
+
started = true;
|
|
475
|
+
notifyStarted();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const body = new ReadableStream({
|
|
479
|
+
start(c) { controller = c; },
|
|
480
|
+
cancel() { res.destroy(); },
|
|
481
|
+
});
|
|
482
|
+
const enqueue = chunk => {
|
|
483
|
+
try {
|
|
484
|
+
controller.enqueue(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : new Uint8Array(chunk));
|
|
485
|
+
} catch {}
|
|
486
|
+
};
|
|
487
|
+
const res = new Writable({
|
|
488
|
+
write(chunk, _encoding, callback) { start(); enqueue(chunk); callback(); },
|
|
489
|
+
final(callback) { start(); try { controller.close(); } catch {} callback(); },
|
|
490
|
+
destroy(error, callback) { try { controller.close(); } catch {} callback(error); },
|
|
491
|
+
});
|
|
492
|
+
Object.defineProperty(res, 'statusCode', {
|
|
493
|
+
get: () => state.status,
|
|
494
|
+
set: value => { state.status = value; },
|
|
495
|
+
});
|
|
496
|
+
res.setHeader = (name, value) => { state.headers.set(name, String(value)); return res; };
|
|
497
|
+
res.getHeader = name => state.headers.get(name) ?? undefined;
|
|
498
|
+
res.removeHeader = name => { state.headers.delete(name); };
|
|
499
|
+
res.status = code => { state.status = code; return res; };
|
|
500
|
+
res.json = value => { state.headers.set('content-type', 'application/json'); res.end(JSON.stringify(value)); };
|
|
501
|
+
res.send = value => { res.end(value); };
|
|
502
|
+
// A client disconnect aborts the request signal; pages handlers observe it
|
|
503
|
+
// as the response closing (res.on('close') per Node semantics).
|
|
504
|
+
request.signal?.addEventListener?.('abort', () => res.destroy(), { once: true });
|
|
505
|
+
const finished = Promise.resolve()
|
|
506
|
+
.then(() => handler(nextRequest, res))
|
|
507
|
+
.catch(error => {
|
|
508
|
+
if (!started) throw error;
|
|
509
|
+
try { controller.error(error); } catch {}
|
|
510
|
+
});
|
|
511
|
+
const outcome = await Promise.race([
|
|
512
|
+
startedPromise.then(() => 'started'),
|
|
513
|
+
finished.then(() => 'done'),
|
|
514
|
+
]);
|
|
515
|
+
if (outcome === 'done' && !started) {
|
|
516
|
+
const result = await finished;
|
|
517
|
+
return result instanceof Response ? result : new Response(null, { status: state.status, headers: state.headers });
|
|
518
|
+
}
|
|
519
|
+
return new Response(body, { status: state.status, headers: state.headers });
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function relativeImport(fromFile: string, targetFile: string): string {
|
|
525
|
+
const relative = toPosixPath(path.relative(path.dirname(fromFile), targetFile));
|
|
526
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
527
|
+
}
|