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
|
@@ -26,6 +26,7 @@ import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFr
|
|
|
26
26
|
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
27
27
|
import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
28
28
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
29
|
+
import { createPagesDevModuleUrl } from "./pages-dev-module-url.js";
|
|
29
30
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
30
31
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
31
32
|
import { callDocumentGetInitialProps } from "./document-initial-head.js";
|
|
@@ -100,7 +101,7 @@ const STREAM_BODY_MARKER = "<!--VINEXT_STREAM_BODY-->";
|
|
|
100
101
|
* shell sooner).
|
|
101
102
|
*/
|
|
102
103
|
async function streamPageToResponse(res, element, options) {
|
|
103
|
-
const { url, server, fontHeadHTML, scripts, DocumentComponent, statusCode = 200, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead } = options;
|
|
104
|
+
const { url, server, fontHeadHTML, scripts, DocumentComponent, statusCode = 200, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, bufferBodyBeforeHeaders = false } = options;
|
|
104
105
|
const documentRenderPage = await runDocumentRenderPage({
|
|
105
106
|
DocumentComponent,
|
|
106
107
|
enhancePageElement,
|
|
@@ -144,6 +145,7 @@ async function streamPageToResponse(res, element, options) {
|
|
|
144
145
|
const markerIdx = transformedShell.indexOf(STREAM_BODY_MARKER);
|
|
145
146
|
const prefix = transformedShell.slice(0, markerIdx);
|
|
146
147
|
const suffix = transformedShell.slice(markerIdx + 25);
|
|
148
|
+
const bufferedBody = bufferBodyBeforeHeaders ? await new Response(bodyStream).text() : null;
|
|
147
149
|
const headers = {
|
|
148
150
|
"Content-Type": "text/html",
|
|
149
151
|
"Transfer-Encoding": "chunked"
|
|
@@ -152,6 +154,10 @@ async function streamPageToResponse(res, element, options) {
|
|
|
152
154
|
else headers[key] = val;
|
|
153
155
|
res.writeHead(statusCode, headers);
|
|
154
156
|
res.write(prefix);
|
|
157
|
+
if (bufferedBody !== null) {
|
|
158
|
+
res.end(bufferedBody + suffix);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
155
161
|
const reader = bodyStream.getReader();
|
|
156
162
|
try {
|
|
157
163
|
for (;;) {
|
|
@@ -622,9 +628,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
622
628
|
}) : React.createElement(pageModule.default, freshPageProps);
|
|
623
629
|
if (routerShim.wrapWithRouterContext) el = routerShim.wrapWithRouterContext(el);
|
|
624
630
|
const freshBody = await renderIsrPassToStringAsync(withScriptNonce(el, scriptNonce));
|
|
625
|
-
const viteRoot = server.config
|
|
626
|
-
const
|
|
627
|
-
const
|
|
631
|
+
const viteRoot = server.config.root;
|
|
632
|
+
const viteBase = server.config.base;
|
|
633
|
+
const regenPageUrl = createPagesDevModuleUrl(viteRoot, route.filePath, viteBase);
|
|
634
|
+
const regenAppUrl = RegenApp ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), viteBase) : null;
|
|
628
635
|
const freshPagesNextData = {
|
|
629
636
|
...pagesNextData,
|
|
630
637
|
__vinext: {
|
|
@@ -634,7 +641,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
634
641
|
hasMiddleware
|
|
635
642
|
}
|
|
636
643
|
};
|
|
637
|
-
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script
|
|
644
|
+
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script id="__NEXT_DATA__" type="application/json">${safeJsonStringify({
|
|
638
645
|
props: freshRenderProps,
|
|
639
646
|
page: patternToNextFormat(route.pattern),
|
|
640
647
|
query: params,
|
|
@@ -645,7 +652,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
645
652
|
defaultLocale: currentDefaultLocale,
|
|
646
653
|
domainLocales,
|
|
647
654
|
...freshPagesNextData
|
|
648
|
-
})}
|
|
655
|
+
})}<\/script>`}\n ${cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/)?.[0] ?? ""}</body></html>`, freshRenderProps), revalidate);
|
|
649
656
|
setRevalidateDuration(cacheKey, revalidate);
|
|
650
657
|
}
|
|
651
658
|
}
|
|
@@ -809,8 +816,11 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
809
816
|
}
|
|
810
817
|
if (allFontStyles.length > 0) fontHeadHTML += `<style data-vinext-fonts${nonceAttr}>${allFontStyles.join("\n")}</style>\n `;
|
|
811
818
|
const viteRoot = server.config.root;
|
|
812
|
-
const
|
|
813
|
-
const
|
|
819
|
+
const viteBase = server.config.base;
|
|
820
|
+
const pageModuleUrl = createPagesDevModuleUrl(viteRoot, route.filePath, viteBase);
|
|
821
|
+
const pageModuleSource = createPagesDevModuleUrl(viteRoot, route.filePath, "/");
|
|
822
|
+
const appModuleUrl = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), viteBase) : null;
|
|
823
|
+
const appModuleSource = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), "/") : null;
|
|
814
824
|
const serializedPagesNextData = {
|
|
815
825
|
...pagesNextData,
|
|
816
826
|
__vinext: {
|
|
@@ -825,14 +835,22 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
825
835
|
import "vinext/instrumentation-client";
|
|
826
836
|
import React from "react";
|
|
827
837
|
import { hydrateRoot } from "react-dom/client";
|
|
828
|
-
import Router, { wrapWithRouterContext } from "next/router";
|
|
838
|
+
import Router, { wrapWithRouterContext, _initializePagesRouterReadyFromNextData } from "next/router";
|
|
829
839
|
|
|
840
|
+
const nextDataElement = document.getElementById("__NEXT_DATA__");
|
|
841
|
+
if (nextDataElement?.textContent) {
|
|
842
|
+
window.__NEXT_DATA__ = JSON.parse(nextDataElement.textContent);
|
|
843
|
+
window.__VINEXT_LOCALE__ = window.__NEXT_DATA__.locale;
|
|
844
|
+
window.__VINEXT_LOCALES__ = window.__NEXT_DATA__.locales;
|
|
845
|
+
window.__VINEXT_DEFAULT_LOCALE__ = window.__NEXT_DATA__.defaultLocale;
|
|
846
|
+
}
|
|
830
847
|
const nextData = window.__NEXT_DATA__;
|
|
848
|
+
_initializePagesRouterReadyFromNextData(nextData);
|
|
831
849
|
const props = nextData.props && typeof nextData.props === "object" ? nextData.props : {};
|
|
832
850
|
const rawPageProps = props.pageProps;
|
|
833
851
|
const pageProps = rawPageProps && typeof rawPageProps === "object" ? rawPageProps : {};
|
|
834
|
-
window.__VINEXT_PAGE_LOADERS__ = { [nextData.page]: () => import("${
|
|
835
|
-
window.__VINEXT_APP_LOADER__ = ${
|
|
852
|
+
window.__VINEXT_PAGE_LOADERS__ = { [nextData.page]: () => import("${pageModuleSource}") };
|
|
853
|
+
window.__VINEXT_APP_LOADER__ = ${appModuleSource ? `() => import("${appModuleSource}")` : "undefined"};
|
|
836
854
|
|
|
837
855
|
async function hydrate() {
|
|
838
856
|
let hydrateRootOptions;
|
|
@@ -847,11 +865,11 @@ async function hydrate() {
|
|
|
847
865
|
};
|
|
848
866
|
}
|
|
849
867
|
|
|
850
|
-
const pageModule = await import("${
|
|
868
|
+
const pageModule = await import("${pageModuleSource}");
|
|
851
869
|
const PageComponent = pageModule.default;
|
|
852
870
|
let element;
|
|
853
|
-
${
|
|
854
|
-
const appModule = await import("${
|
|
871
|
+
${appModuleSource ? `
|
|
872
|
+
const appModule = await import("${appModuleSource}");
|
|
855
873
|
const AppComponent = appModule.default;
|
|
856
874
|
window.__VINEXT_APP__ = AppComponent;
|
|
857
875
|
element = React.createElement(AppComponent, {
|
|
@@ -880,7 +898,7 @@ async function hydrate() {
|
|
|
880
898
|
}
|
|
881
899
|
hydrate();
|
|
882
900
|
<\/script>`;
|
|
883
|
-
const nextDataScript =
|
|
901
|
+
const nextDataScript = `<script id="__NEXT_DATA__" type="application/json"${nonceAttr}>${safeJsonStringify({
|
|
884
902
|
props: renderProps,
|
|
885
903
|
page: patternToNextFormat(route.pattern),
|
|
886
904
|
query: params,
|
|
@@ -891,7 +909,7 @@ hydrate();
|
|
|
891
909
|
defaultLocale: currentDefaultLocale,
|
|
892
910
|
domainLocales,
|
|
893
911
|
...serializedPagesNextData
|
|
894
|
-
})}
|
|
912
|
+
})}<\/script>`;
|
|
895
913
|
const docPath = path.join(pagesDir, "_document");
|
|
896
914
|
let DocumentComponent = null;
|
|
897
915
|
if (findFileWithExtensions(docPath, matcher)) try {
|
|
@@ -939,7 +957,8 @@ hydrate();
|
|
|
939
957
|
const traceHTML = getClientTraceMetadataHTML(clientTraceMetadata);
|
|
940
958
|
return traceHTML ? `${headHTML}\n ${traceHTML}` : headHTML;
|
|
941
959
|
},
|
|
942
|
-
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0
|
|
960
|
+
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0,
|
|
961
|
+
bufferBodyBeforeHeaders: true
|
|
943
962
|
});
|
|
944
963
|
_renderEnd = now();
|
|
945
964
|
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext(null);
|
|
@@ -1015,41 +1034,67 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
|
|
|
1015
1034
|
if (!wrapFn) try {
|
|
1016
1035
|
wrapFn = (await importModule(runner, "next/router")).wrapWithRouterContext;
|
|
1017
1036
|
} catch {}
|
|
1018
|
-
let element;
|
|
1019
|
-
if (AppComponent) element = createElement(AppComponent, {
|
|
1020
|
-
Component: ErrorComponent,
|
|
1021
|
-
pageProps: errorProps
|
|
1022
|
-
});
|
|
1023
|
-
else element = createElement(ErrorComponent, errorProps);
|
|
1024
|
-
if (wrapFn) element = wrapFn(element);
|
|
1025
|
-
const bodyHtml = await renderToStringAsync(element);
|
|
1026
|
-
let html;
|
|
1027
1037
|
let DocumentComponent = null;
|
|
1028
1038
|
const docPathErr = path.join(pagesDir, "_document");
|
|
1029
1039
|
if (findFileWithExtensions(docPathErr, matcher)) try {
|
|
1030
1040
|
DocumentComponent = (await importModule(runner, docPathErr)).default ?? null;
|
|
1031
1041
|
} catch {}
|
|
1042
|
+
const createErrorElement = (FinalApp, FinalComponent) => {
|
|
1043
|
+
let errorElement = FinalApp ? createElement(FinalApp, {
|
|
1044
|
+
Component: FinalComponent,
|
|
1045
|
+
pageProps: errorProps
|
|
1046
|
+
}) : createElement(FinalComponent, errorProps);
|
|
1047
|
+
if (wrapFn) errorElement = wrapFn(errorElement);
|
|
1048
|
+
return errorElement;
|
|
1049
|
+
};
|
|
1050
|
+
const element = createErrorElement(AppComponent, ErrorComponent);
|
|
1051
|
+
const headShim = await importModule(runner, "next/head");
|
|
1052
|
+
if (typeof headShim.resetSSRHead === "function") headShim.resetSSRHead();
|
|
1032
1053
|
if (DocumentComponent) {
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1054
|
+
const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`;
|
|
1055
|
+
await streamPageToResponse(res, element, {
|
|
1056
|
+
url,
|
|
1057
|
+
server,
|
|
1058
|
+
fontHeadHTML: "",
|
|
1059
|
+
scripts: "",
|
|
1060
|
+
DocumentComponent,
|
|
1061
|
+
statusCode,
|
|
1062
|
+
documentContext: {
|
|
1063
|
+
err,
|
|
1064
|
+
pathname: errorPathname,
|
|
1065
|
+
query: parseQueryString(url),
|
|
1066
|
+
asPath: url,
|
|
1067
|
+
req,
|
|
1068
|
+
res
|
|
1069
|
+
},
|
|
1070
|
+
enhancePageElement: (renderPageOpts) => {
|
|
1071
|
+
let FinalApp = AppComponent;
|
|
1072
|
+
let FinalComponent = ErrorComponent;
|
|
1073
|
+
if (renderPageOpts.enhanceApp && FinalApp) FinalApp = renderPageOpts.enhanceApp(FinalApp);
|
|
1074
|
+
if (renderPageOpts.enhanceComponent) FinalComponent = renderPageOpts.enhanceComponent(FinalComponent);
|
|
1075
|
+
return createErrorElement(FinalApp, FinalComponent);
|
|
1076
|
+
},
|
|
1077
|
+
getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function" ? headShim.getSSRHeadHTML() : "",
|
|
1078
|
+
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0
|
|
1079
|
+
});
|
|
1080
|
+
} else {
|
|
1081
|
+
const html = `<!DOCTYPE html>
|
|
1039
1082
|
<html>
|
|
1040
1083
|
<head>
|
|
1041
1084
|
<meta charset="utf-8" />
|
|
1042
1085
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1043
1086
|
</head>
|
|
1044
1087
|
<body>
|
|
1045
|
-
<div id="__next">${
|
|
1088
|
+
<div id="__next">${await renderToStringAsync(element)}</div>
|
|
1046
1089
|
</body>
|
|
1047
1090
|
</html>`;
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1091
|
+
const transformedHtml = await server.transformIndexHtml(url, html);
|
|
1092
|
+
res.writeHead(statusCode, { "Content-Type": "text/html" });
|
|
1093
|
+
res.end(transformedHtml);
|
|
1094
|
+
}
|
|
1051
1095
|
return;
|
|
1052
1096
|
} catch {
|
|
1097
|
+
if (res.headersSent || res.writableEnded) return;
|
|
1053
1098
|
continue;
|
|
1054
1099
|
}
|
|
1055
1100
|
if (statusCode === 404) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { removeTrailingSlash } from "../utils/base-path.js";
|
|
2
|
-
import { checkHasConditions, requestContextFromRequest, safeRegExp } from "../config/config-matchers.js";
|
|
2
|
+
import { checkHasConditions, isSafeRegex, requestContextFromRequest, safeRegExp } from "../config/config-matchers.js";
|
|
3
3
|
//#region src/server/middleware-matcher.ts
|
|
4
4
|
const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
5
5
|
headers: new Headers(),
|
|
@@ -7,6 +7,7 @@ const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
|
7
7
|
query: new URLSearchParams(),
|
|
8
8
|
host: ""
|
|
9
9
|
};
|
|
10
|
+
const UNSAFE_MATCHER_PATTERN = Symbol("unsafe matcher pattern");
|
|
10
11
|
const _mwPatternCache = /* @__PURE__ */ new Map();
|
|
11
12
|
function matchesMiddleware(pathname, matcher, request, i18nConfig) {
|
|
12
13
|
if (!matcher) return true;
|
|
@@ -55,6 +56,7 @@ function matchPattern(pathname, pattern) {
|
|
|
55
56
|
cached = compileMatcherPattern(pattern);
|
|
56
57
|
_mwPatternCache.set(pattern, cached);
|
|
57
58
|
}
|
|
59
|
+
if (cached === UNSAFE_MATCHER_PATTERN) return true;
|
|
58
60
|
if (cached === null) return pathname === pattern;
|
|
59
61
|
return cached.test(pathname);
|
|
60
62
|
}
|
|
@@ -74,7 +76,7 @@ function extractConstraint(str, re) {
|
|
|
74
76
|
}
|
|
75
77
|
function compileMatcherPattern(pattern) {
|
|
76
78
|
const hasConstraints = /:[\w-]+[*+]?\(/.test(pattern);
|
|
77
|
-
if (!hasConstraints && (pattern.includes("(") || pattern.includes("\\"))) return
|
|
79
|
+
if (!hasConstraints && (pattern.includes("(") || pattern.includes("\\"))) return compileMatcherRegExp("^" + pattern + "$", pattern);
|
|
78
80
|
let regexStr = "";
|
|
79
81
|
const tokenRe = /\/:([\w-]+)\*|\/:([\w-]+)\+|:([\w-]+)|[.]|[^/:.]+|./g;
|
|
80
82
|
let tok;
|
|
@@ -94,7 +96,14 @@ function compileMatcherPattern(pattern) {
|
|
|
94
96
|
else regexStr += group;
|
|
95
97
|
} else if (tok[0] === ".") regexStr += "\\.";
|
|
96
98
|
else regexStr += tok[0];
|
|
97
|
-
return
|
|
99
|
+
return compileMatcherRegExp("^" + regexStr + "$", pattern);
|
|
100
|
+
}
|
|
101
|
+
function compileMatcherRegExp(regexPattern, sourcePattern) {
|
|
102
|
+
if (!isSafeRegex(regexPattern)) {
|
|
103
|
+
console.warn(`[vinext] Rejecting potentially unsafe middleware matcher (ReDoS risk): ${sourcePattern}\n Middleware will run for all paths to avoid bypassing request guards.\n Simplify the matcher to avoid nested repetition.`);
|
|
104
|
+
return UNSAFE_MATCHER_PATTERN;
|
|
105
|
+
}
|
|
106
|
+
return safeRegExp(regexPattern);
|
|
98
107
|
}
|
|
99
108
|
//#endregion
|
|
100
109
|
export { matchPattern, matchesMiddleware };
|
|
@@ -35,10 +35,9 @@ type ExecuteMiddlewareOptions = {
|
|
|
35
35
|
i18nConfig?: NextI18nConfig | null;
|
|
36
36
|
includeErrorDetails?: boolean;
|
|
37
37
|
/**
|
|
38
|
-
* Whether the incoming request was a Next.js `_next/data`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* flag from the raw incoming headers and forward it explicitly.
|
|
38
|
+
* Whether the incoming request was recognized as a Next.js `_next/data`
|
|
39
|
+
* fetch. Internal headers are stripped before middleware runs, so adapters
|
|
40
|
+
* must derive and forward this from trusted URL normalization.
|
|
42
41
|
*/
|
|
43
42
|
isDataRequest?: boolean;
|
|
44
43
|
isProxy: boolean;
|
|
@@ -142,6 +142,8 @@ async function executeMiddleware(options) {
|
|
|
142
142
|
response: internalServerErrorResponse(options.includeErrorDetails ? "Middleware Error: " + (e instanceof Error ? e.message : String(e)) : "Internal Server Error"),
|
|
143
143
|
waitUntilPromises
|
|
144
144
|
};
|
|
145
|
+
} finally {
|
|
146
|
+
if (process.env.NODE_ENV !== "development" && nextRequest.body) nextRequest.body.cancel().catch(() => {});
|
|
145
147
|
}
|
|
146
148
|
const waitUntilPromises = drainFetchEvent(fetchEvent);
|
|
147
149
|
if (!response) return {
|
|
@@ -2,18 +2,9 @@ import { RouteManifest } from "../routing/app-route-graph.js";
|
|
|
2
2
|
import { CacheEntryReuseDecision, CacheEntryReuseProof } from "./cache-proof.js";
|
|
3
3
|
import { AppElementsSlotBinding } from "./app-elements-wire.js";
|
|
4
4
|
import { NavigationTrace, NavigationTraceFields } from "./navigation-trace.js";
|
|
5
|
+
import { OperationLane, OperationToken } from "./operation-token.js";
|
|
5
6
|
|
|
6
7
|
//#region src/server/navigation-planner.d.ts
|
|
7
|
-
type OperationLane = "hmr" | "navigation" | "prefetch" | "refresh" | "server-action" | "traverse";
|
|
8
|
-
type OperationToken = {
|
|
9
|
-
operationId: number;
|
|
10
|
-
lane: OperationLane;
|
|
11
|
-
baseVisibleCommitVersion: number;
|
|
12
|
-
graphVersion: string | null;
|
|
13
|
-
deploymentVersion: string | null;
|
|
14
|
-
targetSnapshotFingerprint: string;
|
|
15
|
-
cacheVariantFingerprint?: string;
|
|
16
|
-
};
|
|
17
8
|
type RouteSnapshot = {
|
|
18
9
|
interception: InterceptionSnapshot | null;
|
|
19
10
|
interceptionContext: string | null;
|
|
@@ -85,7 +76,7 @@ type CommitProposal = {
|
|
|
85
76
|
targetSnapshot: RouteSnapshot;
|
|
86
77
|
};
|
|
87
78
|
type NoCommitReason = "prefetchOnly";
|
|
88
|
-
type HardNavigationReason = "cacheProofRejected" | "interceptionProofRejected" | "rootBoundaryChanged";
|
|
79
|
+
type HardNavigationReason = "cacheProofRejected" | "cacheReuseTokenRejected" | "interceptionProofRejected" | "rootBoundaryChanged";
|
|
89
80
|
type RootBoundaryTransition = "currentRootBoundary" | "rootBoundaryChanged" | "rootBoundaryUnknown";
|
|
90
81
|
type NavigationDecision = {
|
|
91
82
|
kind: "requestWork";
|
|
@@ -314,4 +305,4 @@ declare const navigationPlanner: {
|
|
|
314
305
|
resolveSameLayoutAncestorPersistence: typeof resolveSameLayoutAncestorPersistence;
|
|
315
306
|
};
|
|
316
307
|
//#endregion
|
|
317
|
-
export { EarlyNavigationIntentDecision, EarlyNavigationIntentFacts, FlightResult, InterceptionSnapshot, MountedParallelSlotSnapshot, NavigationDecision, NavigationEvent, NavigationPlannerInput, NavigationPlannerState, NavigationReuseDecision, NavigationReuseFacts, OperationLane, OperationToken, ParallelSlotBindingSnapshot, RefreshScope, RootBoundaryTransition, RouteSnapshot, RscFetchResultDecision, RscFetchResultFacts, RscNavigationErrorDecision, RscNavigationErrorFacts, ServerActionResultDecision, ServerActionResultFacts, TraverseDirection, VisitedResponseCacheCandidateFacts, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
308
|
+
export { EarlyNavigationIntentDecision, EarlyNavigationIntentFacts, FlightResult, InterceptionSnapshot, MountedParallelSlotSnapshot, NavigationDecision, NavigationEvent, NavigationPlannerInput, NavigationPlannerState, NavigationReuseDecision, NavigationReuseFacts, type OperationLane, type OperationToken, ParallelSlotBindingSnapshot, RefreshScope, RootBoundaryTransition, RouteSnapshot, RscFetchResultDecision, RscFetchResultFacts, RscNavigationErrorDecision, RscNavigationErrorFacts, ServerActionResultDecision, ServerActionResultFacts, TraverseDirection, VisitedResponseCacheCandidateFacts, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
@@ -6,6 +6,7 @@ import "./app-elements.js";
|
|
|
6
6
|
import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
7
7
|
import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
8
8
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
9
|
+
import { verifyOperationTokenForCacheReuse } from "./operation-token.js";
|
|
9
10
|
//#region src/server/navigation-planner.ts
|
|
10
11
|
const ROUTE_INTERCEPTION_CONTEXT_SEPARATOR = "\0";
|
|
11
12
|
const CACHE_ENTRY_PROOF_MISSING_CODE = "CP_CACHE_ENTRY_PROOF_MISSING";
|
|
@@ -531,6 +532,18 @@ function createCacheProofRejectedDecision(options) {
|
|
|
531
532
|
url: options.event.result.href
|
|
532
533
|
};
|
|
533
534
|
}
|
|
535
|
+
function createCacheReuseTokenRejectedDecision(options) {
|
|
536
|
+
return {
|
|
537
|
+
kind: "hardNavigate",
|
|
538
|
+
reason: "cacheReuseTokenRejected",
|
|
539
|
+
token: options.event.token,
|
|
540
|
+
trace: createNavigationTrace(NavigationTraceReasonCodes.cacheReuseTokenRejected, {
|
|
541
|
+
...options.traceFields,
|
|
542
|
+
cacheReuseTokenReason: options.reason
|
|
543
|
+
}),
|
|
544
|
+
url: options.event.result.href
|
|
545
|
+
};
|
|
546
|
+
}
|
|
534
547
|
function createAcceptedCacheProofTraceFields(traceFields, decision) {
|
|
535
548
|
if (decision === null) return traceFields;
|
|
536
549
|
return {
|
|
@@ -619,6 +632,17 @@ function planFlightResponseArrived(options) {
|
|
|
619
632
|
traceFields
|
|
620
633
|
});
|
|
621
634
|
const acceptedCacheEntryDecision = cacheEntryProofEvaluation.decision;
|
|
635
|
+
if (acceptedCacheEntryDecision !== null) {
|
|
636
|
+
const reuseVerdict = verifyOperationTokenForCacheReuse(options.event.token, {
|
|
637
|
+
graphVersion: options.routeManifest?.graphVersion ?? null,
|
|
638
|
+
installedCacheVariantFingerprint: null
|
|
639
|
+
});
|
|
640
|
+
if (!reuseVerdict.authorized) return createCacheReuseTokenRejectedDecision({
|
|
641
|
+
event: options.event,
|
|
642
|
+
reason: reuseVerdict.reason,
|
|
643
|
+
traceFields
|
|
644
|
+
});
|
|
645
|
+
}
|
|
622
646
|
const commitTraceFields = createAcceptedCacheProofTraceFields(traceFields, acceptedCacheEntryDecision);
|
|
623
647
|
const cacheEntryProposalFields = createCacheEntryProposalFields(acceptedCacheEntryDecision);
|
|
624
648
|
if (targetSnapshot.interception !== null) {
|
|
@@ -3,6 +3,7 @@ declare const NAVIGATION_TRACE_SCHEMA_VERSION = 0;
|
|
|
3
3
|
type NavigationTraceSchemaVersion = 0;
|
|
4
4
|
declare const NavigationTraceReasonCodes: {
|
|
5
5
|
cacheProofRejected: "NC_CACHE_REJECT";
|
|
6
|
+
cacheReuseTokenRejected: "NC_CACHE_TOKEN_REJECT";
|
|
6
7
|
commitCurrent: "NC_COMMIT";
|
|
7
8
|
crossDocumentFlight: "NC_CROSS_DOC_FLIGHT";
|
|
8
9
|
fetchFresh: "NC_FETCH_FRESH";
|
|
@@ -42,7 +43,7 @@ declare const NavigationTraceTransactionCodes: {
|
|
|
42
43
|
type NavigationTraceReasonCode = (typeof NavigationTraceReasonCodes)[keyof typeof NavigationTraceReasonCodes];
|
|
43
44
|
type NavigationTraceTransactionCode = (typeof NavigationTraceTransactionCodes)[keyof typeof NavigationTraceTransactionCodes];
|
|
44
45
|
type NavigationTraceCode = NavigationTraceReasonCode | NavigationTraceTransactionCode;
|
|
45
|
-
type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "fetchResultSource" | "freshFetchReason" | "operationLane" | "pendingOperationId" | "redirectDepth" | "redirectSignal" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
|
|
46
|
+
type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "cacheReuseTokenReason" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "fetchResultSource" | "freshFetchReason" | "operationLane" | "pendingOperationId" | "redirectDepth" | "redirectSignal" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
|
|
46
47
|
type NavigationTraceFieldValue = string | number | boolean | null;
|
|
47
48
|
type NavigationTraceFields = Readonly<Partial<Record<NavigationTraceFieldName, NavigationTraceFieldValue>>>;
|
|
48
49
|
type NavigationTraceEntry = Readonly<{
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const NAVIGATION_TRACE_SCHEMA_VERSION = 0;
|
|
3
3
|
const NavigationTraceReasonCodes = {
|
|
4
4
|
cacheProofRejected: "NC_CACHE_REJECT",
|
|
5
|
+
cacheReuseTokenRejected: "NC_CACHE_TOKEN_REJECT",
|
|
5
6
|
commitCurrent: "NC_COMMIT",
|
|
6
7
|
crossDocumentFlight: "NC_CROSS_DOC_FLIGHT",
|
|
7
8
|
fetchFresh: "NC_FETCH_FRESH",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/server/operation-token.d.ts
|
|
2
|
+
type OperationLane = "hmr" | "navigation" | "prefetch" | "refresh" | "server-action" | "traverse";
|
|
3
|
+
type OperationToken = {
|
|
4
|
+
operationId: number;
|
|
5
|
+
lane: OperationLane;
|
|
6
|
+
navigationId: number;
|
|
7
|
+
baseVisibleCommitVersion: number;
|
|
8
|
+
graphVersion: string | null;
|
|
9
|
+
deploymentVersion: string | null;
|
|
10
|
+
targetSnapshotFingerprint: string;
|
|
11
|
+
cacheVariantFingerprint?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const verifiedOperationTokenBrand: unique symbol;
|
|
14
|
+
type VerifiedOperationToken = OperationToken & {
|
|
15
|
+
readonly [verifiedOperationTokenBrand]: true;
|
|
16
|
+
};
|
|
17
|
+
type OperationTokenAuthority = {
|
|
18
|
+
activeNavigationId: number;
|
|
19
|
+
visibleCommitVersion: number;
|
|
20
|
+
graphVersion: string | null;
|
|
21
|
+
installedCacheVariantFingerprint: string | null;
|
|
22
|
+
};
|
|
23
|
+
type OperationTokenDimension = "navigation" | "visibleCommit" | "graphVersion" | "cacheVariant";
|
|
24
|
+
type OperationTokenRejectionReason = "staleNavigation" | "staleVisibleCommit" | "graphVersionMismatch" | "graphVersionMissing" | "cacheVariantMismatch" | "cacheVariantMissing";
|
|
25
|
+
type OperationTokenVerdict = {
|
|
26
|
+
readonly authorized: true;
|
|
27
|
+
readonly token: VerifiedOperationToken;
|
|
28
|
+
} | {
|
|
29
|
+
readonly authorized: false;
|
|
30
|
+
readonly reason: OperationTokenRejectionReason;
|
|
31
|
+
};
|
|
32
|
+
type OperationTokenVerificationPolicy = {
|
|
33
|
+
check: readonly OperationTokenDimension[];
|
|
34
|
+
require: readonly OperationTokenDimension[];
|
|
35
|
+
};
|
|
36
|
+
declare function verifyOperationToken(token: OperationToken, authority: OperationTokenAuthority, policy: OperationTokenVerificationPolicy): OperationTokenVerdict;
|
|
37
|
+
declare function verifyOperationTokenForCommit(token: OperationToken, authority: Pick<OperationTokenAuthority, "activeNavigationId" | "visibleCommitVersion">): OperationTokenVerdict;
|
|
38
|
+
declare function verifyOperationTokenForCacheReuse(token: OperationToken, authority: Pick<OperationTokenAuthority, "graphVersion" | "installedCacheVariantFingerprint">): OperationTokenVerdict;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { OperationLane, OperationToken, OperationTokenAuthority, OperationTokenRejectionReason, OperationTokenVerdict, OperationTokenVerificationPolicy, VerifiedOperationToken, verifyOperationToken, verifyOperationTokenForCacheReuse, verifyOperationTokenForCommit };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region src/server/operation-token.ts
|
|
2
|
+
const DIMENSION_ORDER = [
|
|
3
|
+
"navigation",
|
|
4
|
+
"visibleCommit",
|
|
5
|
+
"graphVersion",
|
|
6
|
+
"cacheVariant"
|
|
7
|
+
];
|
|
8
|
+
function evaluateDimension(dimension, token, authority) {
|
|
9
|
+
switch (dimension) {
|
|
10
|
+
case "navigation": return token.navigationId === authority.activeNavigationId ? { kind: "satisfied" } : {
|
|
11
|
+
kind: "mismatch",
|
|
12
|
+
reason: "staleNavigation"
|
|
13
|
+
};
|
|
14
|
+
case "visibleCommit": return token.baseVisibleCommitVersion === authority.visibleCommitVersion ? { kind: "satisfied" } : {
|
|
15
|
+
kind: "mismatch",
|
|
16
|
+
reason: "staleVisibleCommit"
|
|
17
|
+
};
|
|
18
|
+
case "graphVersion":
|
|
19
|
+
if (token.graphVersion === null || authority.graphVersion === null) return {
|
|
20
|
+
kind: "absent",
|
|
21
|
+
missingReason: "graphVersionMissing"
|
|
22
|
+
};
|
|
23
|
+
return token.graphVersion === authority.graphVersion ? { kind: "satisfied" } : {
|
|
24
|
+
kind: "mismatch",
|
|
25
|
+
reason: "graphVersionMismatch"
|
|
26
|
+
};
|
|
27
|
+
case "cacheVariant": {
|
|
28
|
+
const tokenVariant = token.cacheVariantFingerprint;
|
|
29
|
+
const installedVariant = authority.installedCacheVariantFingerprint;
|
|
30
|
+
if (tokenVariant === void 0 || installedVariant === null) return {
|
|
31
|
+
kind: "absent",
|
|
32
|
+
missingReason: "cacheVariantMissing"
|
|
33
|
+
};
|
|
34
|
+
return tokenVariant === installedVariant ? { kind: "satisfied" } : {
|
|
35
|
+
kind: "mismatch",
|
|
36
|
+
reason: "cacheVariantMismatch"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
default: throw new Error("[vinext] Unknown operation-token dimension: " + String(dimension));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function verifyOperationToken(token, authority, policy) {
|
|
43
|
+
const required = new Set(policy.require);
|
|
44
|
+
const evaluated = new Set([...policy.check, ...policy.require]);
|
|
45
|
+
for (const dimension of DIMENSION_ORDER) {
|
|
46
|
+
if (!evaluated.has(dimension)) continue;
|
|
47
|
+
const status = evaluateDimension(dimension, token, authority);
|
|
48
|
+
if (status.kind === "mismatch") return {
|
|
49
|
+
authorized: false,
|
|
50
|
+
reason: status.reason
|
|
51
|
+
};
|
|
52
|
+
if (status.kind === "absent" && required.has(dimension)) return {
|
|
53
|
+
authorized: false,
|
|
54
|
+
reason: status.missingReason
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
authorized: true,
|
|
59
|
+
token
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function verifyOperationTokenForCommit(token, authority) {
|
|
63
|
+
return verifyOperationToken(token, {
|
|
64
|
+
activeNavigationId: authority.activeNavigationId,
|
|
65
|
+
visibleCommitVersion: authority.visibleCommitVersion,
|
|
66
|
+
graphVersion: token.graphVersion,
|
|
67
|
+
installedCacheVariantFingerprint: token.cacheVariantFingerprint ?? null
|
|
68
|
+
}, {
|
|
69
|
+
check: ["navigation", "visibleCommit"],
|
|
70
|
+
require: ["navigation", "visibleCommit"]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function verifyOperationTokenForCacheReuse(token, authority) {
|
|
74
|
+
return verifyOperationToken(token, {
|
|
75
|
+
activeNavigationId: token.navigationId,
|
|
76
|
+
visibleCommitVersion: token.baseVisibleCommitVersion,
|
|
77
|
+
graphVersion: authority.graphVersion,
|
|
78
|
+
installedCacheVariantFingerprint: authority.installedCacheVariantFingerprint
|
|
79
|
+
}, {
|
|
80
|
+
check: ["graphVersion", "cacheVariant"],
|
|
81
|
+
require: []
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { verifyOperationToken, verifyOperationTokenForCacheReuse, verifyOperationTokenForCommit };
|
|
@@ -116,6 +116,6 @@ type NormalizePagesDataRequestResult = {
|
|
|
116
116
|
* Extracted from `entries/pages-server-entry.ts` so both `renderPage` and
|
|
117
117
|
* `runMiddleware` share a single implementation.
|
|
118
118
|
*/
|
|
119
|
-
declare function normalizePagesDataRequest(request: Request, buildId: string | null): NormalizePagesDataRequestResult;
|
|
119
|
+
declare function normalizePagesDataRequest(request: Request, buildId: string | null, basePath?: string): NormalizePagesDataRequestResult;
|
|
120
120
|
//#endregion
|
|
121
121
|
export { buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizePagesDataRequest, parseNextDataPathname };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
1
2
|
import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
|
|
2
3
|
//#region src/server/pages-data-route.ts
|
|
3
4
|
/**
|
|
@@ -123,16 +124,18 @@ function buildNextDataNotFoundResponse() {
|
|
|
123
124
|
* Extracted from `entries/pages-server-entry.ts` so both `renderPage` and
|
|
124
125
|
* `runMiddleware` share a single implementation.
|
|
125
126
|
*/
|
|
126
|
-
function normalizePagesDataRequest(request, buildId) {
|
|
127
|
+
function normalizePagesDataRequest(request, buildId, basePath = "") {
|
|
127
128
|
const reqUrl = new URL(request.url);
|
|
128
|
-
|
|
129
|
+
const hadBasePath = !!basePath && hasBasePath(reqUrl.pathname, basePath);
|
|
130
|
+
const dataPathname = basePath ? stripBasePath(reqUrl.pathname, basePath) : reqUrl.pathname;
|
|
131
|
+
if (!isNextDataPathname(dataPathname)) return {
|
|
129
132
|
isDataReq: false,
|
|
130
133
|
request,
|
|
131
134
|
normalizedPathname: null,
|
|
132
135
|
search: "",
|
|
133
136
|
notFoundResponse: null
|
|
134
137
|
};
|
|
135
|
-
const dataMatch = buildId ? parseNextDataPathname(
|
|
138
|
+
const dataMatch = buildId ? parseNextDataPathname(dataPathname, buildId) : null;
|
|
136
139
|
if (!dataMatch) return {
|
|
137
140
|
isDataReq: false,
|
|
138
141
|
request,
|
|
@@ -141,7 +144,7 @@ function normalizePagesDataRequest(request, buildId) {
|
|
|
141
144
|
notFoundResponse: buildNextDataNotFoundResponse()
|
|
142
145
|
};
|
|
143
146
|
const normalizedUrl = new URL(reqUrl);
|
|
144
|
-
normalizedUrl.pathname = dataMatch.pagePathname;
|
|
147
|
+
normalizedUrl.pathname = hadBasePath ? addBasePathToPathname(dataMatch.pagePathname, basePath) : dataMatch.pagePathname;
|
|
145
148
|
return {
|
|
146
149
|
isDataReq: true,
|
|
147
150
|
request: new Request(normalizedUrl, request),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
//#region src/server/pages-dev-module-url.ts
|
|
3
|
+
function normalizeBase(base) {
|
|
4
|
+
if (!base || base === "/") return "/";
|
|
5
|
+
return `/${base.replace(/^\/+|\/+$/g, "")}/`;
|
|
6
|
+
}
|
|
7
|
+
function encodeModulePath(modulePath) {
|
|
8
|
+
return encodeURI(modulePath).replace(/%5B/gi, "[").replace(/%5D/gi, "]").replace(/\?/g, "%3F").replace(/#/g, "%23");
|
|
9
|
+
}
|
|
10
|
+
function createPagesDevModuleUrl(viteRoot, moduleFilePath, viteBase) {
|
|
11
|
+
const relativePath = (/^[A-Za-z]:[\\/]/.test(viteRoot) ? path.win32 : path).relative(viteRoot, moduleFilePath).replace(/\\/g, "/");
|
|
12
|
+
return normalizeBase(viteBase) + encodeModulePath(relativePath);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createPagesDevModuleUrl };
|
|
@@ -45,12 +45,6 @@ type DocumentRenderPageInput = {
|
|
|
45
45
|
* the head nodes returned by `getInitialProps` (forward them to
|
|
46
46
|
* `setDocumentInitialHead()` — do NOT call
|
|
47
47
|
* `callDocumentGetInitialProps()` as well).
|
|
48
|
-
* - `consumed` — `getInitialProps` WAS invoked but no body was produced
|
|
49
|
-
* (it never called `renderPage`, returned no `{ html }`, or
|
|
50
|
-
* threw). Callers must NOT re-invoke `getInitialProps` (that
|
|
51
|
-
* would call it a second time) — render the streaming body,
|
|
52
|
-
* spread `docProps` (possibly empty) onto `<Document>`, and
|
|
53
|
-
* forward `head` to `setDocumentInitialHead()`.
|
|
54
48
|
*/
|
|
55
49
|
type RunDocumentRenderPageResult = {
|
|
56
50
|
status: "skipped";
|
|
@@ -60,10 +54,6 @@ type RunDocumentRenderPageResult = {
|
|
|
60
54
|
stylesHTML: string;
|
|
61
55
|
docProps: Record<string, unknown>;
|
|
62
56
|
head: ReactNode[];
|
|
63
|
-
} | {
|
|
64
|
-
status: "consumed";
|
|
65
|
-
docProps: Record<string, unknown>;
|
|
66
|
-
head: ReactNode[];
|
|
67
57
|
};
|
|
68
58
|
/**
|
|
69
59
|
* Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that
|
|
@@ -75,11 +65,10 @@ type RunDocumentRenderPageResult = {
|
|
|
75
65
|
* prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
|
|
76
66
|
* the `getInitialProps` + `renderPage` contract lives in one place.
|
|
77
67
|
*
|
|
78
|
-
* `getInitialProps` is invoked at most once here. When this returns `
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* this contract guarantees).
|
|
68
|
+
* `getInitialProps` is invoked at most once here. When this returns `rendered`,
|
|
69
|
+
* callers MUST treat that as the single invocation and must not call
|
|
70
|
+
* `loadUserDocumentInitialProps` again. Errors intentionally propagate to the
|
|
71
|
+
* Pages Router's normal error-page pipeline, matching Next.js.
|
|
83
72
|
*
|
|
84
73
|
* @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
|
|
85
74
|
*/
|