vinext 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +27 -1
- package/dist/build/client-build-config.js +58 -1
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +8 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/config/config-matchers.d.ts +20 -1
- package/dist/config/config-matchers.js +35 -1
- package/dist/config/next-config.d.ts +16 -3
- package/dist/config/next-config.js +30 -2
- package/dist/deploy.js +40 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +54 -4
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-server-entry.js +9 -1
- package/dist/index.js +162 -217
- package/dist/plugins/postcss.js +18 -14
- package/dist/plugins/require-context.d.ts +6 -0
- package/dist/plugins/require-context.js +184 -0
- package/dist/routing/app-route-graph.d.ts +12 -1
- package/dist/routing/app-route-graph.js +137 -5
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +16 -1
- package/dist/server/api-handler.js +4 -0
- package/dist/server/app-browser-entry.js +84 -39
- package/dist/server/app-browser-interception-context.d.ts +2 -1
- package/dist/server/app-browser-interception-context.js +15 -2
- package/dist/server/app-browser-navigation-controller.d.ts +11 -1
- package/dist/server/app-browser-navigation-controller.js +77 -1
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +6 -3
- package/dist/server/app-browser-visible-commit.js +9 -7
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-page-boundary-render.js +41 -19
- package/dist/server/app-page-dispatch.d.ts +6 -0
- package/dist/server/app-page-dispatch.js +3 -1
- package/dist/server/app-page-element-builder.d.ts +1 -0
- package/dist/server/app-page-element-builder.js +22 -10
- package/dist/server/app-page-render.d.ts +6 -0
- package/dist/server/app-page-render.js +5 -3
- package/dist/server/app-page-request.d.ts +8 -6
- package/dist/server/app-page-request.js +12 -9
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +2 -1
- package/dist/server/app-page-stream.d.ts +37 -2
- package/dist/server/app-page-stream.js +36 -3
- package/dist/server/app-pages-bridge.d.ts +16 -0
- package/dist/server/app-pages-bridge.js +23 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +2 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +3 -2
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +4 -3
- package/dist/server/app-rsc-route-matching.d.ts +20 -1
- package/dist/server/app-rsc-route-matching.js +29 -4
- package/dist/server/app-server-action-execution.d.ts +11 -1
- package/dist/server/app-server-action-execution.js +68 -10
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +17 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +54 -31
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/navigation-planner.js +5 -3
- package/dist/server/navigation-trace.d.ts +1 -1
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +4 -0
- package/dist/server/pages-page-data.d.ts +10 -7
- package/dist/server/pages-page-data.js +4 -2
- package/dist/server/pages-page-handler.d.ts +9 -2
- package/dist/server/pages-page-handler.js +29 -16
- package/dist/server/pages-page-response.d.ts +11 -2
- package/dist/server/pages-page-response.js +8 -1
- package/dist/server/pages-readiness.d.ts +36 -0
- package/dist/server/pages-readiness.js +21 -0
- package/dist/server/pages-request-pipeline.d.ts +99 -0
- package/dist/server/pages-request-pipeline.js +209 -0
- package/dist/server/pages-revalidate.d.ts +15 -0
- package/dist/server/pages-revalidate.js +19 -0
- package/dist/server/prod-server.d.ts +6 -2
- package/dist/server/prod-server.js +101 -217
- package/dist/server/socket-error-backstop.d.ts +19 -1
- package/dist/server/socket-error-backstop.js +77 -4
- package/dist/shims/app-router-scroll.js +22 -4
- package/dist/shims/cache-runtime.js +31 -1
- package/dist/shims/error-boundary.d.ts +21 -11
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/fetch-cache.d.ts +14 -1
- package/dist/shims/fetch-cache.js +18 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/internal/link-status-registry.d.ts +43 -0
- package/dist/shims/internal/link-status-registry.js +42 -0
- package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
- package/dist/shims/internal/route-pattern-for-warning.js +40 -0
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/link.js +20 -6
- package/dist/shims/navigation.d.ts +2 -2
- package/dist/shims/navigation.js +63 -7
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +2 -0
- package/dist/shims/router.d.ts +6 -3
- package/dist/shims/router.js +128 -21
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +30 -5
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/package.json +5 -1
|
@@ -2,11 +2,9 @@ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
|
2
2
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
3
3
|
import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
|
|
4
4
|
import { normalizePath } from "./normalize-path.js";
|
|
5
|
-
import { applyMiddlewareRequestHeaders, isExternalUrl, matchRedirect, matchRewrite, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
|
|
6
5
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
|
|
6
|
+
import { filterInternalHeaders, isOpenRedirectShaped } from "./request-pipeline.js";
|
|
7
|
+
import { isUnknownRecord } from "../utils/record.js";
|
|
10
8
|
import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
11
9
|
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
|
|
12
10
|
import { installSocketErrorBackstop } from "./socket-error-backstop.js";
|
|
@@ -14,10 +12,13 @@ import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix, resol
|
|
|
14
12
|
import { CONTENT_TYPES, StaticFileCache, etagFromFilenameHash } from "./static-file-cache.js";
|
|
15
13
|
import { buildNextDataNotFoundResponse, isNextDataPathname, parseNextDataPathname } from "./pages-data-route.js";
|
|
16
14
|
import { collectInlineCssManifest } from "../build/inline-css.js";
|
|
15
|
+
import { mergeHeaders } from "./worker-utils.js";
|
|
16
|
+
import { runPagesRequest } from "./pages-request-pipeline.js";
|
|
17
17
|
import { manifestFileWithBase } from "../utils/manifest-paths.js";
|
|
18
18
|
import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
|
|
19
19
|
import { computeLazyChunks } from "../utils/lazy-chunks.js";
|
|
20
|
-
import { findClientEntryFile, readClientBuildManifest } from "../utils/client-build-manifest.js";
|
|
20
|
+
import { findClientEntryFile, findPagesClientEntryFile, readClientBuildManifest } from "../utils/client-build-manifest.js";
|
|
21
|
+
import { findClientEntryFileFromVinextManifest, findPagesClientEntryFileFromVinextManifest, readClientEntryManifest } from "../utils/client-entry-manifest.js";
|
|
21
22
|
import { readPrerenderSecret } from "../build/server-manifest.js";
|
|
22
23
|
import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
|
|
23
24
|
import fs from "node:fs";
|
|
@@ -157,10 +158,6 @@ const NO_BODY_RESPONSE_STATUSES = new Set([
|
|
|
157
158
|
205,
|
|
158
159
|
304
|
|
159
160
|
]);
|
|
160
|
-
function hasHeader(headersRecord, name) {
|
|
161
|
-
const target = name.toLowerCase();
|
|
162
|
-
return Object.keys(headersRecord).some((key) => key.toLowerCase() === target);
|
|
163
|
-
}
|
|
164
161
|
function omitHeadersCaseInsensitive(headersRecord, names) {
|
|
165
162
|
const targets = new Set(names.map((name) => name.toLowerCase()));
|
|
166
163
|
const filtered = {};
|
|
@@ -175,10 +172,6 @@ function matchesIfNoneMatchHeader(ifNoneMatch, etag) {
|
|
|
175
172
|
if (ifNoneMatch === "*") return true;
|
|
176
173
|
return ifNoneMatch.split(",").map((value) => value.trim()).some((value) => value === etag);
|
|
177
174
|
}
|
|
178
|
-
function stripHeaders(headersRecord, names) {
|
|
179
|
-
const targets = new Set(names.map((name) => name.toLowerCase()));
|
|
180
|
-
for (const key of Object.keys(headersRecord)) if (targets.has(key.toLowerCase())) delete headersRecord[key];
|
|
181
|
-
}
|
|
182
175
|
function isNoBodyResponseStatus(status) {
|
|
183
176
|
return NO_BODY_RESPONSE_STATUSES.has(status);
|
|
184
177
|
}
|
|
@@ -201,36 +194,15 @@ function logProdServerStarted(host, port, purpose) {
|
|
|
201
194
|
/**
|
|
202
195
|
* Merge middleware/config headers and an optional status override into a new
|
|
203
196
|
* Web Response while preserving the original body stream when allowed.
|
|
204
|
-
*
|
|
205
|
-
*
|
|
197
|
+
*
|
|
198
|
+
* This is the canonical {@link mergeHeaders} (server/worker-utils.ts) with the
|
|
199
|
+
* arguments in (headers, response) order. The request path now calls
|
|
200
|
+
* `runPagesRequest`, which uses `mergeHeaders` directly; this wrapper is retained
|
|
201
|
+
* only for its existing tests and any external callers, so there is a single
|
|
202
|
+
* implementation to keep in sync. (deploy.ts still emits its own generated copy.)
|
|
206
203
|
*/
|
|
207
204
|
function mergeWebResponse(middlewareHeaders, response, statusOverride) {
|
|
208
|
-
|
|
209
|
-
const status = statusOverride ?? response.status;
|
|
210
|
-
const mergedHeaders = mergeResponseHeaders(filteredMiddlewareHeaders, response);
|
|
211
|
-
const shouldDropBody = isNoBodyResponseStatus(status);
|
|
212
|
-
const shouldStripStreamLength = isVinextStreamedHtmlResponse(response) && hasHeader(mergedHeaders, "content-length");
|
|
213
|
-
if (!Object.keys(filteredMiddlewareHeaders).length && statusOverride === void 0 && !shouldDropBody && !shouldStripStreamLength) return response;
|
|
214
|
-
if (shouldDropBody) {
|
|
215
|
-
cancelResponseBody(response);
|
|
216
|
-
stripHeaders(mergedHeaders, [
|
|
217
|
-
"content-encoding",
|
|
218
|
-
"content-length",
|
|
219
|
-
"content-type",
|
|
220
|
-
"transfer-encoding"
|
|
221
|
-
]);
|
|
222
|
-
return new Response(null, {
|
|
223
|
-
status,
|
|
224
|
-
statusText: status === response.status ? response.statusText : void 0,
|
|
225
|
-
headers: toWebHeaders(mergedHeaders)
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (shouldStripStreamLength) stripHeaders(mergedHeaders, ["content-length"]);
|
|
229
|
-
return new Response(response.body, {
|
|
230
|
-
status,
|
|
231
|
-
statusText: status === response.status ? response.statusText : void 0,
|
|
232
|
-
headers: toWebHeaders(mergedHeaders)
|
|
233
|
-
});
|
|
205
|
+
return mergeHeaders(response, middlewareHeaders, statusOverride);
|
|
234
206
|
}
|
|
235
207
|
/**
|
|
236
208
|
* Send a compressed response if the content type is compressible and the
|
|
@@ -624,6 +596,44 @@ function resolveAppRouterAssetPath(pathname, assetPathPrefix, assetPrefix) {
|
|
|
624
596
|
if (pathname.startsWith(nextStaticDir)) return pathname;
|
|
625
597
|
return null;
|
|
626
598
|
}
|
|
599
|
+
function isSsrManifest(value) {
|
|
600
|
+
if (!isUnknownRecord(value)) return false;
|
|
601
|
+
return Object.values(value).every((files) => Array.isArray(files) && files.every((file) => typeof file === "string"));
|
|
602
|
+
}
|
|
603
|
+
function readSsrManifest(clientDir) {
|
|
604
|
+
const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
|
|
605
|
+
if (!fs.existsSync(manifestPath)) return {};
|
|
606
|
+
let parsed;
|
|
607
|
+
try {
|
|
608
|
+
parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.warn(`[vinext] Ignoring unparseable SSR manifest at ${manifestPath}:`, error);
|
|
611
|
+
return {};
|
|
612
|
+
}
|
|
613
|
+
if (!isSsrManifest(parsed)) {
|
|
614
|
+
console.warn(`[vinext] Ignoring SSR manifest with unexpected shape at ${manifestPath}`);
|
|
615
|
+
return {};
|
|
616
|
+
}
|
|
617
|
+
return parsed;
|
|
618
|
+
}
|
|
619
|
+
function installPagesClientAssetGlobals(options) {
|
|
620
|
+
const ssrManifest = readSsrManifest(options.clientDir);
|
|
621
|
+
globalThis.__VINEXT_SSR_MANIFEST__ = Object.keys(ssrManifest).length > 0 ? ssrManifest : void 0;
|
|
622
|
+
const buildManifest = readClientBuildManifest(path.join(options.clientDir, ".vite", "manifest.json"));
|
|
623
|
+
const clientEntryManifest = readClientEntryManifest(options.clientDir);
|
|
624
|
+
const entryOptions = {
|
|
625
|
+
clientDir: options.clientDir,
|
|
626
|
+
assetsSubdir: options.assetsSubdir,
|
|
627
|
+
assetBase: options.assetBase,
|
|
628
|
+
...buildManifest ? { buildManifest } : {}
|
|
629
|
+
};
|
|
630
|
+
globalThis.__VINEXT_CLIENT_ENTRY__ = options.clientEntryLookup === "pages-client-entry" ? findPagesClientEntryFileFromVinextManifest(clientEntryManifest, options.assetBase) ?? findPagesClientEntryFile(entryOptions) : findClientEntryFileFromVinextManifest(clientEntryManifest, options.assetBase) ?? findClientEntryFile(entryOptions);
|
|
631
|
+
if (buildManifest) {
|
|
632
|
+
const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, options.assetBase));
|
|
633
|
+
globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks.length > 0 ? lazyChunks : void 0;
|
|
634
|
+
} else globalThis.__VINEXT_LAZY_CHUNKS__ = void 0;
|
|
635
|
+
return ssrManifest;
|
|
636
|
+
}
|
|
627
637
|
/**
|
|
628
638
|
* Start the App Router production server.
|
|
629
639
|
*
|
|
@@ -653,9 +663,18 @@ async function startAppRouterServer(options) {
|
|
|
653
663
|
const rscModule = await import(`${pathToFileURL(rscEntryPath).href}?t=${rscMtime}`);
|
|
654
664
|
const rscHandler = resolveAppRouterHandler(rscModule.default);
|
|
655
665
|
const appRouterAssetPrefix = typeof rscModule.__assetPrefix === "string" ? rscModule.__assetPrefix : "";
|
|
666
|
+
const appRouterBasePath = typeof rscModule.__basePath === "string" ? rscModule.__basePath : "";
|
|
656
667
|
const appRouterInlineCss = rscModule.__inlineCss === true;
|
|
668
|
+
const appRouterHasPagesDir = rscModule.__hasPagesDir === true;
|
|
657
669
|
globalThis.__VINEXT_INLINE_CSS__ = appRouterInlineCss ? collectInlineCssManifest(clientDir, appRouterAssetPrefix) : void 0;
|
|
658
670
|
const appAssetPathPrefix = assetPrefixPathname(appRouterAssetPrefix);
|
|
671
|
+
const appAssetBase = appRouterBasePath ? `${appRouterBasePath}/` : "/";
|
|
672
|
+
if (appRouterHasPagesDir) installPagesClientAssetGlobals({
|
|
673
|
+
clientDir,
|
|
674
|
+
assetsSubdir: resolveAssetsDir(appRouterAssetPrefix),
|
|
675
|
+
assetBase: appAssetBase,
|
|
676
|
+
clientEntryLookup: "pages-client-entry"
|
|
677
|
+
});
|
|
659
678
|
const seededRoutes = await resolveAppRouterPrerenderSeeder(rscModule)(path.dirname(rscEntryPath));
|
|
660
679
|
if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
|
|
661
680
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
@@ -811,20 +830,12 @@ async function startPagesRouterServer(options) {
|
|
|
811
830
|
contentDispositionType: vinextConfig.images.contentDispositionType,
|
|
812
831
|
contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
|
|
813
832
|
} : void 0;
|
|
814
|
-
|
|
815
|
-
const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
|
|
816
|
-
if (fs.existsSync(manifestPath)) ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
817
|
-
const buildManifest = readClientBuildManifest(path.join(clientDir, ".vite", "manifest.json"));
|
|
818
|
-
globalThis.__VINEXT_CLIENT_ENTRY__ = findClientEntryFile({
|
|
819
|
-
buildManifest,
|
|
833
|
+
const ssrManifest = installPagesClientAssetGlobals({
|
|
820
834
|
clientDir,
|
|
821
835
|
assetsSubdir: resolveAssetsDir(assetPrefix),
|
|
822
|
-
assetBase
|
|
836
|
+
assetBase,
|
|
837
|
+
clientEntryLookup: "any-client-entry"
|
|
823
838
|
});
|
|
824
|
-
if (buildManifest) {
|
|
825
|
-
const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, assetBase));
|
|
826
|
-
globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks.length > 0 ? lazyChunks : void 0;
|
|
827
|
-
} else globalThis.__VINEXT_LAZY_CHUNKS__ = void 0;
|
|
828
839
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
829
840
|
const handleRequest = async (req, res) => {
|
|
830
841
|
const rawUrl = req.url ?? "/";
|
|
@@ -915,20 +926,6 @@ async function startPagesRouterServer(options) {
|
|
|
915
926
|
pathname = stripped;
|
|
916
927
|
}
|
|
917
928
|
}
|
|
918
|
-
const basePathState = {
|
|
919
|
-
basePath,
|
|
920
|
-
hadBasePath
|
|
921
|
-
};
|
|
922
|
-
{
|
|
923
|
-
const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
924
|
-
const trailingSlashRedirect = normalizeTrailingSlash(pathname, basePath, trailingSlash, qs);
|
|
925
|
-
if (trailingSlashRedirect) {
|
|
926
|
-
const location = trailingSlashRedirect.headers.get("Location");
|
|
927
|
-
res.writeHead(trailingSlashRedirect.status, location ? { Location: location } : void 0);
|
|
928
|
-
res.end();
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
929
|
let isDataReq = false;
|
|
933
930
|
if (isNextDataPathname(pathname)) {
|
|
934
931
|
const dataMatch = pagesBuildId ? parseNextDataPathname(pathname, pagesBuildId) : null;
|
|
@@ -948,167 +945,54 @@ async function startPagesRouterServer(options) {
|
|
|
948
945
|
const reqHeaders = filterInternalHeaders(rawReqHeaders);
|
|
949
946
|
const method = req.method ?? "GET";
|
|
950
947
|
const hasBody = method !== "GET" && method !== "HEAD";
|
|
951
|
-
|
|
948
|
+
const result = await runPagesRequest(new Request(`${protocol}://${hostHeader}${url}`, {
|
|
952
949
|
method,
|
|
953
950
|
headers: reqHeaders,
|
|
954
951
|
body: hasBody ? readNodeStream(req) : void 0,
|
|
955
952
|
duplex: hasBody ? "half" : void 0
|
|
956
|
-
})
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (redirect) {
|
|
963
|
-
const dest = sanitizeDestination(basePath && hadBasePath && !isExternalUrl(redirect.destination) && !hasBasePath(redirect.destination, basePath) ? basePath + redirect.destination : redirect.destination);
|
|
964
|
-
res.writeHead(redirect.permanent ? 308 : 307, { Location: dest });
|
|
965
|
-
res.end();
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
let resolvedUrl = url;
|
|
970
|
-
const middlewareHeaders = {};
|
|
971
|
-
let middlewareStatus;
|
|
972
|
-
if (typeof runMiddleware === "function") {
|
|
973
|
-
const result = await runMiddleware(webRequest, void 0, { isDataRequest });
|
|
974
|
-
if (result.waitUntilPromises && result.waitUntilPromises.length > 0) Promise.allSettled(result.waitUntilPromises);
|
|
975
|
-
if (!result.continue) {
|
|
976
|
-
if (result.redirectUrl) {
|
|
977
|
-
const redirectHeaders = { Location: result.redirectUrl };
|
|
978
|
-
if (result.responseHeaders) for (const [key, value] of result.responseHeaders) {
|
|
979
|
-
const existing = redirectHeaders[key];
|
|
980
|
-
if (existing === void 0) redirectHeaders[key] = value;
|
|
981
|
-
else if (Array.isArray(existing)) existing.push(value);
|
|
982
|
-
else redirectHeaders[key] = [existing, value];
|
|
983
|
-
}
|
|
984
|
-
res.writeHead(result.redirectStatus ?? 307, redirectHeaders);
|
|
985
|
-
res.end();
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
if (result.response) {
|
|
989
|
-
const body = Buffer.from(await result.response.arrayBuffer());
|
|
990
|
-
const respHeaders = {};
|
|
991
|
-
result.response.headers.forEach((value, key) => {
|
|
992
|
-
if (key === "set-cookie") return;
|
|
993
|
-
respHeaders[key] = value;
|
|
994
|
-
});
|
|
995
|
-
const setCookies = result.response.headers.getSetCookie?.() ?? [];
|
|
996
|
-
if (setCookies.length > 0) respHeaders["set-cookie"] = setCookies;
|
|
997
|
-
if (result.response.statusText) res.writeHead(result.response.status, result.response.statusText, respHeaders);
|
|
998
|
-
else res.writeHead(result.response.status, respHeaders);
|
|
999
|
-
res.end(body);
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
if (result.responseHeaders) for (const [key, value] of result.responseHeaders) if (key === "set-cookie") {
|
|
1004
|
-
const existing = middlewareHeaders[key];
|
|
1005
|
-
if (Array.isArray(existing)) existing.push(value);
|
|
1006
|
-
else if (existing) middlewareHeaders[key] = [existing, value];
|
|
1007
|
-
else middlewareHeaders[key] = [value];
|
|
1008
|
-
} else middlewareHeaders[key] = value;
|
|
1009
|
-
if (result.rewriteUrl) resolvedUrl = result.rewriteUrl;
|
|
1010
|
-
middlewareStatus = result.status ?? result.rewriteStatus;
|
|
1011
|
-
}
|
|
1012
|
-
const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, webRequest, { preserveCredentialHeaders: isExternalUrl(resolvedUrl) });
|
|
1013
|
-
webRequest = postMwReq;
|
|
1014
|
-
let resolvedPathname = resolvedUrl.split("?")[0];
|
|
1015
|
-
const matchResolvedPathname = (p) => i18nConfig ? normalizeDefaultLocalePathname(p, i18nConfig, { hostname: requestHostname }) : p;
|
|
1016
|
-
if (configHeaders.length) applyConfigHeadersToHeaderRecord(middlewareHeaders, {
|
|
953
|
+
}), {
|
|
954
|
+
basePath,
|
|
955
|
+
trailingSlash,
|
|
956
|
+
i18nConfig,
|
|
957
|
+
configRedirects,
|
|
958
|
+
configRewrites,
|
|
1017
959
|
configHeaders,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (rewritten) {
|
|
1031
|
-
if (isExternalUrl(rewritten)) {
|
|
1032
|
-
await sendWebResponse(await proxyExternalRequest(webRequest, rewritten), req, res, compress);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
|
|
1036
|
-
resolvedPathname = resolvedUrl.split("?")[0];
|
|
1037
|
-
configRewriteFired = true;
|
|
960
|
+
hadBasePath,
|
|
961
|
+
isDataReq,
|
|
962
|
+
isDataRequest,
|
|
963
|
+
ctx: void 0,
|
|
964
|
+
rawSearch: rawQs,
|
|
965
|
+
matchPageRoute: matchPageRoute ?? null,
|
|
966
|
+
runMiddleware: typeof runMiddleware === "function" ? runMiddleware : null,
|
|
967
|
+
renderPage: typeof renderPage === "function" ? (request, resolvedUrl, options, stagedHeaders) => renderPage(request, resolvedUrl, ssrManifest, void 0, stagedHeaders, options) : null,
|
|
968
|
+
handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
|
|
969
|
+
serveStaticFile: async (requestPathname, stagedHeaders) => {
|
|
970
|
+
if (requestPathname === "/" || requestPathname.startsWith("/api/") || requestPathname.startsWith(`/_next/static/`)) return false;
|
|
971
|
+
return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
|
|
1038
972
|
}
|
|
1039
|
-
}
|
|
1040
|
-
if (
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, vinextConfig?.i18n ?? null);
|
|
1046
|
-
const apiLookupPathname = apiLookupUrl.split("?")[0];
|
|
1047
|
-
if (apiLookupPathname.startsWith("/api/") || apiLookupPathname === "/api") {
|
|
1048
|
-
let response;
|
|
1049
|
-
if (typeof handleApi === "function") response = await handleApi(webRequest, apiLookupUrl, createNodeExecutionContext());
|
|
1050
|
-
else response = new Response("404 - API route not found", { status: 404 });
|
|
1051
|
-
const mergedResponse = mergeWebResponse(middlewareHeaders, response, middlewareStatus);
|
|
1052
|
-
if (!mergedResponse.body) {
|
|
1053
|
-
await sendWebResponse(mergedResponse, req, res, compress);
|
|
973
|
+
});
|
|
974
|
+
if (result.type === "handled") return;
|
|
975
|
+
if (result.type === "response") {
|
|
976
|
+
const { response } = result;
|
|
977
|
+
if (isVinextStreamedHtmlResponse(response) || !response.body || result.defaultContentType === void 0) {
|
|
978
|
+
await sendWebResponse(response, req, res, compress);
|
|
1054
979
|
return;
|
|
1055
980
|
}
|
|
1056
|
-
const responseBody = Buffer.from(await
|
|
1057
|
-
const ct =
|
|
1058
|
-
const responseHeaders =
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
if (isExternalUrl(rewritten)) {
|
|
1068
|
-
await sendWebResponse(await proxyExternalRequest(webRequest, rewritten), req, res, compress);
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
|
|
1072
|
-
resolvedPathname = resolvedUrl.split("?")[0];
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
let response;
|
|
1076
|
-
if (typeof renderPage === "function") {
|
|
1077
|
-
const middlewareResponseHeaders = toWebHeaders(middlewareHeaders);
|
|
1078
|
-
const renderPageMatch = matchPageRoute ? matchPageRoute(resolvedPathname, webRequest) : null;
|
|
1079
|
-
const shouldDeferErrorPageOnMiss = !isDataReq && !!matchPageRoute && !renderPageMatch;
|
|
1080
|
-
const dataRenderOptions = isDataReq ? { isDataReq: true } : void 0;
|
|
1081
|
-
response = await renderPage(webRequest, resolvedUrl, ssrManifest, void 0, middlewareResponseHeaders, shouldDeferErrorPageOnMiss ? { renderErrorPageOnMiss: false } : dataRenderOptions);
|
|
1082
|
-
let matchedFallbackRewrite = false;
|
|
1083
|
-
if (response && response.status === 404 && shouldDeferErrorPageOnMiss && configRewrites.fallback?.length) {
|
|
1084
|
-
const fallbackRewrite = matchRewrite(matchResolvedPathname(resolvedPathname), configRewrites.fallback, postMwReqCtx, basePathState);
|
|
1085
|
-
if (fallbackRewrite) {
|
|
1086
|
-
if (isExternalUrl(fallbackRewrite)) {
|
|
1087
|
-
await sendWebResponse(await proxyExternalRequest(webRequest, fallbackRewrite), req, res, compress);
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
matchedFallbackRewrite = true;
|
|
1091
|
-
response = await renderPage(webRequest, mergeRewriteQuery(resolvedUrl, fallbackRewrite), ssrManifest, void 0, middlewareResponseHeaders, dataRenderOptions);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
if (response && response.status === 404 && shouldDeferErrorPageOnMiss && !matchedFallbackRewrite) response = await renderPage(webRequest, resolvedUrl, ssrManifest, void 0, middlewareResponseHeaders);
|
|
1095
|
-
}
|
|
1096
|
-
if (!response) {
|
|
1097
|
-
res.writeHead(404);
|
|
1098
|
-
res.end("This page could not be found");
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
const shouldStreamPagesResponse = isVinextStreamedHtmlResponse(response);
|
|
1102
|
-
const mergedResponse = mergeWebResponse(middlewareHeaders, response, middlewareStatus);
|
|
1103
|
-
if (shouldStreamPagesResponse || !mergedResponse.body) {
|
|
1104
|
-
await sendWebResponse(mergedResponse, req, res, compress);
|
|
981
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
982
|
+
const ct = response.headers.get("content-type") ?? result.defaultContentType;
|
|
983
|
+
const responseHeaders = {};
|
|
984
|
+
response.headers.forEach((v, k) => {
|
|
985
|
+
if (k === "set-cookie") return;
|
|
986
|
+
responseHeaders[k] = v;
|
|
987
|
+
});
|
|
988
|
+
const setCookies = response.headers.getSetCookie?.() ?? [];
|
|
989
|
+
if (setCookies.length > 0) responseHeaders["set-cookie"] = setCookies;
|
|
990
|
+
const finalStatusText = response.statusText || void 0;
|
|
991
|
+
sendCompressed(req, res, responseBody, ct, response.status, responseHeaders, compress, finalStatusText);
|
|
1105
992
|
return;
|
|
1106
993
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
const responseHeaders = mergeResponseHeaders({}, mergedResponse);
|
|
1110
|
-
const finalStatusText = mergedResponse.statusText || void 0;
|
|
1111
|
-
sendCompressed(req, res, responseBody, ct, mergedResponse.status, responseHeaders, compress, finalStatusText);
|
|
994
|
+
res.writeHead(404);
|
|
995
|
+
res.end("This page could not be found");
|
|
1112
996
|
} catch (e) {
|
|
1113
997
|
console.error("[vinext] Server error:", e);
|
|
1114
998
|
if (!res.headersSent) {
|
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
* Exported for unit testing in isolation (no process-state mutation).
|
|
6
6
|
*/
|
|
7
7
|
declare function peerDisconnectCode(err: unknown): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Pure predicate: returns `true` when `err` is a benign failure from a
|
|
10
|
+
* dynamic `import()` of a static asset URL — the "URL dependency" pattern
|
|
11
|
+
* that Next.js tolerates at build time. Two shapes are recognised:
|
|
12
|
+
*
|
|
13
|
+
* - `ERR_UNKNOWN_FILE_EXTENSION`: the asset resolved on disk but is not an
|
|
14
|
+
* ES module (e.g. a co-located `./style.css`). The error message ends in
|
|
15
|
+
* the offending extension in quotes: `... extension ".css" for /path`.
|
|
16
|
+
* - `ERR_MODULE_NOT_FOUND`: the asset URL did not resolve (the chunk lives
|
|
17
|
+
* in `dist/server/` but the source asset does not). Node attaches the
|
|
18
|
+
* unresolved specifier on `err.url`; we match only when it points at a
|
|
19
|
+
* static-asset extension.
|
|
20
|
+
*
|
|
21
|
+
* Anything outside this allow-list (including missing `.js`/`.mjs`/`.ts`
|
|
22
|
+
* modules with no extension) returns `false` so real bugs still crash.
|
|
23
|
+
* Exported for unit testing in isolation.
|
|
24
|
+
*/
|
|
25
|
+
declare function isBenignAssetImportError(err: unknown): boolean;
|
|
8
26
|
/**
|
|
9
27
|
* Test-only: returns whether the backstop has been installed in this
|
|
10
28
|
* process. Used by the unit test to assert idempotent install via the
|
|
@@ -13,4 +31,4 @@ declare function peerDisconnectCode(err: unknown): string | undefined;
|
|
|
13
31
|
declare function isSocketErrorBackstopInstalled(): boolean;
|
|
14
32
|
declare function installSocketErrorBackstop(): void;
|
|
15
33
|
//#endregion
|
|
16
|
-
export { installSocketErrorBackstop, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
34
|
+
export { installSocketErrorBackstop, isBenignAssetImportError, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* `uncaughtException`, where this listener filters it.
|
|
16
16
|
*
|
|
17
17
|
* Filters strictly on peer-disconnect codes (ECONNRESET / EPIPE /
|
|
18
|
-
* ECONNABORTED)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* ECONNABORTED) plus benign static-asset `import()` rejections (see
|
|
19
|
+
* `isBenignAssetImportError`), and synchronously re-throws everything
|
|
20
|
+
* else, preserving Node's default crash semantics for genuine bugs.
|
|
21
|
+
* This is more conservative than Next.js's equivalent
|
|
21
22
|
* (`router-server.ts`'s log-only handler), which silently swallows
|
|
22
23
|
* every uncaught — vinext keeps real bugs surfacing.
|
|
23
24
|
*
|
|
@@ -89,6 +90,68 @@ function peerDisconnectCode(err) {
|
|
|
89
90
|
const code = err?.code;
|
|
90
91
|
return code === "ECONNRESET" || code === "EPIPE" || code === "ECONNABORTED" ? code : void 0;
|
|
91
92
|
}
|
|
93
|
+
const STATIC_ASSET_IMPORT_EXTENSIONS = new Set([
|
|
94
|
+
".css",
|
|
95
|
+
".scss",
|
|
96
|
+
".sass",
|
|
97
|
+
".less",
|
|
98
|
+
".styl",
|
|
99
|
+
".png",
|
|
100
|
+
".jpg",
|
|
101
|
+
".jpeg",
|
|
102
|
+
".gif",
|
|
103
|
+
".svg",
|
|
104
|
+
".webp",
|
|
105
|
+
".avif",
|
|
106
|
+
".ico",
|
|
107
|
+
".bmp",
|
|
108
|
+
".woff",
|
|
109
|
+
".woff2",
|
|
110
|
+
".ttf",
|
|
111
|
+
".otf",
|
|
112
|
+
".eot",
|
|
113
|
+
".txt",
|
|
114
|
+
".md",
|
|
115
|
+
".csv",
|
|
116
|
+
".xml",
|
|
117
|
+
".pdf",
|
|
118
|
+
".wasm"
|
|
119
|
+
]);
|
|
120
|
+
/**
|
|
121
|
+
* Pure predicate: returns `true` when `err` is a benign failure from a
|
|
122
|
+
* dynamic `import()` of a static asset URL — the "URL dependency" pattern
|
|
123
|
+
* that Next.js tolerates at build time. Two shapes are recognised:
|
|
124
|
+
*
|
|
125
|
+
* - `ERR_UNKNOWN_FILE_EXTENSION`: the asset resolved on disk but is not an
|
|
126
|
+
* ES module (e.g. a co-located `./style.css`). The error message ends in
|
|
127
|
+
* the offending extension in quotes: `... extension ".css" for /path`.
|
|
128
|
+
* - `ERR_MODULE_NOT_FOUND`: the asset URL did not resolve (the chunk lives
|
|
129
|
+
* in `dist/server/` but the source asset does not). Node attaches the
|
|
130
|
+
* unresolved specifier on `err.url`; we match only when it points at a
|
|
131
|
+
* static-asset extension.
|
|
132
|
+
*
|
|
133
|
+
* Anything outside this allow-list (including missing `.js`/`.mjs`/`.ts`
|
|
134
|
+
* modules with no extension) returns `false` so real bugs still crash.
|
|
135
|
+
* Exported for unit testing in isolation.
|
|
136
|
+
*/
|
|
137
|
+
function isBenignAssetImportError(err) {
|
|
138
|
+
if (err === null || typeof err !== "object") return false;
|
|
139
|
+
const code = err.code;
|
|
140
|
+
if (code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
141
|
+
const message = err.message ?? "";
|
|
142
|
+
const match = /extension\s+"([^"]+)"/.exec(message);
|
|
143
|
+
return match != null && STATIC_ASSET_IMPORT_EXTENSIONS.has(match[1].toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
if (code === "ERR_MODULE_NOT_FOUND") {
|
|
146
|
+
const url = err.url;
|
|
147
|
+
if (typeof url !== "string") return false;
|
|
148
|
+
const pathname = url.split("?", 1)[0].split("#", 1)[0];
|
|
149
|
+
const dot = pathname.lastIndexOf(".");
|
|
150
|
+
if (dot === -1) return false;
|
|
151
|
+
return STATIC_ASSET_IMPORT_EXTENSIONS.has(pathname.slice(dot).toLowerCase());
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
92
155
|
/**
|
|
93
156
|
* Test-only: returns whether the backstop has been installed in this
|
|
94
157
|
* process. Used by the unit test to assert idempotent install via the
|
|
@@ -104,7 +167,16 @@ function installSocketErrorBackstop() {
|
|
|
104
167
|
proc[SOCKET_BACKSTOP_FLAG] = true;
|
|
105
168
|
const debug = process.env.VINEXT_DEBUG_SOCKET_ERRORS === "1";
|
|
106
169
|
if (debug) console.warn("[vinext] socket-error backstop installed");
|
|
170
|
+
const absorbBenignAssetImport = (reason, kind) => {
|
|
171
|
+
if (!isBenignAssetImportError(reason)) return false;
|
|
172
|
+
if (debug) {
|
|
173
|
+
const code = reason?.code;
|
|
174
|
+
console.warn(`[vinext] absorbed ${kind} ${code} (asset URL import)`);
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
};
|
|
107
178
|
process.on("uncaughtException", (err) => {
|
|
179
|
+
if (absorbBenignAssetImport(err, "uncaughtException")) return;
|
|
108
180
|
if (process.env.VINEXT_PRERENDER === "1") throw err;
|
|
109
181
|
const code = peerDisconnectCode(err);
|
|
110
182
|
if (code) {
|
|
@@ -114,6 +186,7 @@ function installSocketErrorBackstop() {
|
|
|
114
186
|
throw err;
|
|
115
187
|
});
|
|
116
188
|
process.on("unhandledRejection", (reason) => {
|
|
189
|
+
if (absorbBenignAssetImport(reason, "unhandledRejection")) return;
|
|
117
190
|
if (process.env.VINEXT_PRERENDER === "1") throw reason;
|
|
118
191
|
const code = peerDisconnectCode(reason);
|
|
119
192
|
if (code) {
|
|
@@ -124,4 +197,4 @@ function installSocketErrorBackstop() {
|
|
|
124
197
|
});
|
|
125
198
|
}
|
|
126
199
|
//#endregion
|
|
127
|
-
export { installSocketErrorBackstop, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
200
|
+
export { installSocketErrorBackstop, isBenignAssetImportError, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
@@ -49,14 +49,22 @@ function getHashFragmentDomNode(hash) {
|
|
|
49
49
|
const element = document.getElementById(fragment) ?? document.getElementsByName(fragment)[0];
|
|
50
50
|
return element instanceof HTMLElement ? element : null;
|
|
51
51
|
}
|
|
52
|
+
function isInDocumentHead(node) {
|
|
53
|
+
const head = node.ownerDocument?.head;
|
|
54
|
+
return head != null && head.contains(node);
|
|
55
|
+
}
|
|
52
56
|
function findNextScrollTarget(node) {
|
|
53
57
|
if (!(node instanceof Element)) return null;
|
|
58
|
+
if (isInDocumentHead(node)) return { kind: "document-top" };
|
|
54
59
|
let target = node;
|
|
55
60
|
while (!(target instanceof HTMLElement) || shouldSkipElement(target)) {
|
|
56
61
|
if (target.nextElementSibling === null) return null;
|
|
57
62
|
target = target.nextElementSibling;
|
|
58
63
|
}
|
|
59
|
-
return
|
|
64
|
+
return {
|
|
65
|
+
kind: "element",
|
|
66
|
+
element: target
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
function scrollToElement(target, hash) {
|
|
62
70
|
if (hash !== null) {
|
|
@@ -79,9 +87,19 @@ var AppRouterScrollTargetInner = class extends React$1.Component {
|
|
|
79
87
|
if (intent === null) return;
|
|
80
88
|
if (this.props.commitId === null || intent.commitId !== this.props.commitId) return;
|
|
81
89
|
let target;
|
|
82
|
-
if (intent.hash !== null)
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
if (intent.hash !== null) {
|
|
91
|
+
target = getHashFragmentDomNode(intent.hash);
|
|
92
|
+
if (target === null) return;
|
|
93
|
+
} else {
|
|
94
|
+
const next = findNextScrollTarget(findDOMNode(this));
|
|
95
|
+
if (next === null) return;
|
|
96
|
+
if (next.kind === "document-top") {
|
|
97
|
+
if (consumeAppRouterScrollIntent(intent, this.props.commitId) === null) return;
|
|
98
|
+
document.documentElement.scrollTop = 0;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
target = next.element;
|
|
102
|
+
}
|
|
85
103
|
const consumed = consumeAppRouterScrollIntent(intent, this.props.commitId);
|
|
86
104
|
if (consumed === null) return;
|
|
87
105
|
scrollToElement(target, consumed.hash);
|