vinext 0.0.38 → 0.0.40
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 +33 -20
- package/dist/build/nitro-route-rules.d.ts +50 -0
- package/dist/build/nitro-route-rules.js +81 -0
- package/dist/build/nitro-route-rules.js.map +1 -0
- package/dist/build/precompress.d.ts +17 -0
- package/dist/build/precompress.js +102 -0
- package/dist/build/precompress.js.map +1 -0
- package/dist/build/prerender.d.ts +27 -22
- package/dist/build/prerender.js +17 -17
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/report.d.ts +3 -4
- package/dist/build/report.js.map +1 -1
- package/dist/build/run-prerender.d.ts +3 -4
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/standalone.d.ts +32 -0
- package/dist/build/standalone.js +206 -0
- package/dist/build/standalone.js.map +1 -0
- package/dist/build/static-export.d.ts +17 -29
- package/dist/build/static-export.js.map +1 -1
- package/dist/check.d.ts +4 -4
- package/dist/check.js +1 -1
- package/dist/check.js.map +1 -1
- package/dist/cli.js +31 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/instrumentation-client.d.ts +2 -2
- package/dist/client/instrumentation-client.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +5 -8
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/kv-cache-handler.d.ts +5 -3
- package/dist/cloudflare/kv-cache-handler.js +1 -1
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/cloudflare/tpr.d.ts +35 -27
- package/dist/cloudflare/tpr.js +36 -12
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.d.ts +2 -2
- package/dist/config/config-matchers.js +1 -1
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/dotenv.d.ts +4 -4
- package/dist/config/dotenv.js.map +1 -1
- package/dist/config/next-config.d.ts +40 -61
- package/dist/config/next-config.js +5 -4
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts +25 -41
- package/dist/deploy.js +1 -1
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +8 -11
- package/dist/entries/app-rsc-entry.js +133 -249
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +1 -3
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +49 -28
- package/dist/index.js +238 -83
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +14 -26
- package/dist/init.js +8 -2
- package/dist/init.js.map +1 -1
- package/dist/plugins/client-reference-dedup.js.map +1 -1
- package/dist/plugins/fix-use-server-closure-collision.js.map +1 -1
- package/dist/plugins/fonts.d.ts +18 -1
- package/dist/plugins/fonts.js +107 -8
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/optimize-imports.d.ts +2 -2
- package/dist/plugins/optimize-imports.js +4 -4
- package/dist/plugins/optimize-imports.js.map +1 -1
- package/dist/plugins/server-externals-manifest.d.ts +37 -0
- package/dist/plugins/server-externals-manifest.js +83 -0
- package/dist/plugins/server-externals-manifest.js.map +1 -0
- package/dist/routing/app-router.d.ts +37 -55
- package/dist/routing/app-router.js +37 -22
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +2 -2
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +6 -11
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.d.ts +2 -2
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-entry.js +270 -39
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-stream.d.ts +6 -6
- package/dist/server/app-browser-stream.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +8 -8
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +13 -11
- package/dist/server/app-page-boundary.js +1 -1
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +10 -10
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +10 -10
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +2 -2
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render.d.ts +4 -4
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +12 -12
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.d.ts +30 -19
- package/dist/server/app-page-response.js +26 -7
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +79 -0
- package/dist/server/app-page-route-wiring.js +165 -0
- package/dist/server/app-page-route-wiring.js.map +1 -0
- package/dist/server/app-page-stream.d.ts +18 -18
- package/dist/server/app-page-stream.js +3 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-cache.d.ts +2 -2
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +6 -6
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-policy.d.ts +8 -8
- package/dist/server/app-route-handler-policy.js.map +1 -1
- package/dist/server/app-route-handler-response.d.ts +6 -6
- package/dist/server/app-route-handler-response.js +4 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-route-handler-runtime.d.ts +4 -4
- package/dist/server/app-route-handler-runtime.js.map +1 -1
- package/dist/server/app-router-entry.d.ts +6 -1
- package/dist/server/app-router-entry.js +9 -2
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +4 -4
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +2 -2
- package/dist/server/app-ssr-stream.js +1 -3
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/dev-module-runner.d.ts +2 -2
- package/dist/server/dev-module-runner.js.map +1 -1
- package/dist/server/dev-server.js +5 -7
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts +7 -12
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation.d.ts +8 -12
- package/dist/server/instrumentation.js +1 -1
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/isr-cache.d.ts +2 -2
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +14 -19
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware.d.ts +9 -17
- package/dist/server/middleware.js +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +6 -6
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-i18n.d.ts +4 -4
- package/dist/server/pages-i18n.js.map +1 -1
- package/dist/server/pages-node-compat.d.ts +10 -10
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +22 -22
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +8 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +20 -15
- package/dist/server/prod-server.js +198 -55
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/static-file-cache.d.ts +57 -0
- package/dist/server/static-file-cache.js +219 -0
- package/dist/server/static-file-cache.js.map +1 -0
- package/dist/server/worker-utils.d.ts +4 -1
- package/dist/server/worker-utils.js +31 -1
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/app.d.ts +2 -2
- package/dist/shims/cache-runtime.d.ts +6 -9
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +28 -31
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/config.d.ts +2 -2
- package/dist/shims/config.js.map +1 -1
- package/dist/shims/dynamic.d.ts +2 -2
- package/dist/shims/dynamic.js +5 -7
- package/dist/shims/dynamic.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +19 -10
- package/dist/shims/error-boundary.js +23 -3
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/error.d.ts +2 -2
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +4 -4
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/font-google-base.d.ts +4 -4
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-local.d.ts +6 -6
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/form.d.ts +4 -8
- package/dist/shims/form.js +4 -6
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/head-state.d.ts +2 -2
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +2 -2
- package/dist/shims/head.js +18 -20
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +4 -4
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-context.d.ts +2 -2
- package/dist/shims/i18n-context.js.map +1 -1
- package/dist/shims/i18n-state.d.ts +2 -2
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/image-config.d.ts +2 -2
- package/dist/shims/image-config.js.map +1 -1
- package/dist/shims/image.d.ts +5 -6
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/app-router-context.d.ts +6 -6
- package/dist/shims/internal/app-router-context.js.map +1 -1
- package/dist/shims/internal/utils.d.ts +2 -2
- package/dist/shims/internal/utils.js.map +1 -1
- package/dist/shims/layout-segment-context.d.ts +12 -5
- package/dist/shims/layout-segment-context.js +9 -4
- package/dist/shims/layout-segment-context.js.map +1 -1
- package/dist/shims/legacy-image.d.ts +5 -8
- package/dist/shims/legacy-image.js.map +1 -1
- package/dist/shims/link.d.ts +21 -31
- package/dist/shims/link.js +4 -58
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +23 -31
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +2 -2
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +118 -18
- package/dist/shims/navigation.js +377 -116
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/request-context.d.ts +2 -2
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/router-state.d.ts +4 -4
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +28 -47
- package/dist/shims/router.js +127 -38
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +16 -31
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts +27 -14
- package/dist/shims/server.js +91 -73
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +28 -0
- package/dist/shims/slot.js +49 -0
- package/dist/shims/slot.js.map +1 -0
- package/dist/shims/unified-request-context.d.ts +3 -5
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/shims/web-vitals.d.ts +2 -2
- package/dist/shims/web-vitals.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +34 -0
- package/dist/utils/lazy-chunks.js +50 -0
- package/dist/utils/lazy-chunks.js.map +1 -0
- package/dist/utils/vinext-root.d.ts +24 -0
- package/dist/utils/vinext-root.js +31 -0
- package/dist/utils/vinext-root.js.map +1 -0
- package/package.json +1 -2
|
@@ -3,16 +3,18 @@ import { applyMiddlewareRequestHeaders, isExternalUrl, matchHeaders, matchRedire
|
|
|
3
3
|
import { normalizePath } from "./normalize-path.js";
|
|
4
4
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
5
5
|
import { manifestFileWithBase } from "../utils/manifest-paths.js";
|
|
6
|
+
import { CONTENT_TYPES, StaticFileCache, etagFromFilenameHash } from "./static-file-cache.js";
|
|
6
7
|
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
|
|
8
|
+
import { computeLazyChunks } from "../utils/lazy-chunks.js";
|
|
7
9
|
import { readPrerenderSecret } from "../build/server-manifest.js";
|
|
8
10
|
import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
|
|
9
|
-
import { computeLazyChunks } from "../index.js";
|
|
10
11
|
import fs from "node:fs";
|
|
11
12
|
import path from "node:path";
|
|
13
|
+
import fsp from "node:fs/promises";
|
|
12
14
|
import { pathToFileURL } from "node:url";
|
|
15
|
+
import zlib from "node:zlib";
|
|
13
16
|
import { createServer } from "node:http";
|
|
14
17
|
import { Readable, pipeline } from "node:stream";
|
|
15
|
-
import zlib from "node:zlib";
|
|
16
18
|
//#region src/server/prod-server.ts
|
|
17
19
|
/**
|
|
18
20
|
* Production server for vinext.
|
|
@@ -21,7 +23,7 @@ import zlib from "node:zlib";
|
|
|
21
23
|
* - Static asset serving from client build output
|
|
22
24
|
* - Pages Router: SSR rendering + API route handling
|
|
23
25
|
* - App Router: RSC/SSR rendering, route handlers, server actions
|
|
24
|
-
* -
|
|
26
|
+
* - Zstd/Brotli/Gzip compression for text-based responses
|
|
25
27
|
* - Streaming SSR for App Router
|
|
26
28
|
*
|
|
27
29
|
* Build output for Pages Router:
|
|
@@ -62,12 +64,18 @@ const COMPRESSIBLE_TYPES = new Set([
|
|
|
62
64
|
const COMPRESS_THRESHOLD = 1024;
|
|
63
65
|
/**
|
|
64
66
|
* Parse the Accept-Encoding header and return the best supported encoding.
|
|
65
|
-
* Preference order: br > gzip > deflate > identity.
|
|
67
|
+
* Preference order: zstd > br > gzip > deflate > identity.
|
|
68
|
+
*
|
|
69
|
+
* zstd decompresses ~3-5x faster than brotli at similar compression ratios.
|
|
70
|
+
* Supported in Chrome 123+, Firefox 126+. Safari can decompress but doesn't
|
|
71
|
+
* send zstd in Accept-Encoding, so it transparently falls back to br/gzip.
|
|
66
72
|
*/
|
|
73
|
+
const HAS_ZSTD = typeof zlib.createZstdCompress === "function";
|
|
67
74
|
function negotiateEncoding(req) {
|
|
68
75
|
const accept = req.headers["accept-encoding"];
|
|
69
76
|
if (!accept || typeof accept !== "string") return null;
|
|
70
77
|
const lower = accept.toLowerCase();
|
|
78
|
+
if (HAS_ZSTD && lower.includes("zstd")) return "zstd";
|
|
71
79
|
if (lower.includes("br")) return "br";
|
|
72
80
|
if (lower.includes("gzip")) return "gzip";
|
|
73
81
|
if (lower.includes("deflate")) return "deflate";
|
|
@@ -78,6 +86,10 @@ function negotiateEncoding(req) {
|
|
|
78
86
|
*/
|
|
79
87
|
function createCompressor(encoding, mode = "default") {
|
|
80
88
|
switch (encoding) {
|
|
89
|
+
case "zstd": return zlib.createZstdCompress({
|
|
90
|
+
...mode === "streaming" ? { flush: zlib.constants.ZSTD_e_flush } : {},
|
|
91
|
+
params: { [zlib.constants.ZSTD_c_compressionLevel]: 3 }
|
|
92
|
+
});
|
|
81
93
|
case "br": return zlib.createBrotliCompress({
|
|
82
94
|
...mode === "streaming" ? { flush: zlib.constants.BROTLI_OPERATION_FLUSH } : {},
|
|
83
95
|
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 }
|
|
@@ -134,6 +146,11 @@ function omitHeadersCaseInsensitive(headersRecord, names) {
|
|
|
134
146
|
}
|
|
135
147
|
return filtered;
|
|
136
148
|
}
|
|
149
|
+
function matchesIfNoneMatchHeader(ifNoneMatch, etag) {
|
|
150
|
+
if (!ifNoneMatch) return false;
|
|
151
|
+
if (ifNoneMatch === "*") return true;
|
|
152
|
+
return ifNoneMatch.split(",").map((value) => value.trim()).some((value) => value === etag);
|
|
153
|
+
}
|
|
137
154
|
function stripHeaders(headersRecord, names) {
|
|
138
155
|
const targets = new Set(names.map((name) => name.toLowerCase()));
|
|
139
156
|
for (const key of Object.keys(headersRecord)) if (targets.has(key.toLowerCase())) delete headersRecord[key];
|
|
@@ -220,33 +237,67 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
220
237
|
res.end(buf);
|
|
221
238
|
}
|
|
222
239
|
}
|
|
223
|
-
/** Content-type lookup for static assets. */
|
|
224
|
-
const CONTENT_TYPES = {
|
|
225
|
-
".js": "application/javascript",
|
|
226
|
-
".mjs": "application/javascript",
|
|
227
|
-
".css": "text/css",
|
|
228
|
-
".html": "text/html",
|
|
229
|
-
".json": "application/json",
|
|
230
|
-
".png": "image/png",
|
|
231
|
-
".jpg": "image/jpeg",
|
|
232
|
-
".jpeg": "image/jpeg",
|
|
233
|
-
".gif": "image/gif",
|
|
234
|
-
".svg": "image/svg+xml",
|
|
235
|
-
".ico": "image/x-icon",
|
|
236
|
-
".woff": "font/woff",
|
|
237
|
-
".woff2": "font/woff2",
|
|
238
|
-
".ttf": "font/ttf",
|
|
239
|
-
".eot": "application/vnd.ms-fontobject",
|
|
240
|
-
".webp": "image/webp",
|
|
241
|
-
".avif": "image/avif",
|
|
242
|
-
".map": "application/json",
|
|
243
|
-
".rsc": "text/x-component"
|
|
244
|
-
};
|
|
245
240
|
/**
|
|
246
241
|
* Try to serve a static file from the client build directory.
|
|
247
|
-
*
|
|
242
|
+
*
|
|
243
|
+
* When a `StaticFileCache` is provided, lookups are pure in-memory Map.get()
|
|
244
|
+
* with zero filesystem calls. Precompressed .br/.gz/.zst variants (generated at
|
|
245
|
+
* build time) are served directly — no per-request compression needed for
|
|
246
|
+
* hashed assets.
|
|
247
|
+
*
|
|
248
|
+
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
249
|
+
* unlike the old sync existsSync/statSync approach).
|
|
248
250
|
*/
|
|
249
|
-
function tryServeStatic(req, res, clientDir, pathname, compress, extraHeaders) {
|
|
251
|
+
async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode) {
|
|
252
|
+
if (pathname === "/") return false;
|
|
253
|
+
const responseStatus = statusCode ?? 200;
|
|
254
|
+
const omitBody = isNoBodyResponseStatus(responseStatus);
|
|
255
|
+
if (cache) {
|
|
256
|
+
let lookupPath;
|
|
257
|
+
if (pathname.includes("%")) {
|
|
258
|
+
try {
|
|
259
|
+
lookupPath = decodeURIComponent(pathname);
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (lookupPath.startsWith("/.vite/") || lookupPath === "/.vite") return false;
|
|
264
|
+
} else {
|
|
265
|
+
if (pathname.startsWith("/.vite/") || pathname === "/.vite") return false;
|
|
266
|
+
lookupPath = pathname;
|
|
267
|
+
}
|
|
268
|
+
const entry = cache.lookup(lookupPath);
|
|
269
|
+
if (!entry) return false;
|
|
270
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
271
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
|
|
272
|
+
if (extraHeaders) res.writeHead(304, {
|
|
273
|
+
...entry.notModifiedHeaders,
|
|
274
|
+
...extraHeaders
|
|
275
|
+
});
|
|
276
|
+
else res.writeHead(304, entry.notModifiedHeaders);
|
|
277
|
+
res.end();
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
const rawAe = compress ? req.headers["accept-encoding"] : void 0;
|
|
281
|
+
const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
|
|
282
|
+
const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
|
|
283
|
+
if (extraHeaders) res.writeHead(responseStatus, {
|
|
284
|
+
...variant.headers,
|
|
285
|
+
...extraHeaders
|
|
286
|
+
});
|
|
287
|
+
else res.writeHead(responseStatus, variant.headers);
|
|
288
|
+
if (omitBody || req.method === "HEAD") {
|
|
289
|
+
res.end();
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (variant.buffer) res.end(variant.buffer);
|
|
293
|
+
else pipeline(fs.createReadStream(variant.path), res, (err) => {
|
|
294
|
+
if (err) {
|
|
295
|
+
console.warn(`[vinext] Static file stream error for ${variant.path}:`, err.message);
|
|
296
|
+
res.destroy(err);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
250
301
|
const resolvedClient = path.resolve(clientDir);
|
|
251
302
|
let decodedPathname;
|
|
252
303
|
try {
|
|
@@ -257,44 +308,110 @@ function tryServeStatic(req, res, clientDir, pathname, compress, extraHeaders) {
|
|
|
257
308
|
if (decodedPathname.startsWith("/.vite/") || decodedPathname === "/.vite") return false;
|
|
258
309
|
const staticFile = path.resolve(clientDir, "." + decodedPathname);
|
|
259
310
|
if (!staticFile.startsWith(resolvedClient + path.sep) && staticFile !== resolvedClient) return false;
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
311
|
+
const resolved = await resolveStaticFile(staticFile);
|
|
312
|
+
if (!resolved) return false;
|
|
313
|
+
const ext = path.extname(resolved.path);
|
|
314
|
+
const ct = CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
315
|
+
const isHashed = pathname.startsWith("/assets/");
|
|
316
|
+
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
|
|
317
|
+
const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
|
|
318
|
+
const baseType = ct.split(";")[0].trim();
|
|
319
|
+
const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
|
|
320
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
321
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
322
|
+
const notModifiedHeaders = {
|
|
323
|
+
ETag: etag,
|
|
324
|
+
"Cache-Control": cacheControl,
|
|
325
|
+
...isCompressible ? { Vary: "Accept-Encoding" } : void 0,
|
|
326
|
+
...extraHeaders
|
|
327
|
+
};
|
|
328
|
+
res.writeHead(304, notModifiedHeaders);
|
|
329
|
+
res.end();
|
|
330
|
+
return true;
|
|
270
331
|
}
|
|
271
|
-
const ct = CONTENT_TYPES[path.extname(resolvedStaticFile)] ?? "application/octet-stream";
|
|
272
|
-
const cacheControl = pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600";
|
|
273
332
|
const baseHeaders = {
|
|
274
333
|
"Content-Type": ct,
|
|
275
334
|
"Cache-Control": cacheControl,
|
|
335
|
+
ETag: etag,
|
|
276
336
|
...extraHeaders
|
|
277
337
|
};
|
|
278
|
-
|
|
279
|
-
if (compress && COMPRESSIBLE_TYPES.has(baseType)) {
|
|
338
|
+
if (isCompressible) {
|
|
280
339
|
const encoding = negotiateEncoding(req);
|
|
281
340
|
if (encoding) {
|
|
282
|
-
|
|
283
|
-
const compressor = createCompressor(encoding);
|
|
284
|
-
res.writeHead(200, {
|
|
341
|
+
res.writeHead(responseStatus, {
|
|
285
342
|
...baseHeaders,
|
|
286
343
|
"Content-Encoding": encoding,
|
|
287
344
|
Vary: "Accept-Encoding"
|
|
288
345
|
});
|
|
289
|
-
|
|
346
|
+
if (omitBody || req.method === "HEAD") {
|
|
347
|
+
res.end();
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
const compressor = createCompressor(encoding);
|
|
351
|
+
pipeline(fs.createReadStream(resolved.path), compressor, res, (err) => {
|
|
352
|
+
if (err) {
|
|
353
|
+
console.warn(`[vinext] Static file stream error for ${resolved.path}:`, err.message);
|
|
354
|
+
res.destroy(err);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
290
357
|
return true;
|
|
291
358
|
}
|
|
292
359
|
}
|
|
293
|
-
res.writeHead(
|
|
294
|
-
|
|
360
|
+
res.writeHead(responseStatus, {
|
|
361
|
+
...baseHeaders,
|
|
362
|
+
"Content-Length": String(resolved.size)
|
|
363
|
+
});
|
|
364
|
+
if (omitBody || req.method === "HEAD") {
|
|
365
|
+
res.end();
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
pipeline(fs.createReadStream(resolved.path), res, (err) => {
|
|
369
|
+
if (err) {
|
|
370
|
+
console.warn(`[vinext] Static file stream error for ${resolved.path}:`, err.message);
|
|
371
|
+
res.destroy(err);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
295
374
|
return true;
|
|
296
375
|
}
|
|
297
376
|
/**
|
|
377
|
+
* Resolve the actual file to serve, trying extension-less HTML fallbacks.
|
|
378
|
+
* Returns the resolved path + size + mtime, or null if not found.
|
|
379
|
+
*/
|
|
380
|
+
async function resolveStaticFile(staticFile) {
|
|
381
|
+
const stat = await statIfFile(staticFile);
|
|
382
|
+
if (stat) return {
|
|
383
|
+
path: staticFile,
|
|
384
|
+
size: stat.size,
|
|
385
|
+
mtimeMs: stat.mtimeMs
|
|
386
|
+
};
|
|
387
|
+
const htmlFallback = staticFile + ".html";
|
|
388
|
+
const htmlStat = await statIfFile(htmlFallback);
|
|
389
|
+
if (htmlStat) return {
|
|
390
|
+
path: htmlFallback,
|
|
391
|
+
size: htmlStat.size,
|
|
392
|
+
mtimeMs: htmlStat.mtimeMs
|
|
393
|
+
};
|
|
394
|
+
const indexFallback = path.join(staticFile, "index.html");
|
|
395
|
+
const indexStat = await statIfFile(indexFallback);
|
|
396
|
+
if (indexStat) return {
|
|
397
|
+
path: indexFallback,
|
|
398
|
+
size: indexStat.size,
|
|
399
|
+
mtimeMs: indexStat.mtimeMs
|
|
400
|
+
};
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
async function statIfFile(filePath) {
|
|
404
|
+
try {
|
|
405
|
+
const stat = await fsp.stat(filePath);
|
|
406
|
+
return stat.isFile() ? {
|
|
407
|
+
size: stat.size,
|
|
408
|
+
mtimeMs: stat.mtimeMs
|
|
409
|
+
} : null;
|
|
410
|
+
} catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
298
415
|
* Resolve the host for a request, ignoring X-Forwarded-Host to prevent
|
|
299
416
|
* host header poisoning attacks (open redirects, cache poisoning).
|
|
300
417
|
*
|
|
@@ -479,6 +596,7 @@ async function startAppRouterServer(options) {
|
|
|
479
596
|
const rscHandler = resolveAppRouterHandler((await import(`${pathToFileURL(rscEntryPath).href}?t=${rscMtime}`)).default);
|
|
480
597
|
const seededRoutes = await seedMemoryCacheFromPrerender(path.dirname(rscEntryPath));
|
|
481
598
|
if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
|
|
599
|
+
const staticCache = await StaticFileCache.create(clientDir);
|
|
482
600
|
const server = createServer(async (req, res) => {
|
|
483
601
|
const rawUrl = req.url ?? "/";
|
|
484
602
|
const rawPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
|
|
@@ -503,7 +621,7 @@ async function startAppRouterServer(options) {
|
|
|
503
621
|
return;
|
|
504
622
|
}
|
|
505
623
|
}
|
|
506
|
-
if (pathname.startsWith("/assets/") && tryServeStatic(req, res, clientDir, pathname, compress)) return;
|
|
624
|
+
if (pathname.startsWith("/assets/") && await tryServeStatic(req, res, clientDir, pathname, compress, staticCache)) return;
|
|
507
625
|
if (pathname === "/_vinext/image") {
|
|
508
626
|
const params = parseImageParams(new URL(rawUrl, "http://localhost"), [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES]);
|
|
509
627
|
if (!params) {
|
|
@@ -521,14 +639,38 @@ async function startAppRouterServer(options) {
|
|
|
521
639
|
"X-Content-Type-Options": "nosniff",
|
|
522
640
|
"Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
523
641
|
};
|
|
524
|
-
if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) return;
|
|
642
|
+
if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders)) return;
|
|
525
643
|
res.writeHead(404);
|
|
526
644
|
res.end("Image not found");
|
|
527
645
|
return;
|
|
528
646
|
}
|
|
529
647
|
try {
|
|
530
648
|
const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
531
|
-
|
|
649
|
+
const response = await rscHandler(nodeToWebRequest(req, pathname + qs));
|
|
650
|
+
const staticFileSignal = response.headers.get("x-vinext-static-file");
|
|
651
|
+
if (staticFileSignal) {
|
|
652
|
+
let staticFilePath = "/";
|
|
653
|
+
try {
|
|
654
|
+
staticFilePath = decodeURIComponent(staticFileSignal);
|
|
655
|
+
} catch {
|
|
656
|
+
staticFilePath = staticFileSignal;
|
|
657
|
+
}
|
|
658
|
+
const staticResponseHeaders = omitHeadersCaseInsensitive(mergeResponseHeaders({}, response), [
|
|
659
|
+
"x-vinext-static-file",
|
|
660
|
+
"content-encoding",
|
|
661
|
+
"content-length",
|
|
662
|
+
"content-type"
|
|
663
|
+
]);
|
|
664
|
+
const served = await tryServeStatic(req, res, clientDir, staticFilePath, compress, staticCache, staticResponseHeaders, response.status);
|
|
665
|
+
cancelResponseBody(response);
|
|
666
|
+
if (served) return;
|
|
667
|
+
await sendWebResponse(new Response("Not Found", {
|
|
668
|
+
status: 404,
|
|
669
|
+
headers: toWebHeaders(staticResponseHeaders)
|
|
670
|
+
}), req, res, compress);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
await sendWebResponse(response, req, res, compress);
|
|
532
674
|
} catch (e) {
|
|
533
675
|
console.error("[vinext] Server error:", e);
|
|
534
676
|
if (!res.headersSent) {
|
|
@@ -590,6 +732,7 @@ async function startPagesRouterServer(options) {
|
|
|
590
732
|
const lazyChunks = computeLazyChunks(JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"))).map((file) => manifestFileWithBase(file, assetBase));
|
|
591
733
|
if (lazyChunks.length > 0) globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
|
|
592
734
|
} catch {}
|
|
735
|
+
const staticCache = await StaticFileCache.create(clientDir);
|
|
593
736
|
const server = createServer(async (req, res) => {
|
|
594
737
|
const rawUrl = req.url ?? "/";
|
|
595
738
|
const rawPagesPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
|
|
@@ -640,7 +783,7 @@ async function startPagesRouterServer(options) {
|
|
|
640
783
|
return;
|
|
641
784
|
}
|
|
642
785
|
const staticLookupPath = stripBasePath(pathname, basePath);
|
|
643
|
-
if (staticLookupPath.startsWith("/assets/") && tryServeStatic(req, res, clientDir, staticLookupPath, compress)) return;
|
|
786
|
+
if (staticLookupPath.startsWith("/assets/") && await tryServeStatic(req, res, clientDir, staticLookupPath, compress, staticCache)) return;
|
|
644
787
|
if (pathname === "/_vinext/image" || staticLookupPath === "/_vinext/image") {
|
|
645
788
|
const params = parseImageParams(new URL(rawUrl, "http://localhost"), allowedImageWidths);
|
|
646
789
|
if (!params) {
|
|
@@ -658,7 +801,7 @@ async function startPagesRouterServer(options) {
|
|
|
658
801
|
"X-Content-Type-Options": "nosniff",
|
|
659
802
|
"Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
660
803
|
};
|
|
661
|
-
if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) return;
|
|
804
|
+
if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders)) return;
|
|
662
805
|
res.writeHead(404);
|
|
663
806
|
res.end("Image not found");
|
|
664
807
|
return;
|
|
@@ -769,7 +912,7 @@ async function startPagesRouterServer(options) {
|
|
|
769
912
|
else if (!(lk in middlewareHeaders)) middlewareHeaders[lk] = h.value;
|
|
770
913
|
}
|
|
771
914
|
}
|
|
772
|
-
if (staticLookupPath !== "/" && !staticLookupPath.startsWith("/api/") && !staticLookupPath.startsWith("/assets/") && tryServeStatic(req, res, clientDir, staticLookupPath, compress, middlewareHeaders)) return;
|
|
915
|
+
if (staticLookupPath !== "/" && !staticLookupPath.startsWith("/api/") && !staticLookupPath.startsWith("/assets/") && await tryServeStatic(req, res, clientDir, staticLookupPath, compress, staticCache, middlewareHeaders)) return;
|
|
773
916
|
if (configRewrites.beforeFiles?.length) {
|
|
774
917
|
const rewritten = matchRewrite(resolvedPathname, configRewrites.beforeFiles, postMwReqCtx);
|
|
775
918
|
if (rewritten) {
|
|
@@ -861,6 +1004,6 @@ async function startPagesRouterServer(options) {
|
|
|
861
1004
|
};
|
|
862
1005
|
}
|
|
863
1006
|
//#endregion
|
|
864
|
-
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts };
|
|
1007
|
+
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
|
|
865
1008
|
|
|
866
1009
|
//# sourceMappingURL=prod-server.js.map
|