vinext 0.1.0 → 0.1.2

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.
Files changed (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -0,0 +1,89 @@
1
+ import { workUnitAsyncStorage } from "../shims/internal/work-unit-async-storage.js";
2
+ //#region src/server/otel-tracer-extension.ts
3
+ /**
4
+ * OpenTelemetry tracer provider extension for Cache Components.
5
+ *
6
+ * When `cacheComponents: true` is enabled in next.config, component renders
7
+ * go through multiple phases (warmup → resume). During these phases, the
8
+ * `workUnitAsyncStorage` carries a prerender or cache store. Without this
9
+ * extension, calls to `tracer.startSpan()` / `tracer.startActiveSpan()` from
10
+ * inside user RSC code would inherit that prerender context, causing:
11
+ *
12
+ * 1. Spans to reuse the same trace ID across requests (the frozen prerender
13
+ * context bleeds into the runtime resume render).
14
+ * 2. Spans not being created at all during fallback resume (the work unit
15
+ * context gates span creation in some OTel SDK implementations).
16
+ *
17
+ * The fix mirrors Next.js's `instrumentation-node-extensions.ts`:
18
+ * - Wrap `tracer.startSpan` to exit `workUnitAsyncStorage` before creating
19
+ * the span, ensuring a clean context for span ID generation.
20
+ * - Wrap `tracer.startActiveSpan` similarly and re-enter the work unit store
21
+ * for the callback, so that the callback runs with the correct request
22
+ * context restored.
23
+ *
24
+ * This extension is intentionally a no-op when:
25
+ * - `@opentelemetry/api` is not installed (graceful degradation).
26
+ * - No OTel tracer provider has been registered (provider is the noop provider).
27
+ * - `workUnitAsyncStorage` has no active store (non-render contexts).
28
+ *
29
+ * References:
30
+ * - packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
31
+ * - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
32
+ */
33
+ const extendedProviders = /* @__PURE__ */ new WeakSet();
34
+ const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
35
+ function isUseCacheFn(fn) {
36
+ return typeof fn === "function" && fn[USE_CACHE_FUNCTION_SYMBOL] === true;
37
+ }
38
+ /**
39
+ * Extend the registered OTel tracer provider so that `startSpan` and
40
+ * `startActiveSpan` exit the `workUnitAsyncStorage` context before creating
41
+ * spans. This prevents the prerender/cache work unit store from leaking into
42
+ * span ID generation during Cache Component fallback resumes.
43
+ *
44
+ * Safe to call multiple times — subsequent calls are no-ops once the provider
45
+ * has been wrapped.
46
+ *
47
+ * Must only be called in Node.js environments (not Edge runtime).
48
+ */
49
+ function extendTracerProviderForCacheComponents() {
50
+ let api;
51
+ try {
52
+ const req = globalThis.require;
53
+ if (typeof req === "function") api = req("@opentelemetry/api");
54
+ } catch {
55
+ return;
56
+ }
57
+ if (!api) return;
58
+ const provider = api.trace.getTracerProvider();
59
+ if (!provider || typeof provider.getTracer !== "function") return;
60
+ if (extendedProviders.has(provider)) return;
61
+ extendedProviders.add(provider);
62
+ const originalGetTracer = provider.getTracer.bind(provider);
63
+ const wrappedTracers = /* @__PURE__ */ new WeakSet();
64
+ provider.getTracer = (...args) => {
65
+ const tracer = originalGetTracer(...args);
66
+ if (!tracer || wrappedTracers.has(tracer)) return tracer;
67
+ const originalStartSpan = tracer.startSpan;
68
+ if (typeof originalStartSpan === "function") tracer.startSpan = (...startSpanArgs) => workUnitAsyncStorage.exit(() => originalStartSpan.apply(tracer, startSpanArgs));
69
+ const originalStartActiveSpan = tracer.startActiveSpan;
70
+ if (typeof originalStartActiveSpan === "function") tracer.startActiveSpan = (...startActiveSpanArgs) => {
71
+ const workUnitStore = workUnitAsyncStorage.getStore();
72
+ if (!workUnitStore) return originalStartActiveSpan.apply(tracer, startActiveSpanArgs);
73
+ let fnIdx = 0;
74
+ if (startActiveSpanArgs.length === 2 && typeof startActiveSpanArgs[1] === "function") fnIdx = 1;
75
+ else if (startActiveSpanArgs.length === 3 && typeof startActiveSpanArgs[2] === "function") fnIdx = 2;
76
+ else if (startActiveSpanArgs.length > 3 && typeof startActiveSpanArgs[3] === "function") fnIdx = 3;
77
+ if (fnIdx > 0) {
78
+ const originalFn = startActiveSpanArgs[fnIdx];
79
+ if (isUseCacheFn(originalFn)) console.error("A Cache Function (`use cache`) was passed to startActiveSpan which means it will receive a Span argument with a possibly random ID on every invocation leading to cache misses. Provide a wrapping function around the Cache Function that does not forward the Span argument to avoid this issue.");
80
+ startActiveSpanArgs[fnIdx] = (...cbArgs) => workUnitAsyncStorage.run(workUnitStore, originalFn, ...cbArgs);
81
+ }
82
+ return workUnitAsyncStorage.exit(() => originalStartActiveSpan.apply(tracer, startActiveSpanArgs));
83
+ };
84
+ wrappedTracers.add(tracer);
85
+ return tracer;
86
+ };
87
+ }
88
+ //#endregion
89
+ export { extendTracerProviderForCacheComponents };
@@ -12,8 +12,7 @@ type PagesApiRouteConfig = {
12
12
  * `bodyParser: false` is critical for webhook handlers (Stripe, GitHub,
13
13
  * Slack, etc.) that need to read the raw bytes to verify an HMAC
14
14
  * signature. With it set, `req.body` is left undefined and the raw stream
15
- * is exposed on `req.body` as a Web `ReadableStream<Uint8Array>` so user
16
- * code can consume it.
15
+ * remains available through the Node-readable request object.
17
16
  *
18
17
  * @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config
19
18
  */
@@ -22,9 +21,21 @@ type PagesApiRouteConfig = {
22
21
  sizeLimit?: string | number;
23
22
  };
24
23
  responseLimit?: boolean | string | number;
24
+ /**
25
+ * `externalResolver: true` declares that the response is sent by an
26
+ * external resolver (e.g. express/connect proxy middleware) that may
27
+ * complete after the handler's promise settles. Next.js uses it to
28
+ * suppress the "API resolved without sending a response" dev warning;
29
+ * vinext additionally uses it to suppress the auto-`end()` safety net,
30
+ * which would otherwise resolve an empty response before the external
31
+ * resolver writes.
32
+ *
33
+ * @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config
34
+ */
35
+ externalResolver?: boolean;
25
36
  };
26
37
  };
27
- type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesReqResResponse) => void | Promise<void>;
38
+ type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesReqResResponse) => unknown;
28
39
  type PagesEdgeApiRouteHandler = (request: Request) => Response | Promise<Response>;
29
40
  type PagesApiRouteModule = {
30
41
  /**
@@ -43,8 +43,13 @@ async function _handlePagesApiRoute(options) {
43
43
  request: options.request,
44
44
  url: options.url
45
45
  });
46
+ let resWasPiped = false;
47
+ res.once("pipe", () => {
48
+ resWasPiped = true;
49
+ });
50
+ const externalResolver = route.module.config?.api?.externalResolver || false;
46
51
  await route.module.default(req, res);
47
- res.end();
52
+ if (!externalResolver && !resWasPiped && !res.headersSent) res.end();
48
53
  return await responsePromise;
49
54
  } catch (error) {
50
55
  if (error instanceof PagesBodyParseError) return new Response(error.message, {
@@ -23,11 +23,14 @@ declare function resolveSsrManifest(manifest: Record<string, string[]> | null |
23
23
  */
24
24
  declare function getManifestFilesForModule(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined): string[] | null;
25
25
  /**
26
- * Find the first `.js` file in the manifest for `moduleId` and return its
27
- * URL-path form (with a leading `/`). Used to resolve the hydration URL for
28
- * the matched page or the `_app` module.
26
+ * Find the first `.js` file in the manifest for `moduleId` and return the URL it
27
+ * is actually SERVED from. Used to resolve the client-navigation / hydration URL
28
+ * for the matched page or the `_app` module (it is `import()`ed on the client),
29
+ * so it must point at the served location: `assetPrefix` replaces `basePath` for
30
+ * asset URLs. SSR-manifest values are base-anchored; re-anchor under any
31
+ * configured `assetPrefix` (default `""` keeps the legacy `"/" + file`).
29
32
  */
30
- declare function resolveClientModuleUrl(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined): string | undefined;
33
+ declare function resolveClientModuleUrl(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined, basePath?: string, assetPrefix?: string): string | undefined;
31
34
  type CollectAssetTagsOptions = {
32
35
  /**
33
36
  * SSR manifest mapping module file paths to their associated asset list.
@@ -46,6 +49,14 @@ type CollectAssetTagsOptions = {
46
49
  * default.
47
50
  */
48
51
  disableOptimizedLoading: boolean;
52
+ /**
53
+ * Configured `basePath` / `assetPrefix`. SSR-manifest values are base-anchored
54
+ * (needed for the lazy-chunk membership test), but the EMITTED href must point
55
+ * where the asset is actually served — `assetPrefix` replaces `basePath` for
56
+ * asset URLs. Default `""` (both unset) keeps the legacy `"/" + value` href.
57
+ */
58
+ basePath?: string;
59
+ assetPrefix?: string;
49
60
  };
50
61
  /**
51
62
  * Build the HTML `<link>` and `<script>` tag string for the SSR response.
@@ -1,4 +1,5 @@
1
1
  import { createNonceAttribute } from "./html.js";
2
+ import { assetServingUrlFromBaseAnchored } from "../utils/manifest-paths.js";
2
3
  //#region src/server/pages-asset-tags.ts
3
4
  /**
4
5
  * Pages Router SSR asset-tag helpers.
@@ -33,18 +34,20 @@ function getManifestFilesForModule(manifest, moduleId) {
33
34
  return null;
34
35
  }
35
36
  /**
36
- * Find the first `.js` file in the manifest for `moduleId` and return its
37
- * URL-path form (with a leading `/`). Used to resolve the hydration URL for
38
- * the matched page or the `_app` module.
37
+ * Find the first `.js` file in the manifest for `moduleId` and return the URL it
38
+ * is actually SERVED from. Used to resolve the client-navigation / hydration URL
39
+ * for the matched page or the `_app` module (it is `import()`ed on the client),
40
+ * so it must point at the served location: `assetPrefix` replaces `basePath` for
41
+ * asset URLs. SSR-manifest values are base-anchored; re-anchor under any
42
+ * configured `assetPrefix` (default `""` keeps the legacy `"/" + file`).
39
43
  */
40
- function resolveClientModuleUrl(manifest, moduleId) {
44
+ function resolveClientModuleUrl(manifest, moduleId, basePath = "", assetPrefix = "") {
41
45
  const files = getManifestFilesForModule(resolveSsrManifest(manifest), moduleId);
42
46
  if (!files) return void 0;
43
47
  for (let i = 0; i < files.length; i++) {
44
- let file = files[i];
48
+ const file = files[i];
45
49
  if (!file || !file.endsWith(".js")) continue;
46
- if (file.charAt(0) !== "/") file = "/" + file;
47
- return file;
50
+ return assetServingUrlFromBaseAnchored(file, basePath, assetPrefix);
48
51
  }
49
52
  }
50
53
  /**
@@ -67,13 +70,16 @@ function collectAssetTags(options) {
67
70
  const seen = /* @__PURE__ */ new Set();
68
71
  const nonceAttr = createNonceAttribute(options.scriptNonce);
69
72
  const deferAttr = options.disableOptimizedLoading ? "" : " defer";
73
+ const basePath = options.basePath ?? "";
74
+ const assetPrefix = options.assetPrefix ?? "";
75
+ const href = (value) => assetServingUrlFromBaseAnchored(value, basePath, assetPrefix);
70
76
  const lazyChunks = typeof globalThis !== "undefined" && globalThis.__VINEXT_LAZY_CHUNKS__ || null;
71
77
  const lazySet = lazyChunks && lazyChunks.length > 0 ? new Set(lazyChunks) : null;
72
78
  if (typeof globalThis !== "undefined" && globalThis.__VINEXT_CLIENT_ENTRY__) {
73
79
  const entry = globalThis.__VINEXT_CLIENT_ENTRY__;
74
80
  seen.add(entry);
75
- tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"/" + entry + "\" />");
76
- tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"/" + entry + "\" crossorigin><\/script>");
81
+ tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(entry) + "\" />");
82
+ tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(entry) + "\" crossorigin><\/script>");
77
83
  }
78
84
  if (m) {
79
85
  const allFiles = [];
@@ -102,11 +108,11 @@ function collectAssetTags(options) {
102
108
  if (tf.charAt(0) === "/") tf = tf.slice(1);
103
109
  if (seen.has(tf)) continue;
104
110
  seen.add(tf);
105
- if (tf.endsWith(".css")) tags.push("<link rel=\"stylesheet\"" + nonceAttr + " href=\"/" + tf + "\" />");
111
+ if (tf.endsWith(".css")) tags.push("<link rel=\"stylesheet\"" + nonceAttr + " href=\"" + href(tf) + "\" />");
106
112
  else if (tf.endsWith(".js")) {
107
113
  if (lazySet && lazySet.has(tf)) continue;
108
- tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"/" + tf + "\" />");
109
- tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"/" + tf + "\" crossorigin><\/script>");
114
+ tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(tf) + "\" />");
115
+ tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(tf) + "\" crossorigin><\/script>");
110
116
  }
111
117
  }
112
118
  }
@@ -1,3 +1,4 @@
1
+ import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
1
2
  //#region src/server/pages-data-route.ts
2
3
  /**
3
4
  * Helpers for the Pages Router `/_next/data/{buildId}/{...page}.json` endpoint.
@@ -87,9 +88,12 @@ function buildNextDataJsonResponse(pageProps, safeJsonStringify, init) {
87
88
  * before checking the status code.
88
89
  */
89
90
  function buildNextDataNotFoundResponse() {
91
+ const headers = { "Content-Type": "application/json" };
92
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
93
+ if (deploymentId) headers[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
90
94
  return new Response("{}", {
91
95
  status: 404,
92
- headers: { "Content-Type": "application/json" }
96
+ headers
93
97
  });
94
98
  }
95
99
  /**
@@ -1,38 +1,32 @@
1
1
  import { PagesBodyParseError } from "./pages-media-type.js";
2
+ import { RevalidateOptions } from "./pages-revalidate.js";
3
+ import { Readable, Writable } from "node:stream";
2
4
 
3
5
  //#region src/server/pages-node-compat.d.ts
4
6
  type PagesRequestQuery = Record<string, string | string[]>;
5
- type PagesReqResRequest = {
7
+ type PagesReqResRequest = Readable & {
6
8
  method: string;
7
9
  url: string;
8
10
  headers: Record<string, string>;
9
11
  query: PagesRequestQuery;
10
12
  body: unknown;
11
13
  cookies: Record<string, string>;
12
- /**
13
- * Async-iterator hook so handlers can `for await (const chunk of req)` —
14
- * matching Node's `IncomingMessage` contract. Critical for the
15
- * `bodyParser: false` opt-out (webhook signature verification etc.) where
16
- * `req.body` is left undefined and user code is expected to drain the raw
17
- * stream off `req` itself.
18
- */
19
- [Symbol.asyncIterator]: () => AsyncIterator<Uint8Array>;
20
14
  };
21
15
  type PagesReqResHeaders = {
22
16
  [key: string]: string | number | boolean | string[];
23
17
  };
24
- type PagesReqResResponse = {
18
+ type PagesReqResResponse = Writable & {
25
19
  statusCode: number;
26
20
  readonly headersSent: boolean;
27
21
  writeHead: (code: number, headers?: PagesReqResHeaders) => PagesReqResResponse;
28
22
  setHeader: (name: string, value: string | number | boolean | string[]) => PagesReqResResponse;
29
23
  getHeader: (name: string) => string | number | boolean | string[] | undefined;
30
- end: (data?: BodyInit | null) => void;
31
24
  status: (code: number) => PagesReqResResponse;
32
25
  json: (data: unknown) => void;
33
26
  send: (data: unknown) => void;
34
27
  redirect: (statusOrUrl: number | string, url?: string) => void;
35
28
  getHeaders: () => PagesReqResHeaders;
29
+ revalidate: (urlPath: string, opts?: RevalidateOptions) => Promise<void>;
36
30
  };
37
31
  type CreatePagesReqResOptions = {
38
32
  body: unknown;
@@ -2,7 +2,9 @@ import { parseCookies } from "../config/config-matchers.js";
2
2
  import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
3
3
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
4
4
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
5
+ import { performOnDemandRevalidate } from "./pages-revalidate.js";
5
6
  import { decode } from "node:querystring";
7
+ import { Readable, Writable } from "node:stream";
6
8
  //#region src/server/pages-node-compat.ts
7
9
  const MAX_PAGES_API_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;
8
10
  async function readPagesRequestBodyWithLimit(request, maxBytes) {
@@ -40,137 +42,192 @@ async function parsePagesApiBody(request, maxBytes = MAX_PAGES_API_BODY_SIZE) {
40
42
  if (mediaType === "application/x-www-form-urlencoded") return decode(rawBody);
41
43
  return rawBody;
42
44
  }
45
+ async function* requestBodyChunks(request) {
46
+ if (!request.body || request.bodyUsed) return;
47
+ const reader = request.body.getReader();
48
+ try {
49
+ for (;;) {
50
+ const { value, done } = await reader.read();
51
+ if (done) return;
52
+ yield Buffer.from(value.buffer, value.byteOffset, value.byteLength);
53
+ }
54
+ } finally {
55
+ try {
56
+ await reader.cancel();
57
+ } catch {}
58
+ reader.releaseLock();
59
+ }
60
+ }
61
+ function createRequestReadable(request) {
62
+ return Readable.from(requestBodyChunks(request), { objectMode: false });
63
+ }
64
+ var PagesResponseStream = class extends Writable {
65
+ resolveResponse;
66
+ rejectResponse;
67
+ requestHeaders;
68
+ resStatusCode = 200;
69
+ resHeaders = {};
70
+ setCookieHeaders = [];
71
+ resolved = false;
72
+ controller = null;
73
+ bufferedChunks = [];
74
+ streamEnded = false;
75
+ constructor(resolveResponse, rejectResponse, requestHeaders) {
76
+ super();
77
+ this.resolveResponse = resolveResponse;
78
+ this.rejectResponse = rejectResponse;
79
+ this.requestHeaders = requestHeaders;
80
+ this.once("error", (err) => {
81
+ if (!this.resolved) {
82
+ this.resolved = true;
83
+ this.rejectResponse(err);
84
+ }
85
+ });
86
+ }
87
+ get statusCode() {
88
+ return this.resStatusCode;
89
+ }
90
+ set statusCode(code) {
91
+ this.resStatusCode = code;
92
+ }
93
+ get headersSent() {
94
+ return this.writableEnded || this.resolved;
95
+ }
96
+ writeHead(code, headers) {
97
+ this.resStatusCode = code;
98
+ if (headers) for (const [key, value] of Object.entries(headers)) this.setHeaderValue(key, value, { replaceSetCookie: false });
99
+ return this;
100
+ }
101
+ setHeader(name, value) {
102
+ this.setHeaderValue(name, value, { replaceSetCookie: true });
103
+ return this;
104
+ }
105
+ getHeader(name) {
106
+ if (name.toLowerCase() === "set-cookie") return this.setCookieHeaders.length > 0 ? this.setCookieHeaders : void 0;
107
+ return this.resHeaders[name.toLowerCase()];
108
+ }
109
+ status(code) {
110
+ this.resStatusCode = code;
111
+ return this;
112
+ }
113
+ json(data) {
114
+ this.resHeaders["content-type"] = "application/json";
115
+ this.end(JSON.stringify(data));
116
+ }
117
+ send(data) {
118
+ if (Buffer.isBuffer(data)) {
119
+ if (!this.resHeaders["content-type"]) this.resHeaders["content-type"] = "application/octet-stream";
120
+ this.resHeaders["content-length"] = String(data.length);
121
+ this.end(data);
122
+ return;
123
+ }
124
+ if (typeof data === "object" && data !== null) {
125
+ this.resHeaders["content-type"] = "application/json";
126
+ this.end(JSON.stringify(data));
127
+ return;
128
+ }
129
+ if (!this.resHeaders["content-type"]) this.resHeaders["content-type"] = "text/plain";
130
+ this.end(String(data));
131
+ }
132
+ redirect(statusOrUrl, url) {
133
+ if (typeof statusOrUrl === "string") this.writeHead(307, { Location: statusOrUrl });
134
+ else this.writeHead(statusOrUrl, { Location: url ?? "" });
135
+ this.end();
136
+ }
137
+ getHeaders() {
138
+ const headers = { ...this.resHeaders };
139
+ if (this.setCookieHeaders.length > 0) headers["set-cookie"] = this.setCookieHeaders;
140
+ return headers;
141
+ }
142
+ async revalidate(urlPath, opts) {
143
+ await performOnDemandRevalidate(this.requestHeaders, urlPath, opts);
144
+ }
145
+ _write(chunk, encoding, callback) {
146
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk, encoding) : Buffer.from(chunk);
147
+ if (this.controller && !this.streamEnded) try {
148
+ this.controller.enqueue(buffer);
149
+ } catch {}
150
+ else this.bufferedChunks.push(buffer);
151
+ this.resolveOnce();
152
+ callback();
153
+ }
154
+ _final(callback) {
155
+ this.streamEnded = true;
156
+ if (this.controller) try {
157
+ this.controller.close();
158
+ } catch {}
159
+ this.resolveOnce();
160
+ callback();
161
+ }
162
+ _destroy(error, callback) {
163
+ this.streamEnded = true;
164
+ if (!this.resolved) if (error) {
165
+ this.resolved = true;
166
+ this.rejectResponse(error);
167
+ } else this.resolveOnce();
168
+ if (this.controller) try {
169
+ if (error) this.controller.error(error);
170
+ else this.controller.close();
171
+ } catch {}
172
+ callback(error);
173
+ }
174
+ setHeaderValue(name, value, options) {
175
+ if (name.toLowerCase() === "set-cookie") {
176
+ if (options.replaceSetCookie) this.setCookieHeaders.length = 0;
177
+ if (Array.isArray(value)) this.setCookieHeaders.push(...value.map(String));
178
+ else this.setCookieHeaders.push(String(value));
179
+ return;
180
+ }
181
+ this.resHeaders[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
182
+ }
183
+ resolveOnce() {
184
+ if (this.resolved) return;
185
+ this.resolved = true;
186
+ const headers = new Headers();
187
+ for (const [key, value] of Object.entries(this.resHeaders)) headers.set(key, String(value));
188
+ for (const cookie of this.setCookieHeaders) headers.append("set-cookie", cookie);
189
+ const stream = new ReadableStream({
190
+ start: (controller) => {
191
+ this.controller = controller;
192
+ for (const buffer of this.bufferedChunks) try {
193
+ controller.enqueue(buffer);
194
+ } catch {}
195
+ this.bufferedChunks.length = 0;
196
+ if (this.streamEnded) try {
197
+ controller.close();
198
+ } catch {}
199
+ },
200
+ cancel: (reason) => {
201
+ this.bufferedChunks.length = 0;
202
+ this.destroy(reason instanceof Error ? reason : /* @__PURE__ */ new Error("Response body cancelled"));
203
+ }
204
+ });
205
+ this.resolveResponse(new Response(stream, {
206
+ status: this.resStatusCode,
207
+ headers
208
+ }));
209
+ }
210
+ };
43
211
  function createPagesReqRes(options) {
44
212
  const headersObj = {};
45
213
  for (const [key, value] of options.request.headers) headersObj[key.toLowerCase()] = value;
46
- const requestBody = options.request.body;
47
- const reqAsyncIterator = () => {
48
- if (!requestBody) return { next() {
49
- return Promise.resolve({
50
- value: void 0,
51
- done: true
52
- });
53
- } };
54
- const reader = requestBody.getReader();
55
- return {
56
- async next() {
57
- const { value, done } = await reader.read();
58
- if (done) return {
59
- value: void 0,
60
- done: true
61
- };
62
- return {
63
- value,
64
- done: false
65
- };
66
- },
67
- async return() {
68
- try {
69
- await reader.cancel();
70
- } catch {}
71
- return {
72
- value: void 0,
73
- done: true
74
- };
75
- }
76
- };
77
- };
78
- const req = {
214
+ const req = Object.assign(createRequestReadable(options.request), {
79
215
  method: options.request.method,
80
216
  url: options.url,
81
217
  headers: headersObj,
82
218
  query: options.query,
83
219
  body: options.body,
84
- cookies: parseCookies(options.request.headers.get("cookie")),
85
- [Symbol.asyncIterator]: reqAsyncIterator
86
- };
87
- let resStatusCode = 200;
88
- const resHeaders = {};
89
- const setCookieHeaders = [];
90
- let resBody = null;
91
- let ended = false;
220
+ cookies: parseCookies(options.request.headers.get("cookie"))
221
+ });
92
222
  let resolveResponse;
93
- const responsePromise = new Promise((resolve) => {
223
+ let rejectResponse;
224
+ const responsePromise = new Promise((resolve, reject) => {
94
225
  resolveResponse = resolve;
226
+ rejectResponse = reject;
95
227
  });
96
- const res = {
97
- get statusCode() {
98
- return resStatusCode;
99
- },
100
- set statusCode(code) {
101
- resStatusCode = code;
102
- },
103
- get headersSent() {
104
- return ended;
105
- },
106
- writeHead(code, headers) {
107
- resStatusCode = code;
108
- if (headers) for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === "set-cookie") if (Array.isArray(value)) setCookieHeaders.push(...value.map(String));
109
- else setCookieHeaders.push(String(value));
110
- else resHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
111
- return res;
112
- },
113
- setHeader(name, value) {
114
- if (name.toLowerCase() === "set-cookie") {
115
- setCookieHeaders.length = 0;
116
- if (Array.isArray(value)) setCookieHeaders.push(...value.map(String));
117
- else setCookieHeaders.push(String(value));
118
- } else resHeaders[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
119
- return res;
120
- },
121
- getHeader(name) {
122
- if (name.toLowerCase() === "set-cookie") return setCookieHeaders.length > 0 ? setCookieHeaders : void 0;
123
- return resHeaders[name.toLowerCase()];
124
- },
125
- end(data) {
126
- if (ended) return;
127
- ended = true;
128
- if (data !== void 0 && data !== null) resBody = data;
129
- const headers = new Headers();
130
- for (const [key, value] of Object.entries(resHeaders)) headers.set(key, String(value));
131
- for (const cookie of setCookieHeaders) headers.append("set-cookie", cookie);
132
- resolveResponse(new Response(resBody, {
133
- status: resStatusCode,
134
- headers
135
- }));
136
- },
137
- status(code) {
138
- resStatusCode = code;
139
- return res;
140
- },
141
- json(data) {
142
- resHeaders["content-type"] = "application/json";
143
- res.end(JSON.stringify(data));
144
- },
145
- send(data) {
146
- if (Buffer.isBuffer(data)) {
147
- if (!resHeaders["content-type"]) resHeaders["content-type"] = "application/octet-stream";
148
- resHeaders["content-length"] = String(data.length);
149
- res.end(new Uint8Array(data));
150
- return;
151
- }
152
- if (typeof data === "object" && data !== null) {
153
- resHeaders["content-type"] = "application/json";
154
- res.end(JSON.stringify(data));
155
- return;
156
- }
157
- if (!resHeaders["content-type"]) resHeaders["content-type"] = "text/plain";
158
- res.end(String(data));
159
- },
160
- redirect(statusOrUrl, url) {
161
- if (typeof statusOrUrl === "string") res.writeHead(307, { Location: statusOrUrl });
162
- else res.writeHead(statusOrUrl, { Location: url ?? "" });
163
- res.end();
164
- },
165
- getHeaders() {
166
- const headers = { ...resHeaders };
167
- if (setCookieHeaders.length > 0) headers["set-cookie"] = setCookieHeaders;
168
- return headers;
169
- }
170
- };
171
228
  return {
172
229
  req,
173
- res,
230
+ res: new PagesResponseStream(resolveResponse, rejectResponse, options.request.headers),
174
231
  responsePromise
175
232
  };
176
233
  }