vinext 0.1.3 → 0.1.4
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/client-build-config.d.ts +11 -2
- package/dist/build/client-build-config.js +17 -6
- package/dist/build/prerender.js +1 -0
- package/dist/client/pages-router-link-navigation.d.ts +33 -7
- package/dist/client/pages-router-link-navigation.js +32 -2
- package/dist/client/vinext-next-data.js +2 -0
- package/dist/config/config-matchers.d.ts +11 -1
- package/dist/config/config-matchers.js +14 -2
- package/dist/config/tsconfig-paths.js +14 -1
- package/dist/deploy.js +20 -13
- package/dist/entries/app-rsc-entry.js +3 -2
- package/dist/entries/pages-client-entry.js +14 -13
- package/dist/entries/pages-server-entry.js +6 -26
- package/dist/index.js +217 -40
- package/dist/plugins/dynamic-preload-metadata.js +2 -4
- package/dist/plugins/fonts.js +5 -4
- package/dist/plugins/strip-server-exports.d.ts +9 -7
- package/dist/plugins/strip-server-exports.js +493 -46
- package/dist/routing/app-route-graph.js +2 -2
- package/dist/server/app-browser-action-result.js +1 -1
- package/dist/server/app-browser-entry.js +8 -1
- package/dist/server/app-browser-navigation-controller.d.ts +1 -1
- package/dist/server/app-browser-state.d.ts +1 -1
- package/dist/server/app-browser-state.js +19 -11
- package/dist/server/app-browser-visible-commit.d.ts +1 -1
- package/dist/server/app-pages-bridge.d.ts +5 -1
- package/dist/server/app-pages-bridge.js +5 -13
- package/dist/server/app-rsc-handler.d.ts +3 -0
- package/dist/server/app-rsc-handler.js +51 -15
- package/dist/server/app-rsc-route-matching.js +6 -2
- package/dist/server/app-server-action-execution.js +5 -2
- package/dist/server/app-ssr-entry.js +1 -29
- package/dist/server/before-interactive-head.d.ts +17 -0
- package/dist/server/before-interactive-head.js +35 -0
- package/dist/server/csp.js +1 -4
- package/dist/server/dev-server.js +81 -36
- package/dist/server/middleware-matcher.js +12 -3
- package/dist/server/middleware-runtime.d.ts +3 -4
- package/dist/server/middleware-runtime.js +2 -0
- package/dist/server/navigation-planner.d.ts +3 -12
- package/dist/server/navigation-planner.js +24 -0
- package/dist/server/navigation-trace.d.ts +2 -1
- package/dist/server/navigation-trace.js +1 -0
- package/dist/server/operation-token.d.ts +40 -0
- package/dist/server/operation-token.js +85 -0
- package/dist/server/pages-data-route.d.ts +1 -1
- package/dist/server/pages-data-route.js +7 -4
- package/dist/server/pages-dev-module-url.d.ts +4 -0
- package/dist/server/pages-dev-module-url.js +15 -0
- package/dist/server/pages-document-initial-props.d.ts +4 -15
- package/dist/server/pages-document-initial-props.js +27 -56
- package/dist/server/pages-i18n.js +2 -2
- package/dist/server/pages-page-data.js +3 -1
- package/dist/server/pages-page-handler.js +3 -1
- package/dist/server/pages-page-response.d.ts +2 -0
- package/dist/server/pages-page-response.js +4 -4
- package/dist/server/pages-readiness.js +1 -1
- package/dist/server/pages-request-pipeline.d.ts +7 -7
- package/dist/server/pages-request-pipeline.js +63 -21
- package/dist/server/prod-server.d.ts +3 -1
- package/dist/server/prod-server.js +41 -10
- package/dist/server/static-file-cache.js +16 -4
- package/dist/shims/before-interactive-context.d.ts +14 -3
- package/dist/shims/document.d.ts +15 -20
- package/dist/shims/document.js +5 -8
- package/dist/shims/image.js +9 -2
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
- package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
- package/dist/shims/internal/pages-data-target.js +1 -1
- package/dist/shims/link.js +37 -16
- package/dist/shims/metadata.js +4 -4
- package/dist/shims/navigation.js +2 -0
- package/dist/shims/router.d.ts +6 -2
- package/dist/shims/router.js +99 -20
- package/dist/shims/script.js +8 -4
- package/dist/utils/has-trailing-comma.d.ts +24 -0
- package/dist/utils/has-trailing-comma.js +62 -0
- package/dist/utils/text-stream.d.ts +1 -1
- package/dist/utils/text-stream.js +2 -2
- package/dist/utils/vite-version.d.ts +12 -1
- package/dist/utils/vite-version.js +9 -1
- package/package.json +1 -1
|
@@ -19,19 +19,19 @@ import React from "react";
|
|
|
19
19
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx
|
|
20
20
|
* (search for `loadDocumentInitialProps` and `documentElement`).
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* `runDocumentRenderPage()` supplies `renderPage`, `defaultGetInitialProps`,
|
|
23
|
+
* and the request pathname/query/asPath fields needed by the common upstream
|
|
24
|
+
* pattern:
|
|
25
25
|
*
|
|
26
26
|
* static async getInitialProps(ctx) {
|
|
27
27
|
* const initialProps = await Document.getInitialProps(ctx)
|
|
28
28
|
* return { ...initialProps, docValue }
|
|
29
29
|
* }
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* The standalone `loadUserDocumentInitialProps()` compatibility path supplies
|
|
32
|
+
* an empty default render result because it is only used after the body has
|
|
33
|
+
* already been produced. Request-only fields such as req/res remain outside
|
|
34
|
+
* that compatibility helper.
|
|
35
35
|
*
|
|
36
36
|
* Returns `null` when the user did not override the base shim (the static
|
|
37
37
|
* `getInitialProps` reference still points at the shim's stub) so callers
|
|
@@ -47,7 +47,7 @@ async function loadUserDocumentInitialProps(DocumentComponent) {
|
|
|
47
47
|
const getInitialProps = DocumentComponent.getInitialProps;
|
|
48
48
|
if (typeof getInitialProps !== "function") return null;
|
|
49
49
|
if (getInitialProps === BASE_GET_INITIAL_PROPS) return null;
|
|
50
|
-
const result = await getInitialProps({});
|
|
50
|
+
const result = await getInitialProps({ defaultGetInitialProps: async () => ({ html: "" }) });
|
|
51
51
|
return result && typeof result === "object" ? result : null;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
@@ -60,11 +60,10 @@ async function loadUserDocumentInitialProps(DocumentComponent) {
|
|
|
60
60
|
* prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
|
|
61
61
|
* the `getInitialProps` + `renderPage` contract lives in one place.
|
|
62
62
|
*
|
|
63
|
-
* `getInitialProps` is invoked at most once here. When this returns `
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* this contract guarantees).
|
|
63
|
+
* `getInitialProps` is invoked at most once here. When this returns `rendered`,
|
|
64
|
+
* callers MUST treat that as the single invocation and must not call
|
|
65
|
+
* `loadUserDocumentInitialProps` again. Errors intentionally propagate to the
|
|
66
|
+
* Pages Router's normal error-page pipeline, matching Next.js.
|
|
68
67
|
*
|
|
69
68
|
* @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
|
|
70
69
|
*/
|
|
@@ -74,58 +73,30 @@ async function runDocumentRenderPage(input) {
|
|
|
74
73
|
if (DocCtor.getInitialProps === BASE_GET_INITIAL_PROPS) return { status: "skipped" };
|
|
75
74
|
if (!input.enhancePageElement) return { status: "skipped" };
|
|
76
75
|
const enhancePageElement = input.enhancePageElement;
|
|
77
|
-
let renderPageCalled = false;
|
|
78
76
|
const renderPage = async (opts = {}) => {
|
|
79
|
-
|
|
80
|
-
const wrapped = withScriptNonce(enhancePageElement(opts), input.scriptNonce);
|
|
77
|
+
const wrapped = withScriptNonce(enhancePageElement(typeof opts === "function" ? { enhanceComponent: opts } : opts), input.scriptNonce);
|
|
81
78
|
return {
|
|
82
79
|
html: await readStreamAsText(await input.renderToReadableStream(wrapped)),
|
|
83
80
|
head: []
|
|
84
81
|
};
|
|
85
82
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
renderPage,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
...input.context
|
|
99
|
-
});
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.error("[vinext] _document.getInitialProps() threw:", err);
|
|
102
|
-
return {
|
|
103
|
-
status: "consumed",
|
|
104
|
-
docProps: {},
|
|
105
|
-
head: []
|
|
106
|
-
};
|
|
107
|
-
}
|
|
83
|
+
const docInitialProps = await DocCtor.getInitialProps({
|
|
84
|
+
renderPage,
|
|
85
|
+
defaultGetInitialProps: async (ctx) => {
|
|
86
|
+
const result = await ctx.renderPage({ enhanceApp: (App) => (props) => React.createElement(App, props) });
|
|
87
|
+
return {
|
|
88
|
+
html: result.html,
|
|
89
|
+
head: result.head ?? [],
|
|
90
|
+
styles: void 0
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
...input.context
|
|
94
|
+
});
|
|
108
95
|
const { html: _html, head: rawHead, styles: _styles, ...docProps } = docInitialProps ?? {};
|
|
109
96
|
const head = Array.isArray(rawHead) ? rawHead : [];
|
|
110
|
-
if (!
|
|
111
|
-
status: "consumed",
|
|
112
|
-
docProps,
|
|
113
|
-
head
|
|
114
|
-
};
|
|
115
|
-
if (!docInitialProps || typeof docInitialProps.html !== "string") {
|
|
116
|
-
console.error(`[vinext] "${DocCtor.displayName ?? DocCtor.name ?? "Document"}.getInitialProps()" did not return an object with a string "html" prop`);
|
|
117
|
-
return {
|
|
118
|
-
status: "consumed",
|
|
119
|
-
docProps,
|
|
120
|
-
head
|
|
121
|
-
};
|
|
122
|
-
}
|
|
97
|
+
if (!docInitialProps || typeof docInitialProps.html !== "string") throw new Error(`"${DocCtor.displayName ?? DocCtor.name ?? "Document"}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`);
|
|
123
98
|
let stylesHTML = "";
|
|
124
|
-
if (docInitialProps.styles != null)
|
|
125
|
-
stylesHTML = await input.renderStylesToString(React.createElement(React.Fragment, null, docInitialProps.styles));
|
|
126
|
-
} catch (err) {
|
|
127
|
-
console.error("[vinext] Failed to render _document.getInitialProps() styles:", err);
|
|
128
|
-
}
|
|
99
|
+
if (docInitialProps.styles != null) stylesHTML = await input.renderStylesToString(React.createElement(React.Fragment, null, docInitialProps.styles));
|
|
129
100
|
return {
|
|
130
101
|
status: "rendered",
|
|
131
102
|
bodyHtml: docInitialProps.html,
|
|
@@ -127,8 +127,8 @@ function parseCookieLocaleFromHeader(cookieHeader, i18nConfig) {
|
|
|
127
127
|
} catch {
|
|
128
128
|
return null;
|
|
129
129
|
}
|
|
130
|
-
|
|
131
|
-
return null;
|
|
130
|
+
const lowerValue = value.toLowerCase();
|
|
131
|
+
return i18nConfig.locales.find((locale) => locale.toLowerCase() === lowerValue) ?? null;
|
|
132
132
|
}
|
|
133
133
|
function formatLocalizedRootPath(locale, defaultLocale, basePath = "", trailingSlash = false, search = "") {
|
|
134
134
|
if (locale.toLowerCase() === defaultLocale.toLowerCase()) return void 0;
|
|
@@ -198,7 +198,9 @@ function applyBotETagAndCheck(cachedResponse, html, options) {
|
|
|
198
198
|
function rewritePagesCachedHtml(cachedHtml, freshBody, nextDataScript) {
|
|
199
199
|
const bodyStart = cachedHtml.indexOf("<div id=\"__next\">");
|
|
200
200
|
const contentStart = bodyStart >= 0 ? bodyStart + 17 : -1;
|
|
201
|
-
const
|
|
201
|
+
const canonicalNextDataStart = cachedHtml.search(/<script\b(?=[^>]*\bid=["']__NEXT_DATA__["'])(?=[^>]*\btype=["']application\/json["'])[^>]*>/);
|
|
202
|
+
const legacyNextDataStart = cachedHtml.indexOf("<script>window.__NEXT_DATA__");
|
|
203
|
+
const nextDataStart = canonicalNextDataStart >= 0 ? canonicalNextDataStart : legacyNextDataStart;
|
|
202
204
|
if (contentStart >= 0 && nextDataStart >= 0) {
|
|
203
205
|
const region = cachedHtml.slice(contentStart, nextDataStart);
|
|
204
206
|
const lastCloseDiv = region.lastIndexOf("</div>");
|
|
@@ -184,7 +184,7 @@ function createPagesPageHandler(opts) {
|
|
|
184
184
|
const pagesResolvedUrl = (new URL(routeUrl, originalRequestUrl).pathname || "/") + originalRequestUrl.search;
|
|
185
185
|
const pageDataResult = await resolvePagesPageData({
|
|
186
186
|
isDataReq,
|
|
187
|
-
err,
|
|
187
|
+
err: err instanceof Error ? err : void 0,
|
|
188
188
|
applyRequestContexts: applySSRContext,
|
|
189
189
|
buildId,
|
|
190
190
|
deploymentId: process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID,
|
|
@@ -327,6 +327,7 @@ function createPagesPageHandler(opts) {
|
|
|
327
327
|
return typeof wrapWithRouterContext === "function" ? wrapWithRouterContext(el) : el;
|
|
328
328
|
},
|
|
329
329
|
DocumentComponent,
|
|
330
|
+
err: err instanceof Error ? err : void 0,
|
|
330
331
|
flushPreloads: typeof flushPreloads === "function" ? flushPreloads : void 0,
|
|
331
332
|
fontLinkHeader,
|
|
332
333
|
fontPreloads: allFontPreloads,
|
|
@@ -344,6 +345,7 @@ function createPagesPageHandler(opts) {
|
|
|
344
345
|
pageProps,
|
|
345
346
|
props: renderProps,
|
|
346
347
|
params,
|
|
348
|
+
query,
|
|
347
349
|
renderDocumentToString(element) {
|
|
348
350
|
return renderToStringAsync(element);
|
|
349
351
|
},
|
|
@@ -66,6 +66,7 @@ type RenderPagesPageResponseOptions = {
|
|
|
66
66
|
*/
|
|
67
67
|
enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined;
|
|
68
68
|
DocumentComponent: ComponentType | null;
|
|
69
|
+
err?: Error;
|
|
69
70
|
flushPreloads?: (() => Promise<void> | void) | undefined;
|
|
70
71
|
fontLinkHeader: string;
|
|
71
72
|
fontPreloads: PagesFontPreload[];
|
|
@@ -95,6 +96,7 @@ type RenderPagesPageResponseOptions = {
|
|
|
95
96
|
pageProps: Record<string, unknown>;
|
|
96
97
|
props?: Record<string, unknown>;
|
|
97
98
|
params: Record<string, unknown>;
|
|
99
|
+
query?: Record<string, unknown>;
|
|
98
100
|
renderDocumentToString: (element: ReactNode) => Promise<string>;
|
|
99
101
|
renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;
|
|
100
102
|
resetSSRHead?: (() => void) | undefined;
|
|
@@ -7,7 +7,7 @@ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders }
|
|
|
7
7
|
import { buildMissIsrCacheControl } from "./isr-decision.js";
|
|
8
8
|
import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
|
|
9
9
|
import { withScriptNonce } from "../shims/script-nonce-context.js";
|
|
10
|
-
import {
|
|
10
|
+
import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
|
|
11
11
|
import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
|
|
12
12
|
import { readStreamAsText } from "../utils/text-stream.js";
|
|
13
13
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
@@ -89,8 +89,7 @@ function buildPagesNextDataScript(options) {
|
|
|
89
89
|
...options.nextData?.__vinext,
|
|
90
90
|
...options.vinext
|
|
91
91
|
};
|
|
92
|
-
|
|
93
|
-
return createInlineScriptTag(`window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}`, options.scriptNonce);
|
|
92
|
+
return `<script id="__NEXT_DATA__" type="application/json"${createNonceAttribute(options.scriptNonce)}>${options.safeJsonStringify(nextDataPayload)}<\/script>`;
|
|
94
93
|
}
|
|
95
94
|
async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, options) {
|
|
96
95
|
if (options.DocumentComponent) {
|
|
@@ -197,8 +196,9 @@ async function renderPagesPageResponse(options) {
|
|
|
197
196
|
renderStylesToString: async (element) => readStreamAsText(await options.renderToReadableStream(element)),
|
|
198
197
|
scriptNonce: options.scriptNonce,
|
|
199
198
|
context: {
|
|
199
|
+
err: options.err,
|
|
200
200
|
pathname: options.routePattern,
|
|
201
|
-
query: options.params,
|
|
201
|
+
query: options.query ?? options.params,
|
|
202
202
|
asPath: options.routeUrl
|
|
203
203
|
}
|
|
204
204
|
});
|
|
@@ -10,7 +10,7 @@ function buildPagesReadinessNextData(options) {
|
|
|
10
10
|
const hasAppGip = typeof options.appComponent?.getInitialProps === "function";
|
|
11
11
|
return {
|
|
12
12
|
gssp: hasPageGssp,
|
|
13
|
-
gsp: hasPageGsp,
|
|
13
|
+
gsp: hasPageGsp ? true : void 0,
|
|
14
14
|
gip: hasPageGip,
|
|
15
15
|
appGip: hasAppGip,
|
|
16
16
|
autoExport: !hasPageGssp && !hasPageGsp && !hasPageGip && !hasAppGip,
|
|
@@ -7,6 +7,8 @@ type PagesRenderOptions = {
|
|
|
7
7
|
renderErrorPageOnMiss?: boolean;
|
|
8
8
|
originalUrl?: string;
|
|
9
9
|
};
|
|
10
|
+
type FilesystemRoutePhase = "direct" | "beforeFiles" | "afterFiles" | "fallback";
|
|
11
|
+
declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>): Promise<Response | false>;
|
|
10
12
|
type MiddlewareResult = {
|
|
11
13
|
continue: boolean;
|
|
12
14
|
redirectUrl?: string;
|
|
@@ -53,15 +55,13 @@ type PagesPipelineDeps = {
|
|
|
53
55
|
*/
|
|
54
56
|
proxyExternal?: ((currentRequest: Request, externalUrl: string) => Promise<Response>) | null;
|
|
55
57
|
/**
|
|
56
|
-
* Optional
|
|
58
|
+
* Optional filesystem/static-asset probe supplied by each runtime adapter.
|
|
57
59
|
* Called post-middleware (so middleware can intercept/redirect public files) with the
|
|
58
60
|
* original basePath-stripped pathname and the staged middleware response headers.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* to fall through to rewrites/render. Worker/dev adapters omit this — their public files
|
|
62
|
-
* are served by the asset binding / Vite respectively.
|
|
61
|
+
* Node may write directly to `res` and return true; dev/Workers return a Response.
|
|
62
|
+
* Resolves false to continue through rewrites, API routes, and page rendering.
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
serveFilesystemRoute?: ((requestPathname: string, stagedHeaders: HeaderRecord, phase: FilesystemRoutePhase) => Promise<boolean | Response>) | null;
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
67
|
* Wrap an adapter's `runMiddleware` callback so middleware receives the original
|
|
@@ -111,4 +111,4 @@ type PagesPipelineResult = {
|
|
|
111
111
|
*/
|
|
112
112
|
declare function runPagesRequest(request: Request, deps: PagesPipelineDeps): Promise<PagesPipelineResult>;
|
|
113
113
|
//#endregion
|
|
114
|
-
export { MiddlewareResult, PagesPipelineDeps, PagesPipelineResult, PagesRenderOptions, runPagesRequest, wrapMiddlewareWithBasePath };
|
|
114
|
+
export { FilesystemRoutePhase, MiddlewareResult, PagesPipelineDeps, PagesPipelineResult, PagesRenderOptions, fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
|
|
@@ -5,6 +5,14 @@ import { mergeRewriteQuery } from "../utils/query.js";
|
|
|
5
5
|
import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
|
|
6
6
|
import { mergeHeaders } from "./worker-utils.js";
|
|
7
7
|
//#region src/server/pages-request-pipeline.ts
|
|
8
|
+
async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset) {
|
|
9
|
+
if (phase === "direct" || request.method !== "GET" && request.method !== "HEAD" || requestPathname === "/api" || requestPathname.startsWith("/api/")) return false;
|
|
10
|
+
const assetUrl = new URL(request.url);
|
|
11
|
+
assetUrl.pathname = requestPathname;
|
|
12
|
+
assetUrl.search = "";
|
|
13
|
+
const response = await fetchAsset(new Request(assetUrl, request));
|
|
14
|
+
return response.status === 404 ? false : response;
|
|
15
|
+
}
|
|
8
16
|
/**
|
|
9
17
|
* Wrap an adapter's `runMiddleware` callback so middleware receives the original
|
|
10
18
|
* (pre-basePath-stripping) URL. Adapters strip the basePath before handing the
|
|
@@ -72,6 +80,15 @@ async function runPagesRequest(request, deps) {
|
|
|
72
80
|
let resolvedUrl = originalResolvedUrl;
|
|
73
81
|
const middlewareHeaders = {};
|
|
74
82
|
let middlewareStatus;
|
|
83
|
+
const serveFilesystemRoute = async (requestPathname, phase) => {
|
|
84
|
+
if (!deps.serveFilesystemRoute) return null;
|
|
85
|
+
const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase);
|
|
86
|
+
if (served instanceof Response) return {
|
|
87
|
+
type: "response",
|
|
88
|
+
response: mergeHeaders(served, middlewareHeaders, middlewareStatus)
|
|
89
|
+
};
|
|
90
|
+
return served ? { type: "handled" } : null;
|
|
91
|
+
};
|
|
75
92
|
if (typeof deps.runMiddleware === "function") {
|
|
76
93
|
const result = await deps.runMiddleware(request, deps.ctx ?? null, { isDataRequest });
|
|
77
94
|
if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
|
|
@@ -132,9 +149,8 @@ async function runPagesRequest(request, deps) {
|
|
|
132
149
|
type: "response",
|
|
133
150
|
response: mergeHeaders(await proxyExternal(request, resolvedUrl), middlewareHeaders, void 0)
|
|
134
151
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
152
|
+
const directFilesystemResult = await serveFilesystemRoute(pathname, "direct");
|
|
153
|
+
if (directFilesystemResult) return directFilesystemResult;
|
|
138
154
|
let configRewriteFired = false;
|
|
139
155
|
for (const rewrite of configRewrites.beforeFiles ?? []) {
|
|
140
156
|
const rewritten = matchRewrite(matchResolvedPathname(resolvedPathname), [rewrite], rewriteRequestContext(), basePathState);
|
|
@@ -148,6 +164,10 @@ async function runPagesRequest(request, deps) {
|
|
|
148
164
|
configRewriteFired = true;
|
|
149
165
|
}
|
|
150
166
|
}
|
|
167
|
+
if (configRewriteFired) {
|
|
168
|
+
const beforeFilesResult = await serveFilesystemRoute(resolvedPathname, "beforeFiles");
|
|
169
|
+
if (beforeFilesResult) return beforeFilesResult;
|
|
170
|
+
}
|
|
151
171
|
if (basePath && !hadBasePath && !configRewriteFired) return {
|
|
152
172
|
type: "response",
|
|
153
173
|
response: new Response("This page could not be found", {
|
|
@@ -155,27 +175,33 @@ async function runPagesRequest(request, deps) {
|
|
|
155
175
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
156
176
|
})
|
|
157
177
|
};
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
const handleResolvedApiRoute = async () => {
|
|
179
|
+
const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, i18nConfig);
|
|
180
|
+
const apiLookupPathname = apiLookupUrl.split("?")[0];
|
|
181
|
+
if (!apiLookupPathname.startsWith("/api/") && apiLookupPathname !== "/api") return null;
|
|
182
|
+
if (typeof deps.handleApi === "function") {
|
|
183
|
+
let apiRequest = request;
|
|
184
|
+
if (basePath && hadBasePath) {
|
|
185
|
+
const apiRequestUrl = new URL(request.url);
|
|
186
|
+
apiRequestUrl.pathname = addBasePathToPathname(apiRequestUrl.pathname, basePath);
|
|
187
|
+
apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
type: "response",
|
|
191
|
+
defaultContentType: "application/octet-stream",
|
|
192
|
+
response: mergeHeaders(await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null), middlewareHeaders, middlewareStatus)
|
|
193
|
+
};
|
|
166
194
|
}
|
|
167
195
|
return {
|
|
168
|
-
type: "
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
type: "api",
|
|
197
|
+
apiUrl: apiLookupUrl,
|
|
198
|
+
stagedHeaders: middlewareHeaders,
|
|
199
|
+
requestHeaders: request.headers,
|
|
200
|
+
middlewareStatus
|
|
171
201
|
};
|
|
172
|
-
} else return {
|
|
173
|
-
type: "api",
|
|
174
|
-
apiUrl: apiLookupUrl,
|
|
175
|
-
stagedHeaders: middlewareHeaders,
|
|
176
|
-
requestHeaders: request.headers,
|
|
177
|
-
middlewareStatus
|
|
178
202
|
};
|
|
203
|
+
const apiResult = await handleResolvedApiRoute();
|
|
204
|
+
if (apiResult) return apiResult;
|
|
179
205
|
let pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
|
|
180
206
|
let resolvedPathnameChanged = false;
|
|
181
207
|
if (!pageMatch || pageMatch.route.isDynamic) for (const rewrite of configRewrites.afterFiles ?? []) {
|
|
@@ -188,6 +214,10 @@ async function runPagesRequest(request, deps) {
|
|
|
188
214
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
|
|
189
215
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
190
216
|
resolvedPathnameChanged = true;
|
|
217
|
+
const afterFilesFilesystemResult = await serveFilesystemRoute(resolvedPathname, "afterFiles");
|
|
218
|
+
if (afterFilesFilesystemResult) return afterFilesFilesystemResult;
|
|
219
|
+
const afterFilesApiResult = await handleResolvedApiRoute();
|
|
220
|
+
if (afterFilesApiResult) return afterFilesApiResult;
|
|
191
221
|
pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
|
|
192
222
|
if (pageMatch) break;
|
|
193
223
|
}
|
|
@@ -208,6 +238,10 @@ async function runPagesRequest(request, deps) {
|
|
|
208
238
|
};
|
|
209
239
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
210
240
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
241
|
+
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
242
|
+
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
243
|
+
const fallbackApiResult = await handleResolvedApiRoute();
|
|
244
|
+
if (fallbackApiResult) return fallbackApiResult;
|
|
211
245
|
renderPageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
|
|
212
246
|
refreshDataRewriteHeader();
|
|
213
247
|
if (renderPageMatch) break;
|
|
@@ -228,6 +262,10 @@ async function runPagesRequest(request, deps) {
|
|
|
228
262
|
};
|
|
229
263
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
230
264
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
265
|
+
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
266
|
+
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
267
|
+
const fallbackApiResult = await handleResolvedApiRoute();
|
|
268
|
+
if (fallbackApiResult) return fallbackApiResult;
|
|
231
269
|
response = await deps.renderPage(request, resolvedUrl, void 0, stagedHeaders);
|
|
232
270
|
matchedFallbackRewrite = true;
|
|
233
271
|
if (response.status !== 404) break;
|
|
@@ -250,6 +288,10 @@ async function runPagesRequest(request, deps) {
|
|
|
250
288
|
};
|
|
251
289
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
252
290
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
291
|
+
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
292
|
+
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
293
|
+
const fallbackApiResult = await handleResolvedApiRoute();
|
|
294
|
+
if (fallbackApiResult) return fallbackApiResult;
|
|
253
295
|
if (deps.matchPageRoute?.(resolvedPathname, request)) break;
|
|
254
296
|
}
|
|
255
297
|
refreshDataRewriteHeader();
|
|
@@ -264,4 +306,4 @@ async function runPagesRequest(request, deps) {
|
|
|
264
306
|
};
|
|
265
307
|
}
|
|
266
308
|
//#endregion
|
|
267
|
-
export { runPagesRequest, wrapMiddlewareWithBasePath };
|
|
309
|
+
export { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
|
|
@@ -41,6 +41,8 @@ import { IncomingMessage, ServerResponse } from "node:http";
|
|
|
41
41
|
*/
|
|
42
42
|
declare function resolveServerEntryImportUrl(entryPath: string): string;
|
|
43
43
|
declare function importServerEntryModule(entryPath: string): Promise<any>;
|
|
44
|
+
/** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
|
|
45
|
+
declare function readNodeStream(req: IncomingMessage): ReadableStream<Uint8Array>;
|
|
44
46
|
type ProdServerOptions = {
|
|
45
47
|
/** Port to listen on */port?: number; /** Host to bind to */
|
|
46
48
|
host?: string; /** Path to the build output directory */
|
|
@@ -143,4 +145,4 @@ declare function resolveAppRouterPrerenderSeeder(entryModule: unknown): AppRoute
|
|
|
143
145
|
*/
|
|
144
146
|
declare function resolveAppRouterAssetPath(pathname: string, assetPathPrefix: string, assetPrefix: string): string | null;
|
|
145
147
|
//#endregion
|
|
146
|
-
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
|
|
148
|
+
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
|
|
@@ -109,11 +109,42 @@ async function importServerEntryModule(entryPath) {
|
|
|
109
109
|
}
|
|
110
110
|
/** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
|
|
111
111
|
function readNodeStream(req) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
let cancelled = false;
|
|
113
|
+
let cleanup = () => {};
|
|
114
|
+
return new ReadableStream({
|
|
115
|
+
start(controller) {
|
|
116
|
+
cleanup = () => {
|
|
117
|
+
req.off("data", onData);
|
|
118
|
+
req.off("end", onEnd);
|
|
119
|
+
req.off("error", onError);
|
|
120
|
+
};
|
|
121
|
+
const onData = (chunk) => {
|
|
122
|
+
if (cancelled) return;
|
|
123
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
124
|
+
if ((controller.desiredSize ?? 0) <= 0) req.pause();
|
|
125
|
+
};
|
|
126
|
+
const onEnd = () => {
|
|
127
|
+
cleanup();
|
|
128
|
+
if (!cancelled) controller.close();
|
|
129
|
+
};
|
|
130
|
+
const onError = (error) => {
|
|
131
|
+
cleanup();
|
|
132
|
+
if (!cancelled) controller.error(error);
|
|
133
|
+
};
|
|
134
|
+
req.on("data", onData);
|
|
135
|
+
req.on("end", onEnd);
|
|
136
|
+
req.on("error", onError);
|
|
137
|
+
req.pause();
|
|
138
|
+
},
|
|
139
|
+
pull() {
|
|
140
|
+
if (!cancelled) req.resume();
|
|
141
|
+
},
|
|
142
|
+
cancel() {
|
|
143
|
+
cancelled = true;
|
|
144
|
+
cleanup();
|
|
145
|
+
req.resume();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
117
148
|
}
|
|
118
149
|
/** Content types that benefit from compression. */
|
|
119
150
|
const COMPRESSIBLE_TYPES = new Set([
|
|
@@ -514,7 +545,7 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret) {
|
|
|
514
545
|
headers
|
|
515
546
|
};
|
|
516
547
|
if (hasBody) {
|
|
517
|
-
init.body =
|
|
548
|
+
init.body = readNodeStream(req);
|
|
518
549
|
init.duplex = "half";
|
|
519
550
|
}
|
|
520
551
|
return new Request(url, init);
|
|
@@ -1008,7 +1039,7 @@ async function startPagesRouterServer(options) {
|
|
|
1008
1039
|
const protocol = resolveRequestProtocol(req);
|
|
1009
1040
|
const hostHeader = resolveRequestHost(req, `${host}:${port}`);
|
|
1010
1041
|
const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
|
|
1011
|
-
const isDataRequest =
|
|
1042
|
+
const isDataRequest = isDataReq;
|
|
1012
1043
|
const reqHeaders = filterInternalHeaders(rawReqHeaders);
|
|
1013
1044
|
const method = req.method ?? "GET";
|
|
1014
1045
|
const hasBody = method !== "GET" && method !== "HEAD";
|
|
@@ -1036,8 +1067,8 @@ async function startPagesRouterServer(options) {
|
|
|
1036
1067
|
originalUrl: originalRenderUrl
|
|
1037
1068
|
}) : null,
|
|
1038
1069
|
handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
|
|
1039
|
-
|
|
1040
|
-
if (requestPathname === "/" || requestPathname.startsWith("/api/") || requestPathname.startsWith(`/_next/static/`)) return false;
|
|
1070
|
+
serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
|
|
1071
|
+
if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
|
|
1041
1072
|
return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
|
|
1042
1073
|
}
|
|
1043
1074
|
});
|
|
@@ -1088,4 +1119,4 @@ async function startPagesRouterServer(options) {
|
|
|
1088
1119
|
};
|
|
1089
1120
|
}
|
|
1090
1121
|
//#endregion
|
|
1091
|
-
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
|
|
1122
|
+
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePathSeparators } from "../utils/path.js";
|
|
2
|
-
import "../utils/asset-prefix.js";
|
|
2
|
+
import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
//#region src/server/static-file-cache.ts
|
|
@@ -23,6 +23,7 @@ const CONTENT_TYPES = {
|
|
|
23
23
|
".css": "text/css",
|
|
24
24
|
".html": "text/html",
|
|
25
25
|
".json": "application/json",
|
|
26
|
+
".txt": "text/plain; charset=utf-8",
|
|
26
27
|
".png": "image/png",
|
|
27
28
|
".jpg": "image/jpeg",
|
|
28
29
|
".jpeg": "image/jpeg",
|
|
@@ -165,9 +166,20 @@ var StaticFileCache = class StaticFileCache {
|
|
|
165
166
|
function etagFromFilenameHash(relativePath, ext) {
|
|
166
167
|
const basename = path.basename(relativePath, ext);
|
|
167
168
|
const lastDash = basename.lastIndexOf("-");
|
|
168
|
-
if (lastDash
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
if (lastDash !== -1 && lastDash !== basename.length - 1) {
|
|
170
|
+
const suffix = basename.slice(lastDash + 1);
|
|
171
|
+
if (suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)) return `W/"${suffix}"`;
|
|
172
|
+
}
|
|
173
|
+
const normalizedPath = normalizePathSeparators(relativePath);
|
|
174
|
+
const managedMediaSegment = `${ASSET_PREFIX_URL_DIR}/media/`;
|
|
175
|
+
if (normalizedPath.startsWith(managedMediaSegment) || normalizedPath.includes(`/${managedMediaSegment}`)) {
|
|
176
|
+
const lastDot = basename.lastIndexOf(".");
|
|
177
|
+
if (lastDot !== -1) {
|
|
178
|
+
const suffix = basename.slice(lastDot + 1);
|
|
179
|
+
if (/^[0-9a-f]{8}$/.test(suffix)) return `W/"${suffix}"`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
171
183
|
}
|
|
172
184
|
function buildVariant(info, baseHeaders, encoding) {
|
|
173
185
|
return {
|
|
@@ -2,17 +2,28 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/shims/before-interactive-context.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* A `<Script strategy="beforeInteractive">` captured during SSR.
|
|
6
6
|
*
|
|
7
7
|
* The Script shim hands these records to the SSR pipeline through
|
|
8
8
|
* `BeforeInteractiveContext` instead of rendering the `<script>` tag inline.
|
|
9
9
|
* The pipeline then emits the captured tag immediately after `<head>` opens,
|
|
10
10
|
* so the script runs before any React-hoisted stylesheets or modulepreload
|
|
11
11
|
* links. Matches the standard no-flash dark-mode pattern.
|
|
12
|
+
*
|
|
13
|
+
* Both inline (`children`/`dangerouslySetInnerHTML`) and external (`src`)
|
|
14
|
+
* beforeInteractive scripts flow through here, mirroring Next.js which registers
|
|
15
|
+
* inline and src beforeInteractive scripts equally in the App Router runtime
|
|
16
|
+
* (`(self.__next_s=...).push(...)`). An entry has `src` set for external
|
|
17
|
+
* scripts and `innerHTML` set for inline scripts (never both).
|
|
12
18
|
*/
|
|
13
19
|
type BeforeInteractiveInlineScript = {
|
|
14
|
-
/** Optional id attribute. */id?: string; /**
|
|
15
|
-
|
|
20
|
+
/** Optional id attribute. */id?: string; /** External script URL. Set for src beforeInteractive scripts. */
|
|
21
|
+
src?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Pre-escaped inline content (already passed through `escapeInlineContent`).
|
|
24
|
+
* Set for inline beforeInteractive scripts; omitted for src scripts.
|
|
25
|
+
*/
|
|
26
|
+
innerHTML?: string; /** Nonce to emit on the `<script>` tag, when CSP is enabled. */
|
|
16
27
|
nonce?: string;
|
|
17
28
|
/**
|
|
18
29
|
* Additional HTML attributes to emit on the tag. Booleans render as the
|