vinext 0.1.1 → 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.
- package/README.md +2 -5
- package/dist/build/client-build-config.d.ts +7 -1
- package/dist/build/client-build-config.js +9 -1
- package/dist/check.js +4 -3
- package/dist/client/navigation-runtime.d.ts +3 -2
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +11 -4
- package/dist/config/config-matchers.js +15 -2
- package/dist/config/next-config.d.ts +13 -0
- package/dist/config/next-config.js +2 -0
- package/dist/deploy.js +9 -2
- package/dist/entries/app-rsc-entry.js +7 -1
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +7 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +86 -78
- package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
- package/dist/plugins/dynamic-preload-metadata.js +415 -0
- package/dist/plugins/og-assets.js +2 -2
- package/dist/plugins/optimize-imports.d.ts +8 -4
- package/dist/plugins/optimize-imports.js +16 -12
- package/dist/plugins/sass.d.ts +53 -24
- package/dist/plugins/sass.js +249 -1
- package/dist/plugins/wasm-module-import.d.ts +15 -0
- package/dist/plugins/wasm-module-import.js +50 -0
- package/dist/routing/app-route-graph.d.ts +23 -1
- package/dist/routing/app-route-graph.js +47 -8
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/app-browser-entry.js +108 -213
- package/dist/server/app-browser-error.d.ts +4 -1
- package/dist/server/app-browser-error.js +7 -1
- package/dist/server/app-browser-history-controller.d.ts +104 -0
- package/dist/server/app-browser-history-controller.js +210 -0
- package/dist/server/app-browser-navigation-controller.d.ts +3 -2
- package/dist/server/app-browser-navigation-controller.js +10 -7
- package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
- package/dist/server/app-browser-rsc-redirect.js +30 -8
- package/dist/server/app-browser-state.js +4 -7
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.js +22 -1
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +4 -2
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +8 -0
- package/dist/server/app-page-dispatch.js +18 -5
- package/dist/server/app-page-element-builder.d.ts +22 -2
- package/dist/server/app-page-element-builder.js +37 -8
- package/dist/server/app-page-execution.d.ts +1 -1
- package/dist/server/app-page-execution.js +32 -17
- package/dist/server/app-page-render.d.ts +1 -1
- package/dist/server/app-page-render.js +7 -14
- package/dist/server/app-page-request.d.ts +1 -0
- package/dist/server/app-page-request.js +3 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +8 -7
- package/dist/server/app-page-stream.d.ts +1 -6
- package/dist/server/app-page-stream.js +1 -4
- package/dist/server/app-route-handler-response.js +11 -10
- package/dist/server/app-route-handler-runtime.js +12 -1
- package/dist/server/app-rsc-handler.js +1 -1
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +11 -0
- package/dist/server/app-server-action-execution.js +5 -2
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.js +43 -12
- package/dist/server/headers.d.ts +8 -1
- package/dist/server/headers.js +8 -1
- package/dist/server/instrumentation-runtime.d.ts +6 -0
- package/dist/server/instrumentation-runtime.js +8 -0
- package/dist/server/isr-decision.d.ts +79 -0
- package/dist/server/isr-decision.js +70 -0
- package/dist/server/metadata-route-response.js +5 -3
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +11 -7
- package/dist/server/middleware.js +1 -0
- package/dist/server/navigation-planner.d.ts +62 -1
- package/dist/server/navigation-planner.js +188 -0
- package/dist/server/navigation-trace.d.ts +11 -1
- package/dist/server/navigation-trace.js +11 -1
- package/dist/server/normalize-path.d.ts +0 -8
- package/dist/server/normalize-path.js +3 -1
- package/dist/server/otel-tracer-extension.d.ts +45 -0
- package/dist/server/otel-tracer-extension.js +89 -0
- package/dist/server/pages-api-route.d.ts +14 -3
- package/dist/server/pages-api-route.js +6 -1
- package/dist/server/pages-asset-tags.d.ts +15 -4
- package/dist/server/pages-asset-tags.js +18 -12
- package/dist/server/pages-data-route.js +5 -1
- package/dist/server/pages-node-compat.d.ts +3 -11
- package/dist/server/pages-node-compat.js +174 -121
- package/dist/server/pages-page-data.d.ts +28 -0
- package/dist/server/pages-page-data.js +61 -17
- package/dist/server/pages-page-handler.d.ts +1 -0
- package/dist/server/pages-page-handler.js +22 -6
- package/dist/server/pages-page-response.d.ts +45 -1
- package/dist/server/pages-page-response.js +66 -5
- package/dist/server/pages-readiness.d.ts +1 -1
- package/dist/server/pages-request-pipeline.d.ts +15 -1
- package/dist/server/pages-request-pipeline.js +23 -2
- package/dist/server/prod-server.d.ts +39 -1
- package/dist/server/prod-server.js +98 -34
- package/dist/shims/cache-runtime.js +9 -2
- package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
- package/dist/shims/dynamic-preload-chunks.js +77 -0
- package/dist/shims/dynamic.d.ts +4 -0
- package/dist/shims/dynamic.js +4 -2
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +9 -1
- package/dist/shims/fetch-cache.js +11 -1
- package/dist/shims/head.js +6 -1
- package/dist/shims/headers.d.ts +16 -2
- package/dist/shims/headers.js +37 -1
- package/dist/shims/image-config.js +7 -1
- package/dist/shims/internal/app-route-detection.d.ts +6 -3
- package/dist/shims/internal/app-route-detection.js +10 -6
- package/dist/shims/internal/app-router-context.d.ts +5 -0
- package/dist/shims/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +7 -16
- package/dist/shims/navigation.js +33 -16
- package/dist/shims/router.js +28 -1
- package/dist/shims/script-nonce-context.d.ts +1 -1
- package/dist/shims/script-nonce-context.js +11 -3
- package/dist/shims/server.d.ts +17 -1
- package/dist/shims/server.js +31 -6
- package/dist/shims/slot.js +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -0
- package/dist/utils/client-build-manifest.js +15 -5
- package/dist/utils/client-runtime-metadata.d.ts +45 -0
- package/dist/utils/client-runtime-metadata.js +63 -0
- package/dist/utils/hash.d.ts +17 -1
- package/dist/utils/hash.js +36 -1
- package/dist/utils/lazy-chunks.d.ts +27 -1
- package/dist/utils/lazy-chunks.js +65 -1
- package/dist/utils/manifest-paths.d.ts +20 -2
- package/dist/utils/manifest-paths.js +38 -3
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +5 -1
- package/package.json +2 -2
|
@@ -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
|
|
37
|
-
*
|
|
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
|
-
|
|
48
|
+
const file = files[i];
|
|
45
49
|
if (!file || !file.endsWith(".js")) continue;
|
|
46
|
-
|
|
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=\"
|
|
76
|
-
tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"
|
|
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=\"
|
|
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=\"
|
|
109
|
-
tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"
|
|
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
|
|
96
|
+
headers
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
/**
|
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import { PagesBodyParseError } from "./pages-media-type.js";
|
|
2
2
|
import { RevalidateOptions } from "./pages-revalidate.js";
|
|
3
|
+
import { Readable, Writable } from "node:stream";
|
|
3
4
|
|
|
4
5
|
//#region src/server/pages-node-compat.d.ts
|
|
5
6
|
type PagesRequestQuery = Record<string, string | string[]>;
|
|
6
|
-
type PagesReqResRequest = {
|
|
7
|
+
type PagesReqResRequest = Readable & {
|
|
7
8
|
method: string;
|
|
8
9
|
url: string;
|
|
9
10
|
headers: Record<string, string>;
|
|
10
11
|
query: PagesRequestQuery;
|
|
11
12
|
body: unknown;
|
|
12
13
|
cookies: Record<string, string>;
|
|
13
|
-
/**
|
|
14
|
-
* Async-iterator hook so handlers can `for await (const chunk of req)` —
|
|
15
|
-
* matching Node's `IncomingMessage` contract. Critical for the
|
|
16
|
-
* `bodyParser: false` opt-out (webhook signature verification etc.) where
|
|
17
|
-
* `req.body` is left undefined and user code is expected to drain the raw
|
|
18
|
-
* stream off `req` itself.
|
|
19
|
-
*/
|
|
20
|
-
[Symbol.asyncIterator]: () => AsyncIterator<Uint8Array>;
|
|
21
14
|
};
|
|
22
15
|
type PagesReqResHeaders = {
|
|
23
16
|
[key: string]: string | number | boolean | string[];
|
|
24
17
|
};
|
|
25
|
-
type PagesReqResResponse = {
|
|
18
|
+
type PagesReqResResponse = Writable & {
|
|
26
19
|
statusCode: number;
|
|
27
20
|
readonly headersSent: boolean;
|
|
28
21
|
writeHead: (code: number, headers?: PagesReqResHeaders) => PagesReqResResponse;
|
|
29
22
|
setHeader: (name: string, value: string | number | boolean | string[]) => PagesReqResResponse;
|
|
30
23
|
getHeader: (name: string) => string | number | boolean | string[] | undefined;
|
|
31
|
-
end: (data?: BodyInit | null) => void;
|
|
32
24
|
status: (code: number) => PagesReqResResponse;
|
|
33
25
|
json: (data: unknown) => void;
|
|
34
26
|
send: (data: unknown) => void;
|
|
@@ -4,6 +4,7 @@ import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-medi
|
|
|
4
4
|
import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
|
|
5
5
|
import { performOnDemandRevalidate } from "./pages-revalidate.js";
|
|
6
6
|
import { decode } from "node:querystring";
|
|
7
|
+
import { Readable, Writable } from "node:stream";
|
|
7
8
|
//#region src/server/pages-node-compat.ts
|
|
8
9
|
const MAX_PAGES_API_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;
|
|
9
10
|
async function readPagesRequestBodyWithLimit(request, maxBytes) {
|
|
@@ -41,140 +42,192 @@ async function parsePagesApiBody(request, maxBytes = MAX_PAGES_API_BODY_SIZE) {
|
|
|
41
42
|
if (mediaType === "application/x-www-form-urlencoded") return decode(rawBody);
|
|
42
43
|
return rawBody;
|
|
43
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
|
+
};
|
|
44
211
|
function createPagesReqRes(options) {
|
|
45
212
|
const headersObj = {};
|
|
46
213
|
for (const [key, value] of options.request.headers) headersObj[key.toLowerCase()] = value;
|
|
47
|
-
const
|
|
48
|
-
const reqAsyncIterator = () => {
|
|
49
|
-
if (!requestBody) return { next() {
|
|
50
|
-
return Promise.resolve({
|
|
51
|
-
value: void 0,
|
|
52
|
-
done: true
|
|
53
|
-
});
|
|
54
|
-
} };
|
|
55
|
-
const reader = requestBody.getReader();
|
|
56
|
-
return {
|
|
57
|
-
async next() {
|
|
58
|
-
const { value, done } = await reader.read();
|
|
59
|
-
if (done) return {
|
|
60
|
-
value: void 0,
|
|
61
|
-
done: true
|
|
62
|
-
};
|
|
63
|
-
return {
|
|
64
|
-
value,
|
|
65
|
-
done: false
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
async return() {
|
|
69
|
-
try {
|
|
70
|
-
await reader.cancel();
|
|
71
|
-
} catch {}
|
|
72
|
-
return {
|
|
73
|
-
value: void 0,
|
|
74
|
-
done: true
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
const req = {
|
|
214
|
+
const req = Object.assign(createRequestReadable(options.request), {
|
|
80
215
|
method: options.request.method,
|
|
81
216
|
url: options.url,
|
|
82
217
|
headers: headersObj,
|
|
83
218
|
query: options.query,
|
|
84
219
|
body: options.body,
|
|
85
|
-
cookies: parseCookies(options.request.headers.get("cookie"))
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
let resStatusCode = 200;
|
|
89
|
-
const resHeaders = {};
|
|
90
|
-
const setCookieHeaders = [];
|
|
91
|
-
let resBody = null;
|
|
92
|
-
let ended = false;
|
|
220
|
+
cookies: parseCookies(options.request.headers.get("cookie"))
|
|
221
|
+
});
|
|
93
222
|
let resolveResponse;
|
|
94
|
-
|
|
223
|
+
let rejectResponse;
|
|
224
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
95
225
|
resolveResponse = resolve;
|
|
226
|
+
rejectResponse = reject;
|
|
96
227
|
});
|
|
97
|
-
const res = {
|
|
98
|
-
get statusCode() {
|
|
99
|
-
return resStatusCode;
|
|
100
|
-
},
|
|
101
|
-
set statusCode(code) {
|
|
102
|
-
resStatusCode = code;
|
|
103
|
-
},
|
|
104
|
-
get headersSent() {
|
|
105
|
-
return ended;
|
|
106
|
-
},
|
|
107
|
-
writeHead(code, headers) {
|
|
108
|
-
resStatusCode = code;
|
|
109
|
-
if (headers) for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === "set-cookie") if (Array.isArray(value)) setCookieHeaders.push(...value.map(String));
|
|
110
|
-
else setCookieHeaders.push(String(value));
|
|
111
|
-
else resHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
112
|
-
return res;
|
|
113
|
-
},
|
|
114
|
-
setHeader(name, value) {
|
|
115
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
116
|
-
setCookieHeaders.length = 0;
|
|
117
|
-
if (Array.isArray(value)) setCookieHeaders.push(...value.map(String));
|
|
118
|
-
else setCookieHeaders.push(String(value));
|
|
119
|
-
} else resHeaders[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
120
|
-
return res;
|
|
121
|
-
},
|
|
122
|
-
getHeader(name) {
|
|
123
|
-
if (name.toLowerCase() === "set-cookie") return setCookieHeaders.length > 0 ? setCookieHeaders : void 0;
|
|
124
|
-
return resHeaders[name.toLowerCase()];
|
|
125
|
-
},
|
|
126
|
-
end(data) {
|
|
127
|
-
if (ended) return;
|
|
128
|
-
ended = true;
|
|
129
|
-
if (data !== void 0 && data !== null) resBody = data;
|
|
130
|
-
const headers = new Headers();
|
|
131
|
-
for (const [key, value] of Object.entries(resHeaders)) headers.set(key, String(value));
|
|
132
|
-
for (const cookie of setCookieHeaders) headers.append("set-cookie", cookie);
|
|
133
|
-
resolveResponse(new Response(resBody, {
|
|
134
|
-
status: resStatusCode,
|
|
135
|
-
headers
|
|
136
|
-
}));
|
|
137
|
-
},
|
|
138
|
-
status(code) {
|
|
139
|
-
resStatusCode = code;
|
|
140
|
-
return res;
|
|
141
|
-
},
|
|
142
|
-
json(data) {
|
|
143
|
-
resHeaders["content-type"] = "application/json";
|
|
144
|
-
res.end(JSON.stringify(data));
|
|
145
|
-
},
|
|
146
|
-
send(data) {
|
|
147
|
-
if (Buffer.isBuffer(data)) {
|
|
148
|
-
if (!resHeaders["content-type"]) resHeaders["content-type"] = "application/octet-stream";
|
|
149
|
-
resHeaders["content-length"] = String(data.length);
|
|
150
|
-
res.end(new Uint8Array(data));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (typeof data === "object" && data !== null) {
|
|
154
|
-
resHeaders["content-type"] = "application/json";
|
|
155
|
-
res.end(JSON.stringify(data));
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (!resHeaders["content-type"]) resHeaders["content-type"] = "text/plain";
|
|
159
|
-
res.end(String(data));
|
|
160
|
-
},
|
|
161
|
-
redirect(statusOrUrl, url) {
|
|
162
|
-
if (typeof statusOrUrl === "string") res.writeHead(307, { Location: statusOrUrl });
|
|
163
|
-
else res.writeHead(statusOrUrl, { Location: url ?? "" });
|
|
164
|
-
res.end();
|
|
165
|
-
},
|
|
166
|
-
getHeaders() {
|
|
167
|
-
const headers = { ...resHeaders };
|
|
168
|
-
if (setCookieHeaders.length > 0) headers["set-cookie"] = setCookieHeaders;
|
|
169
|
-
return headers;
|
|
170
|
-
},
|
|
171
|
-
async revalidate(urlPath, opts) {
|
|
172
|
-
await performOnDemandRevalidate(options.request.headers, urlPath, opts);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
228
|
return {
|
|
176
229
|
req,
|
|
177
|
-
res,
|
|
230
|
+
res: new PagesResponseStream(resolveResponse, rejectResponse, options.request.headers),
|
|
178
231
|
responsePromise
|
|
179
232
|
};
|
|
180
233
|
}
|
|
@@ -138,6 +138,13 @@ type ResolvePagesPageDataOptions = {
|
|
|
138
138
|
* presence — see the security note in `isr-cache.ts`.
|
|
139
139
|
*/
|
|
140
140
|
isOnDemandRevalidate?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* The deployment ID used for deployment-skew protection. When set, it is
|
|
143
|
+
* included as `x-nextjs-deployment-id` on all `_next/data` responses
|
|
144
|
+
* (success, redirect, notFound). Mirrors Next.js pages-handler.ts behavior.
|
|
145
|
+
* Typically sourced from `process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID`.
|
|
146
|
+
*/
|
|
147
|
+
deploymentId?: string;
|
|
141
148
|
pageModule: PagesPageModule;
|
|
142
149
|
params: Record<string, unknown>;
|
|
143
150
|
query: Record<string, unknown>;
|
|
@@ -158,6 +165,27 @@ type ResolvePagesPageDataOptions = {
|
|
|
158
165
|
renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
|
|
159
166
|
vinext?: VinextNextData["__vinext"];
|
|
160
167
|
nextData?: PagesNextDataExtras;
|
|
168
|
+
/**
|
|
169
|
+
* The request's User-Agent string. When this matches a known crawler/bot
|
|
170
|
+
* pattern, ISR cache-HIT and cache-STALE responses receive an ETag header
|
|
171
|
+
* for consistency with the fresh-MISS path (which also attaches an ETag for
|
|
172
|
+
* bot UAs via `renderPagesPageResponse`). See the divergence note in
|
|
173
|
+
* `pages-page-response.ts` for why UA-gating is used instead of Next.js's
|
|
174
|
+
* `isDynamic` check.
|
|
175
|
+
*/
|
|
176
|
+
userAgent?: string;
|
|
177
|
+
/**
|
|
178
|
+
* The incoming request's `If-None-Match` header value. When the cached HTML
|
|
179
|
+
* ETag matches (weak-ETag semantics), the ISR cache-HIT or cache-STALE
|
|
180
|
+
* response is a `304 Not Modified` with no body.
|
|
181
|
+
*/
|
|
182
|
+
ifNoneMatch?: string;
|
|
183
|
+
/**
|
|
184
|
+
* The incoming request's `Cache-Control` header value. When it contains
|
|
185
|
+
* `no-cache`, the 304 short-circuit is skipped and a full response is
|
|
186
|
+
* returned — mirroring the `fresh` package used by Next.js.
|
|
187
|
+
*/
|
|
188
|
+
requestCacheControl?: string;
|
|
161
189
|
};
|
|
162
190
|
type ResolvePagesPageDataRenderResult = {
|
|
163
191
|
kind: "render";
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
+
import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
|
|
1
2
|
import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
2
3
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
4
|
+
import { applyCdnResponseHeaders } from "./cache-control.js";
|
|
5
|
+
import { decideIsr } from "./isr-decision.js";
|
|
3
6
|
import { buildPagesCacheValue } from "./isr-cache.js";
|
|
4
7
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
5
8
|
import { hasPagesGetInitialProps, isResponseSent, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
|
|
6
|
-
import { applyCdnResponseHeaders, buildCachedRevalidateCacheControl } from "./cache-control.js";
|
|
7
9
|
import { buildNextDataJsonResponse } from "./pages-data-route.js";
|
|
8
|
-
import { buildPagesNextDataScript } from "./pages-page-response.js";
|
|
10
|
+
import { buildPagesNextDataScript, etagMatches, generatePagesETag, isPagesStreamingBot, requestsNoCache } from "./pages-page-response.js";
|
|
9
11
|
//#region src/server/pages-page-data.ts
|
|
10
|
-
function buildPagesDataNotFoundResponse() {
|
|
12
|
+
function buildPagesDataNotFoundResponse(deploymentId) {
|
|
13
|
+
const headers = { "Content-Type": "application/json" };
|
|
14
|
+
if (deploymentId) headers[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
11
15
|
return new Response("{}", {
|
|
12
16
|
status: 404,
|
|
13
|
-
headers
|
|
17
|
+
headers
|
|
14
18
|
});
|
|
15
19
|
}
|
|
16
20
|
function buildPagesNotFoundResult(options) {
|
|
17
21
|
if (options.isDataReq) return {
|
|
18
22
|
kind: "response",
|
|
19
|
-
response: buildPagesDataNotFoundResponse()
|
|
23
|
+
response: buildPagesDataNotFoundResponse(options.deploymentId)
|
|
20
24
|
};
|
|
21
25
|
return { kind: "notFound" };
|
|
22
26
|
}
|
|
@@ -46,10 +50,14 @@ function resolvePagesRedirectStatus(redirect) {
|
|
|
46
50
|
*/
|
|
47
51
|
function buildPagesRedirectResponse(redirect, options) {
|
|
48
52
|
const destination = options.sanitizeDestination(redirect.destination);
|
|
49
|
-
if (options.isDataReq)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
if (options.isDataReq) {
|
|
54
|
+
const init = { headers: {} };
|
|
55
|
+
if (options.deploymentId) init.headers[NEXTJS_DEPLOYMENT_ID_HEADER] = options.deploymentId;
|
|
56
|
+
return buildNextDataJsonResponse({
|
|
57
|
+
__N_REDIRECT: destination,
|
|
58
|
+
__N_REDIRECT_STATUS: resolvePagesRedirectStatus(redirect)
|
|
59
|
+
}, options.safeJsonStringify, init);
|
|
60
|
+
}
|
|
53
61
|
return new Response(null, {
|
|
54
62
|
status: resolvePagesRedirectStatus(redirect),
|
|
55
63
|
headers: { Location: destination }
|
|
@@ -81,19 +89,47 @@ function matchesPagesStaticPath(pathEntry, params, routeUrl) {
|
|
|
81
89
|
});
|
|
82
90
|
}
|
|
83
91
|
function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSeconds, expireSeconds, cacheControl, status) {
|
|
84
|
-
const
|
|
85
|
-
|
|
92
|
+
const { cacheControl: cacheControlHeader } = decideIsr({
|
|
93
|
+
cacheState,
|
|
94
|
+
kind: "pages",
|
|
95
|
+
revalidateSeconds: revalidateSeconds ?? 60,
|
|
96
|
+
expireSeconds,
|
|
97
|
+
cacheControlMeta: cacheControl
|
|
98
|
+
});
|
|
86
99
|
const headers = new Headers({
|
|
87
100
|
"Content-Type": "text/html",
|
|
88
101
|
...buildCacheStateHeaders(cacheState)
|
|
89
102
|
});
|
|
90
|
-
applyCdnResponseHeaders(headers, { cacheControl:
|
|
103
|
+
applyCdnResponseHeaders(headers, { cacheControl: cacheControlHeader });
|
|
91
104
|
if (fontLinkHeader) headers.set("Link", fontLinkHeader);
|
|
92
105
|
return new Response(html, {
|
|
93
106
|
status: status ?? 200,
|
|
94
107
|
headers
|
|
95
108
|
});
|
|
96
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* For bot / crawler UAs, attach an ETag to a cached ISR response (HIT or
|
|
112
|
+
* STALE) so it is consistent with the fresh-MISS path, then check for a
|
|
113
|
+
* matching `If-None-Match`. When the check passes — and the request did NOT
|
|
114
|
+
* carry `Cache-Control: no-cache` — returns a 304 response; otherwise returns
|
|
115
|
+
* `null` so the caller can return the full response.
|
|
116
|
+
*
|
|
117
|
+
* Extracted to avoid duplicating the same three-line block across the HIT and
|
|
118
|
+
* STALE branches.
|
|
119
|
+
*/
|
|
120
|
+
function applyBotETagAndCheck(cachedResponse, html, options) {
|
|
121
|
+
if (!options.userAgent || !isPagesStreamingBot(options.userAgent)) return null;
|
|
122
|
+
const etag = generatePagesETag(html);
|
|
123
|
+
cachedResponse.headers.set("ETag", etag);
|
|
124
|
+
if (!requestsNoCache(options.requestCacheControl) && options.ifNoneMatch && etagMatches(etag, options.ifNoneMatch)) return {
|
|
125
|
+
kind: "response",
|
|
126
|
+
response: new Response(null, {
|
|
127
|
+
status: 304,
|
|
128
|
+
headers: cachedResponse.headers
|
|
129
|
+
})
|
|
130
|
+
};
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
97
133
|
function rewritePagesCachedHtml(cachedHtml, freshBody, nextDataScript) {
|
|
98
134
|
const bodyStart = cachedHtml.indexOf("<div id=\"__next\">");
|
|
99
135
|
const contentStart = bodyStart >= 0 ? bodyStart + 17 : -1;
|
|
@@ -175,10 +211,15 @@ async function resolvePagesPageData(options) {
|
|
|
175
211
|
const cacheKey = options.isrCacheKey("pages", pathname);
|
|
176
212
|
const cached = await options.isrGet(cacheKey);
|
|
177
213
|
const cachedValue = cached?.value.value;
|
|
178
|
-
if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
214
|
+
if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) {
|
|
215
|
+
const hitResponse = buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
|
|
216
|
+
const hitBotResult = applyBotETagAndCheck(hitResponse, cachedValue.html, options);
|
|
217
|
+
if (hitBotResult) return hitBotResult;
|
|
218
|
+
return {
|
|
219
|
+
kind: "response",
|
|
220
|
+
response: hitResponse
|
|
221
|
+
};
|
|
222
|
+
}
|
|
182
223
|
if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
|
|
183
224
|
options.triggerBackgroundRegeneration(cacheKey, async function() {
|
|
184
225
|
return options.runInFreshUnifiedContext(async () => {
|
|
@@ -212,9 +253,12 @@ async function resolvePagesPageData(options) {
|
|
|
212
253
|
routePath: options.routePattern,
|
|
213
254
|
routeType: "render"
|
|
214
255
|
});
|
|
256
|
+
const staleResponse = buildPagesCacheResponse(cachedValue.html, "STALE", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
|
|
257
|
+
const staleBotResult = applyBotETagAndCheck(staleResponse, cachedValue.html, options);
|
|
258
|
+
if (staleBotResult) return staleBotResult;
|
|
215
259
|
return {
|
|
216
260
|
kind: "response",
|
|
217
|
-
response:
|
|
261
|
+
response: staleResponse
|
|
218
262
|
};
|
|
219
263
|
}
|
|
220
264
|
const result = await options.pageModule.getStaticProps({
|