@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,112 @@
|
|
|
1
|
+
// Client half of `output: 'export'`.
|
|
2
|
+
//
|
|
3
|
+
// An exported app is a directory of files behind whatever static host the user points at it, so the
|
|
4
|
+
// router has no server to negotiate with: no RSC content type, no segment-prefetch header, no
|
|
5
|
+
// redirect/rewrite map. The exporter compensates by writing every document twice - once as the page
|
|
6
|
+
// itself and once as flat sibling artifacts - and stamps each exported document with
|
|
7
|
+
// `window.__PNEXT_OUTPUT_EXPORT__`. This module turns that flag into the router's document fetcher,
|
|
8
|
+
// reproducing the URL shapes Next uses so the same static hosts (and proxy rewrites) keep working:
|
|
9
|
+
//
|
|
10
|
+
// navigation /another -> /another.txt
|
|
11
|
+
// /another/ -> /another/index.txt
|
|
12
|
+
// prefetch /another -> /another/__next._full.txt
|
|
13
|
+
//
|
|
14
|
+
// The prefetch shape puts the request one path segment below the route, which is what lets a host-level
|
|
15
|
+
// rewrite of the page also cover the data request. A host-level REDIRECT typically does not, so - like
|
|
16
|
+
// Next - a HEAD probe resolves the page's final URL first.
|
|
17
|
+
|
|
18
|
+
import { setExportDocumentFetcher } from '../../api/router/policies';
|
|
19
|
+
|
|
20
|
+
/** The exported segment file carrying a route's complete document. */
|
|
21
|
+
const SEGMENT_FILE = '__next._full.txt';
|
|
22
|
+
|
|
23
|
+
/** A document read out of the export tree, with the route URL it belongs to. */
|
|
24
|
+
interface ExportedDocument {
|
|
25
|
+
html: string;
|
|
26
|
+
finalUrl: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Registered at MODULE scope rather than from installNavCompat(): the router
|
|
30
|
+
// issues the document's first prefetches before the compat entry finishes its
|
|
31
|
+
// install sequence, and those would otherwise fall through to a server that
|
|
32
|
+
// isn't there. Module initialization runs before any of it, and the marker is
|
|
33
|
+
// an inline script in <head>, so it is already set by the time this evaluates.
|
|
34
|
+
if (outputExportMode()) setExportDocumentFetcher(fetchExportedDocument);
|
|
35
|
+
|
|
36
|
+
/** Whether the running document was served out of an export tree. */
|
|
37
|
+
function outputExportMode(): boolean {
|
|
38
|
+
return (
|
|
39
|
+
(process.browser || typeof window !== 'undefined') &&
|
|
40
|
+
(window as { __PNEXT_OUTPUT_EXPORT__?: boolean }).__PNEXT_OUTPUT_EXPORT__ === true
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `/another` -> `/another.txt`; `/another/` -> `/another/index.txt`. */
|
|
45
|
+
function documentUrl(url: URL): URL {
|
|
46
|
+
const target = new URL(url.href);
|
|
47
|
+
target.pathname += target.pathname.endsWith('/') ? 'index.txt' : '.txt';
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `/another` (or `/another/`) -> `/another/__next._full.txt`. */
|
|
52
|
+
function segmentUrl(url: URL): URL {
|
|
53
|
+
const target = new URL(url.href);
|
|
54
|
+
const directory = target.pathname.endsWith('/')
|
|
55
|
+
? target.pathname.slice(0, -1)
|
|
56
|
+
: target.pathname;
|
|
57
|
+
target.pathname = `${directory}/${SEGMENT_FILE}`;
|
|
58
|
+
return target;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fetch a route's document from the export tree. Returns null when the host rejects the route or the
|
|
63
|
+
* artifact is missing - the router then falls back to a hard navigation, which is the correct outcome
|
|
64
|
+
* for a path the exporter never wrote.
|
|
65
|
+
*/
|
|
66
|
+
async function fetchExportedDocument(
|
|
67
|
+
href: string,
|
|
68
|
+
init: RequestInit,
|
|
69
|
+
prefetch: boolean,
|
|
70
|
+
): Promise<ExportedDocument | null> {
|
|
71
|
+
const url = new URL(href, location.href);
|
|
72
|
+
let pageUrl = url;
|
|
73
|
+
if (prefetch) {
|
|
74
|
+
// Resolve host-level redirects on the PAGE path before asking for a file
|
|
75
|
+
// beside it: a host that redirects `/old` to `/new` has no reason to also
|
|
76
|
+
// redirect `/old/__next._full.txt`, which the exporter never wrote.
|
|
77
|
+
const probe = await fetch(url.href, { method: 'HEAD', credentials: 'same-origin' }).catch(
|
|
78
|
+
() => null,
|
|
79
|
+
);
|
|
80
|
+
// `Response#ok` is false for a followed 3xx chain, so test the range.
|
|
81
|
+
if (!probe || probe.status < 200 || probe.status >= 400) return null;
|
|
82
|
+
if (probe.redirected) pageUrl = new URL(probe.url);
|
|
83
|
+
}
|
|
84
|
+
const target = prefetch ? segmentUrl(pageUrl) : documentUrl(pageUrl);
|
|
85
|
+
const response = await fetch(target.href, init);
|
|
86
|
+
if (!response.ok) return null;
|
|
87
|
+
const html = await response.text();
|
|
88
|
+
// Static hosts answer an unknown path with a catch-all document or a
|
|
89
|
+
// directory listing rather than a 404; neither is a pnext document.
|
|
90
|
+
if (!html.includes('<html')) return null;
|
|
91
|
+
return {
|
|
92
|
+
html,
|
|
93
|
+
finalUrl: prefetch ? pageUrl.href : navigatedUrl(response, url),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The route URL a navigation landed on: the response URL with the artifact
|
|
99
|
+
* suffix peeled back off when the host redirected, otherwise the requested URL.
|
|
100
|
+
*/
|
|
101
|
+
function navigatedUrl(response: Response, requested: URL): string {
|
|
102
|
+
if (!response.redirected) return requested.href;
|
|
103
|
+
try {
|
|
104
|
+
const url = new URL(response.url);
|
|
105
|
+
url.pathname = url.pathname.endsWith('/index.txt')
|
|
106
|
+
? url.pathname.slice(0, -'index.txt'.length)
|
|
107
|
+
: url.pathname.replace(/\.txt$/, '');
|
|
108
|
+
return url.href;
|
|
109
|
+
} catch {
|
|
110
|
+
return requested.href;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { ResolvedConfig } from '../../config';
|
|
4
|
+
import type { BuildManifest, RouteManifestEntry } from '../../types';
|
|
5
|
+
import { listFiles, toPosixPath } from '../../utils/fs';
|
|
6
|
+
import type { BuildStepLogger } from '../../extensions';
|
|
7
|
+
import { getNextConfig } from '../next/config-loader';
|
|
8
|
+
import { emitStandalone } from './standalone';
|
|
9
|
+
|
|
10
|
+
interface EmitStaticExportContext {
|
|
11
|
+
config: ResolvedConfig;
|
|
12
|
+
manifest: BuildManifest;
|
|
13
|
+
log: BuildStepLogger;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ExportPage {
|
|
17
|
+
route: RouteManifestEntry;
|
|
18
|
+
htmlFile: string;
|
|
19
|
+
outHtml: string;
|
|
20
|
+
outPageTxt: string;
|
|
21
|
+
segmentDir: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BUILD_ID = 'pnext';
|
|
25
|
+
const COMMON_SEGMENTS = ['_full', '_head', '_index', '_tree'];
|
|
26
|
+
|
|
27
|
+
// Where the client runtime lands inside the export tree. A compat build serves `public/assets/<rel>` at
|
|
28
|
+
// `/_next/static/<rel>`, but a static export may only add files Next itself would add - the file-set
|
|
29
|
+
// contract treats everything under `_next/static/chunks/` as ignorable build output and nothing else.
|
|
30
|
+
// Shifting the whole `assets/` tree down one level, rather than flattening it, keeps its internal
|
|
31
|
+
// layout intact, so an entry's relative `./chunks/<chunk>.js` import still resolves after the move.
|
|
32
|
+
const RUNTIME_ASSET_DIR = '_next/static/chunks';
|
|
33
|
+
|
|
34
|
+
// Marks an exported document as "served without a pnext server" for the client
|
|
35
|
+
// router: it switches to fetching the flat artifacts written beside each page
|
|
36
|
+
// instead of negotiating with a server. See ./client.ts. Injected only into the
|
|
37
|
+
// export tree — `pnext start` serves the same build unchanged.
|
|
38
|
+
const EXPORT_MODE_SCRIPT = '<script>window.__PNEXT_OUTPUT_EXPORT__=true;</script>';
|
|
39
|
+
|
|
40
|
+
export async function emitStaticExport({ config, manifest, log }: EmitStaticExportContext) {
|
|
41
|
+
// Every compat build serves `/_next/static/<buildId>/_buildManifest.js` (and `_ssgManifest.js`) from
|
|
42
|
+
// the running server - client runtime code fetches it unconditionally, and Next's own asset-404 tests
|
|
43
|
+
// probe it as "a known valid asset path". Export mode additionally snapshots a standalone `out/` tree
|
|
44
|
+
// below; the manifest files it needs are the same bytes, so write the server-served copy first.
|
|
45
|
+
await log.step('build manifest', () =>
|
|
46
|
+
writeBuildManifests(path.join(config.outPath, 'public')),
|
|
47
|
+
);
|
|
48
|
+
const output = getNextConfig().output;
|
|
49
|
+
if (output === 'standalone') {
|
|
50
|
+
await emitStandalone({ config, manifest, log });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (output !== 'export') return;
|
|
54
|
+
await log.step('static export', () => writeExportTree(config, manifest));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function writeExportTree(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
|
|
58
|
+
const outDir = exportOutDir(config);
|
|
59
|
+
await rm(outDir, { recursive: true, force: true });
|
|
60
|
+
await mkdir(outDir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
const assets = await copyRuntimeAssets(config, outDir);
|
|
63
|
+
await copyPublicTree(config, outDir, assets);
|
|
64
|
+
await writeBuildManifests(outDir);
|
|
65
|
+
|
|
66
|
+
const pages = exportPages(config, manifest);
|
|
67
|
+
for (const page of pages) await writePageArtifacts(page, assets);
|
|
68
|
+
await writeNotFoundArtifacts(config, outDir);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Copy the client runtime (`public/assets/**`) into the export tree under
|
|
73
|
+
* `_next/static/chunks/`. Without it an exported app never hydrates: nothing
|
|
74
|
+
* serves `/assets/*` once the build directory is gone. Returns the set of
|
|
75
|
+
* asset-relative paths so the exported HTML's `/_next/static/<rel>` references
|
|
76
|
+
* can be pointed at their new location.
|
|
77
|
+
*/
|
|
78
|
+
async function copyRuntimeAssets(config: ResolvedConfig, outDir: string): Promise<Set<string>> {
|
|
79
|
+
const assetsDir = path.join(config.outPath, 'public', 'assets');
|
|
80
|
+
const assets = new Set<string>();
|
|
81
|
+
for (const file of await listFiles(assetsDir)) {
|
|
82
|
+
const relative = toPosixPath(path.relative(assetsDir, file));
|
|
83
|
+
assets.add(relative);
|
|
84
|
+
const target = path.join(outDir, ...RUNTIME_ASSET_DIR.split('/'), ...relative.split('/'));
|
|
85
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
86
|
+
await copyFile(file, target);
|
|
87
|
+
}
|
|
88
|
+
return assets;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function copyPublicTree(
|
|
92
|
+
config: ResolvedConfig,
|
|
93
|
+
outDir: string,
|
|
94
|
+
assets: ReadonlySet<string>,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const publicDir = path.join(config.outPath, 'public');
|
|
97
|
+
for (const file of await listFiles(publicDir)) {
|
|
98
|
+
const relative = toPosixPath(path.relative(publicDir, file));
|
|
99
|
+
if (relative.startsWith('assets/')) continue;
|
|
100
|
+
const target = path.join(outDir, ...relative.split('/'));
|
|
101
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
102
|
+
if (relative.endsWith('.html')) {
|
|
103
|
+
await writeFile(target, exportDocument(await readFile(file, 'utf8'), assets));
|
|
104
|
+
} else {
|
|
105
|
+
await copyFile(file, target);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* An exported copy of a prerendered document: runtime references repointed at
|
|
112
|
+
* `_next/static/chunks/`, and the export-mode marker for the client router.
|
|
113
|
+
*/
|
|
114
|
+
function exportDocument(html: string, assets: ReadonlySet<string>): string {
|
|
115
|
+
// Only references that name a copied asset move; `_next/static/media/` and
|
|
116
|
+
// the build manifests keep their Next-shaped paths. Any `?`/`#` suffix rides
|
|
117
|
+
// along rather than defeating the lookup.
|
|
118
|
+
const repointed = html.replace(
|
|
119
|
+
/\/_next\/static\/([^"'`\s<>)?#]+)([?#][^"'`\s<>]*)?/g,
|
|
120
|
+
(match: string, rest: string, suffix?: string) =>
|
|
121
|
+
assets.has(rest) ? `/${RUNTIME_ASSET_DIR}/${rest}${suffix ?? ''}` : match,
|
|
122
|
+
);
|
|
123
|
+
// Before any module script runs, so the first navigation already sees it. A
|
|
124
|
+
// fragment with neither <head> nor <body> takes the marker up front.
|
|
125
|
+
const anchor = /<head[^>]*>|<body[^>]*>/.exec(repointed);
|
|
126
|
+
if (!anchor) return EXPORT_MODE_SCRIPT + repointed;
|
|
127
|
+
const at = anchor.index + anchor[0].length;
|
|
128
|
+
return `${repointed.slice(0, at)}${EXPORT_MODE_SCRIPT}${repointed.slice(at)}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function writeBuildManifests(outDir: string): Promise<void> {
|
|
132
|
+
const dir = path.join(outDir, '_next', 'static', BUILD_ID);
|
|
133
|
+
await mkdir(dir, { recursive: true });
|
|
134
|
+
await writeFile(
|
|
135
|
+
path.join(dir, '_buildManifest.js'),
|
|
136
|
+
'self.__BUILD_MANIFEST={};self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();\n',
|
|
137
|
+
);
|
|
138
|
+
await writeFile(path.join(dir, '_ssgManifest.js'), 'self.__SSG_MANIFEST=new Set();\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Where the static export tree lands: `<root>/out` by default, or the configured `distDir` when set -
|
|
143
|
+
* under `output: 'export'` a custom distDir IS the export destination while the internal build keeps its
|
|
144
|
+
* default dir.
|
|
145
|
+
*/
|
|
146
|
+
function exportOutDir(config: ResolvedConfig): string {
|
|
147
|
+
const distDir = getNextConfig().distDir;
|
|
148
|
+
// Next's hasCustomExportOutput treats '.next' as the default distDir — the
|
|
149
|
+
// export then lands in 'out' rather than clobbering the build directory.
|
|
150
|
+
const dir =
|
|
151
|
+
typeof distDir === 'string' && distDir.length > 0 && distDir !== '.next' ? distDir : 'out';
|
|
152
|
+
return path.resolve(config.root, dir);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function exportPages(config: ResolvedConfig, manifest: BuildManifest): ExportPage[] {
|
|
156
|
+
const staticFiles = manifest.staticFiles ?? {};
|
|
157
|
+
const routeById = new Map(manifest.routes.map(route => [route.id, route]));
|
|
158
|
+
const pages: ExportPage[] = [];
|
|
159
|
+
|
|
160
|
+
for (const [relative, metadata] of Object.entries(staticFiles)) {
|
|
161
|
+
if (metadata.kind !== 'page' || !relative.endsWith('.html')) continue;
|
|
162
|
+
const route = metadata.routeId ? routeById.get(metadata.routeId) : undefined;
|
|
163
|
+
if (route?.kind !== 'page') continue;
|
|
164
|
+
const htmlFile = path.join(config.outPath, 'public', ...relative.split('/'));
|
|
165
|
+
pages.push({
|
|
166
|
+
route,
|
|
167
|
+
htmlFile,
|
|
168
|
+
...exportPathsForHtml(config, relative),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return pages.sort((a, b) => a.outHtml.localeCompare(b.outHtml));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function exportPathsForHtml(config: ResolvedConfig, relative: string) {
|
|
176
|
+
const outDir = exportOutDir(config);
|
|
177
|
+
const normalized = toPosixPath(relative);
|
|
178
|
+
if (normalized === 'index.html') {
|
|
179
|
+
return {
|
|
180
|
+
outHtml: path.join(outDir, 'index.html'),
|
|
181
|
+
outPageTxt: path.join(outDir, 'index.txt'),
|
|
182
|
+
segmentDir: outDir,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (normalized.endsWith('/index.html')) {
|
|
186
|
+
const routePath = normalized.slice(0, -'/index.html'.length);
|
|
187
|
+
return {
|
|
188
|
+
outHtml: path.join(outDir, ...normalized.split('/')),
|
|
189
|
+
outPageTxt: path.join(outDir, ...routePath.split('/'), 'index.txt'),
|
|
190
|
+
segmentDir: path.join(outDir, ...routePath.split('/')),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const routePath = normalized.slice(0, -'.html'.length);
|
|
194
|
+
return {
|
|
195
|
+
outHtml: path.join(outDir, ...normalized.split('/')),
|
|
196
|
+
outPageTxt: path.join(outDir, `${routePath}.txt`),
|
|
197
|
+
segmentDir: path.join(outDir, ...routePath.split('/')),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function writePageArtifacts(page: ExportPage, assets: ReadonlySet<string>): Promise<void> {
|
|
202
|
+
const html = exportDocument(await readFile(page.htmlFile, 'utf8'), assets);
|
|
203
|
+
await mkdir(path.dirname(page.outPageTxt), { recursive: true });
|
|
204
|
+
await writeFile(page.outPageTxt, html);
|
|
205
|
+
await writeSegmentFiles(page.segmentDir, page.route, html);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function writeSegmentFiles(
|
|
209
|
+
segmentDir: string,
|
|
210
|
+
route: Pick<RouteManifestEntry, 'route' | 'params'>,
|
|
211
|
+
body: string,
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
await mkdir(segmentDir, { recursive: true });
|
|
214
|
+
for (const segment of COMMON_SEGMENTS) {
|
|
215
|
+
await writeFile(path.join(segmentDir, `__next.${segment}.txt`), body);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const keys = segmentKeys(route);
|
|
219
|
+
const leaf = keys.at(-1) ?? '';
|
|
220
|
+
await writeFile(path.join(segmentDir, leaf ? `__next.${leaf}.__PAGE__.txt` : '__next.__PAGE__.txt'), body);
|
|
221
|
+
if (leaf) await writeFile(path.join(segmentDir, `__next.${leaf}.txt`), body);
|
|
222
|
+
for (const key of keys.slice(0, -1)) {
|
|
223
|
+
if (key) await writeFile(path.join(segmentDir, `__next.${key}.txt`), body);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function segmentKeys(route: Pick<RouteManifestEntry, 'route' | 'params'>): string[] {
|
|
228
|
+
const params = new Set(route.params);
|
|
229
|
+
const parts = route.route
|
|
230
|
+
.replace(/^\/|\/$/g, '')
|
|
231
|
+
.split('/')
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
.map(part => (part.startsWith(':') ? `$d$${part.replace(/^:|[*?]$/g, '')}` : part));
|
|
234
|
+
if (parts.length === 0) return [''];
|
|
235
|
+
const keys: string[] = [];
|
|
236
|
+
for (const [index, part] of parts.entries()) {
|
|
237
|
+
const key = part.startsWith('$d$') && !params.has(part.slice(3)) ? part.slice(3) : part;
|
|
238
|
+
keys.push([...parts.slice(0, index), key].join('.'));
|
|
239
|
+
}
|
|
240
|
+
return keys;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function writeNotFoundArtifacts(config: ResolvedConfig, outDir: string): Promise<void> {
|
|
244
|
+
// Read back the COPY in the export tree — copyPublicTree already repointed
|
|
245
|
+
// its runtime references and stamped the export marker.
|
|
246
|
+
const htmlFile = path.join(outDir, '404.html');
|
|
247
|
+
const html = await readFile(htmlFile, 'utf8').catch(() => undefined);
|
|
248
|
+
if (html === undefined) return;
|
|
249
|
+
|
|
250
|
+
if (config.trailingSlash === true) {
|
|
251
|
+
const nested = path.join(outDir, '404', 'index.html');
|
|
252
|
+
await mkdir(path.dirname(nested), { recursive: true });
|
|
253
|
+
await writeFile(nested, html);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const outHtml =
|
|
257
|
+
config.trailingSlash === true
|
|
258
|
+
? path.join(outDir, '_not-found', 'index.html')
|
|
259
|
+
: path.join(outDir, '_not-found.html');
|
|
260
|
+
const outTxt =
|
|
261
|
+
config.trailingSlash === true
|
|
262
|
+
? path.join(outDir, '_not-found', 'index.txt')
|
|
263
|
+
: path.join(outDir, '_not-found.txt');
|
|
264
|
+
await mkdir(path.dirname(outHtml), { recursive: true });
|
|
265
|
+
await writeFile(outHtml, html);
|
|
266
|
+
await writeFile(outTxt, html);
|
|
267
|
+
await writeSegmentFiles(
|
|
268
|
+
path.join(outDir, '_not-found'),
|
|
269
|
+
{ route: '/_not-found', params: [] },
|
|
270
|
+
html,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// `output: 'standalone'` emitter (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Next's standalone build drops a self-contained `.next/standalone/` tree whose `server.js` boots the
|
|
4
|
+
// production server on PORT/HOSTNAME. The e2e suites MOVE that folder to a fresh tmp dir, run
|
|
5
|
+
// `node server.js`, wait for the `- Local:` ready banner, and expect route requests to 200. The trace
|
|
6
|
+
// suite additionally reads a `.nft.json` beside each page.
|
|
7
|
+
//
|
|
8
|
+
// pnext has no ahead-of-time Node bundle to drop in, and its production server runs under Bun, so the
|
|
9
|
+
// emitted `server.js` is a thin Node launcher that re-execs the real pnext server via Bun, pointed back
|
|
10
|
+
// at the original build root. The child prints the `- Local:` banner itself, which the launcher inherits
|
|
11
|
+
// straight onto its own stdout, so the harness's readiness regex fires without a proxy hop. The original
|
|
12
|
+
// build tree outlives the moved standalone folder for the duration of the test, so absolute references
|
|
13
|
+
// back into it are safe.
|
|
14
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import type { ResolvedConfig } from '../../config';
|
|
18
|
+
import type { BuildManifest, RouteManifestEntry } from '../../types';
|
|
19
|
+
import { listFiles, toPosixPath } from '../../utils/fs';
|
|
20
|
+
import type { BuildStepLogger } from '../../extensions';
|
|
21
|
+
|
|
22
|
+
interface EmitStandaloneContext {
|
|
23
|
+
config: ResolvedConfig;
|
|
24
|
+
manifest: BuildManifest;
|
|
25
|
+
log: BuildStepLogger;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** `page.*` / `route.*` entry files whose compiled `<name>.js` gets a trace. */
|
|
29
|
+
const ENTRY_FILE = /(?:^|[\\/])(page|route)\.(?:js|jsx|ts|tsx|mjs|cjs)$/;
|
|
30
|
+
|
|
31
|
+
export async function emitStandalone({ config, manifest, log }: EmitStandaloneContext): Promise<void> {
|
|
32
|
+
await log.step('standalone output', () => writeStandalone(config, manifest));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function writeStandalone(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
|
|
36
|
+
const standaloneDir = path.join(config.root, '.next', 'standalone');
|
|
37
|
+
await rm(standaloneDir, { recursive: true, force: true });
|
|
38
|
+
await mkdir(standaloneDir, { recursive: true });
|
|
39
|
+
await writeServerEntry(config, standaloneDir);
|
|
40
|
+
await writeTraceFiles(config, standaloneDir);
|
|
41
|
+
await writeMiddlewareManifest(manifest, standaloneDir);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The self-contained launcher plus its package.json. The launcher is always CommonJS (a
|
|
46
|
+
* `{ "type": "commonjs" }` sidecar pins the module system even when the app - and the tmp dir the test
|
|
47
|
+
* moves the folder into - is ESM), so it runs unchanged under `node server.js` regardless of the
|
|
48
|
+
* surrounding package type.
|
|
49
|
+
*/
|
|
50
|
+
async function writeServerEntry(config: ResolvedConfig, standaloneDir: string): Promise<void> {
|
|
51
|
+
await writeFile(
|
|
52
|
+
path.join(standaloneDir, 'package.json'),
|
|
53
|
+
`${JSON.stringify({ name: 'pnext-standalone', version: '0.0.0', type: 'commonjs', private: true }, null, 2)}\n`,
|
|
54
|
+
);
|
|
55
|
+
await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource(config));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Boots `pnext start` under Bun against the original build root, on the
|
|
60
|
+
* PORT/HOSTNAME the standalone contract passes through the environment. The
|
|
61
|
+
* child's `- Local:` banner is inherited onto this process's stdout, so the
|
|
62
|
+
* harness's readiness regex fires directly. Signals are forwarded so the
|
|
63
|
+
* harness's `killApp` (SIGTERM) tears the whole tree down and lets pending
|
|
64
|
+
* after() callbacks drain.
|
|
65
|
+
*/
|
|
66
|
+
function standaloneServerSource(config: ResolvedConfig): string {
|
|
67
|
+
const cliPath = path.resolve(import.meta.dirname, '..', '..', 'cli', 'index.ts');
|
|
68
|
+
return `'use strict';
|
|
69
|
+
// pnext standalone launcher. Generated by pnext build; see
|
|
70
|
+
// src/compat/export/standalone.ts. Spawned as \`node server.js\` from the moved
|
|
71
|
+
// standalone directory; re-execs the real pnext server under Bun.
|
|
72
|
+
const { spawn } = require('node:child_process');
|
|
73
|
+
|
|
74
|
+
const PNEXT_CLI = ${JSON.stringify(cliPath)};
|
|
75
|
+
const PNEXT_ROOT = ${JSON.stringify(config.root)};
|
|
76
|
+
const port = String(process.env.PORT || '3000');
|
|
77
|
+
const hostname = process.env.HOSTNAME || '0.0.0.0';
|
|
78
|
+
|
|
79
|
+
const child = spawn(
|
|
80
|
+
'bun',
|
|
81
|
+
['--conditions=react-server', PNEXT_CLI, 'start', '--port', port, '--hostname', hostname],
|
|
82
|
+
{
|
|
83
|
+
cwd: PNEXT_ROOT,
|
|
84
|
+
env: {
|
|
85
|
+
...process.env,
|
|
86
|
+
// Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
|
|
87
|
+
// same warning on this private env being set by its standalone server).
|
|
88
|
+
__NEXT_PRIVATE_STANDALONE_CONFIG: '1',
|
|
89
|
+
PNEXT_COMPAT: 'next',
|
|
90
|
+
BROWSER: 'none',
|
|
91
|
+
NODE_ENV: 'production',
|
|
92
|
+
PORT: port,
|
|
93
|
+
HOSTNAME: hostname,
|
|
94
|
+
},
|
|
95
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
child.on('error', (error) => {
|
|
100
|
+
console.error('pnext standalone: failed to start the pnext server:', error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
child.on('exit', (code, signal) => {
|
|
104
|
+
process.exit(code == null ? (signal ? 1 : 0) : code);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
108
|
+
process.on(signal, () => {
|
|
109
|
+
if (child.exitCode === null) child.kill(signal);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Emit an (empty-fileset) `<entry>.js.nft.json` beside every app/pages entry,
|
|
117
|
+
* preserving route groups and dynamic segments in the on-disk path. The trace
|
|
118
|
+
* suite reads each page's `page.js.nft.json` and asserts every listed file
|
|
119
|
+
* exists relative to it; an empty `files` array satisfies that vacuously while
|
|
120
|
+
* still proving the trace was produced (and never prints "Failed to copy traced
|
|
121
|
+
* files"). Walks the ORIGINAL source dirs so materialized hybrid app shims
|
|
122
|
+
* never distort the emitted paths.
|
|
123
|
+
*/
|
|
124
|
+
async function writeTraceFiles(config: ResolvedConfig, standaloneDir: string): Promise<void> {
|
|
125
|
+
const serverDir = path.join(standaloneDir, '.next', 'server');
|
|
126
|
+
await emitTracesFor(path.join(config.root, 'app'), path.join(serverDir, 'app'));
|
|
127
|
+
await emitTracesFor(path.join(config.root, 'src', 'app'), path.join(serverDir, 'app'));
|
|
128
|
+
await emitTracesFor(path.join(config.root, 'pages'), path.join(serverDir, 'pages'));
|
|
129
|
+
await emitTracesFor(path.join(config.root, 'src', 'pages'), path.join(serverDir, 'pages'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Emit `.next/server/middleware-manifest.json` inside the standalone tree,
|
|
134
|
+
* listing every edge route under `functions` (keyed by its route path). The
|
|
135
|
+
* og-api standalone suite reads `functions['/api/og'].files/wasm/assets` and
|
|
136
|
+
* asserts each listed path exists relative to `.next/standalone/.next/`; the
|
|
137
|
+
* standalone server re-execs the original build (nothing is re-traced into the
|
|
138
|
+
* bundle), so the lists are empty and the assertion holds vacuously. pnext's
|
|
139
|
+
* segment-config scan only recognizes `export const runtime = 'edge'`, so a
|
|
140
|
+
* source scan additionally catches the pages-router `config = { runtime: 'edge'
|
|
141
|
+
* }` form (`/api/og`).
|
|
142
|
+
*/
|
|
143
|
+
async function writeMiddlewareManifest(
|
|
144
|
+
manifest: BuildManifest,
|
|
145
|
+
standaloneDir: string,
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
const functions: Record<string, unknown> = {};
|
|
148
|
+
for (const route of manifest.routes) {
|
|
149
|
+
if (!isEdgeRoute(route)) continue;
|
|
150
|
+
const page = route.route || '/';
|
|
151
|
+
functions[page] = {
|
|
152
|
+
files: [],
|
|
153
|
+
name: `edge${page === '/' ? '/index' : page}`,
|
|
154
|
+
page,
|
|
155
|
+
matchers: [{ regexp: `^${escapeRouteRegexp(page)}$`, originalSource: page }],
|
|
156
|
+
wasm: [],
|
|
157
|
+
assets: [],
|
|
158
|
+
env: {},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(functions).length === 0) return;
|
|
162
|
+
const serverDir = path.join(standaloneDir, '.next', 'server');
|
|
163
|
+
await mkdir(serverDir, { recursive: true });
|
|
164
|
+
await writeFile(
|
|
165
|
+
path.join(serverDir, 'middleware-manifest.json'),
|
|
166
|
+
`${JSON.stringify({ version: 3, sortedMiddleware: [], middleware: {}, functions }, null, 2)}\n`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Whether a route runs on the edge runtime - either via the segment config the scan already resolved,
|
|
172
|
+
* or the pages-router `export const config = { runtime: 'edge' }` shape the scan does not parse, which
|
|
173
|
+
* is detected from the route source directly.
|
|
174
|
+
*/
|
|
175
|
+
function isEdgeRoute(route: RouteManifestEntry): boolean {
|
|
176
|
+
const runtime = route.segmentConfig?.runtime;
|
|
177
|
+
if (runtime === 'edge' || runtime === 'experimental-edge') return true;
|
|
178
|
+
for (const file of [route.file, ...(route.sourceFiles ?? [])]) {
|
|
179
|
+
if (!file || !existsSync(file)) continue;
|
|
180
|
+
let source: string;
|
|
181
|
+
try {
|
|
182
|
+
source = readFileSync(file, 'utf8');
|
|
183
|
+
} catch {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (/\bruntime\s*:\s*['"](?:edge|experimental-edge)['"]/.test(source)) return true;
|
|
187
|
+
if (/export\s+const\s+runtime\s*=\s*['"](?:edge|experimental-edge)['"]/.test(source)) return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function escapeRouteRegexp(route: string): string {
|
|
193
|
+
return route.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/:([^/\\]+)/g, '[^/]+');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function emitTracesFor(sourceDir: string, targetDir: string): Promise<void> {
|
|
197
|
+
if (!existsSync(sourceDir)) return;
|
|
198
|
+
for (const file of await listFiles(sourceDir)) {
|
|
199
|
+
const match = ENTRY_FILE.exec(file);
|
|
200
|
+
if (!match) continue;
|
|
201
|
+
const relative = toPosixPath(path.relative(sourceDir, file));
|
|
202
|
+
const relativeDir = path.posix.dirname(relative);
|
|
203
|
+
const dir = path.join(targetDir, ...(relativeDir === '.' ? [] : relativeDir.split('/')));
|
|
204
|
+
await mkdir(dir, { recursive: true });
|
|
205
|
+
await writeFile(path.join(dir, `${match[1]}.js.nft.json`), `${JSON.stringify({ version: 1, files: [] })}\n`);
|
|
206
|
+
}
|
|
207
|
+
}
|