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
|
@@ -8,6 +8,7 @@ import "./app-bfcache-id.js";
|
|
|
8
8
|
import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
9
9
|
import { createRscRequestHeaders } from "./app-rsc-cache-busting.js";
|
|
10
10
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
11
|
+
import { verifyOperationTokenForCommit } from "./operation-token.js";
|
|
11
12
|
import { navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts } from "./navigation-planner.js";
|
|
12
13
|
import { createSnapshotPathAndSearch } from "../shims/navigation.js";
|
|
13
14
|
import { createCacheEntryReuseProof } from "./cache-proof.js";
|
|
@@ -192,7 +193,17 @@ function resolveServerActionRequestState(options) {
|
|
|
192
193
|
}
|
|
193
194
|
function resolvePendingNavigationCommitDispositionDecision(options) {
|
|
194
195
|
const traceFields = createPendingNavigationTraceFields(options);
|
|
195
|
-
|
|
196
|
+
const targetSnapshot = createPendingRouteSnapshot(options.pending);
|
|
197
|
+
const verdict = verifyOperationTokenForCommit(createPendingNavigationOperationToken({
|
|
198
|
+
pending: options.pending,
|
|
199
|
+
routeManifest: options.routeManifest ?? null,
|
|
200
|
+
startedNavigationId: options.startedNavigationId,
|
|
201
|
+
targetSnapshot
|
|
202
|
+
}), {
|
|
203
|
+
activeNavigationId: options.activeNavigationId,
|
|
204
|
+
visibleCommitVersion: options.currentState.visibleCommitVersion
|
|
205
|
+
});
|
|
206
|
+
if (!verdict.authorized) return {
|
|
196
207
|
disposition: "skip",
|
|
197
208
|
preserveElementIds: [],
|
|
198
209
|
trace: createNavigationTrace(NavigationTraceReasonCodes.staleOperation, traceFields)
|
|
@@ -202,6 +213,8 @@ function resolvePendingNavigationCommitDispositionDecision(options) {
|
|
|
202
213
|
pending: options.pending,
|
|
203
214
|
routeManifest: options.routeManifest ?? null,
|
|
204
215
|
targetHref: options.targetHref,
|
|
216
|
+
targetSnapshot,
|
|
217
|
+
token: verdict.token,
|
|
205
218
|
traceFields
|
|
206
219
|
}));
|
|
207
220
|
return mergeSkippedLayoutPreservation({
|
|
@@ -277,6 +290,7 @@ function createPendingNavigationOperationToken(options) {
|
|
|
277
290
|
deploymentVersion: null,
|
|
278
291
|
graphVersion: options.routeManifest?.graphVersion ?? null,
|
|
279
292
|
lane: options.pending.action.operation.lane,
|
|
293
|
+
navigationId: options.startedNavigationId,
|
|
280
294
|
operationId: options.pending.action.operation.id,
|
|
281
295
|
targetSnapshotFingerprint: createRootBoundarySnapshotFingerprint(options.targetSnapshot)
|
|
282
296
|
};
|
|
@@ -285,17 +299,11 @@ function createRootBoundarySnapshotFingerprint(snapshot) {
|
|
|
285
299
|
return `${snapshot.routeId}|root:${snapshot.rootBoundaryId ?? "unknown"}`;
|
|
286
300
|
}
|
|
287
301
|
function planPendingRootBoundaryFlightResponse(options) {
|
|
288
|
-
const targetSnapshot = createPendingRouteSnapshot(options.pending);
|
|
289
|
-
const token = createPendingNavigationOperationToken({
|
|
290
|
-
pending: options.pending,
|
|
291
|
-
routeManifest: options.routeManifest,
|
|
292
|
-
targetSnapshot
|
|
293
|
-
});
|
|
294
302
|
const cacheEntryReuseProof = options.pending.cacheEntryReuseProof;
|
|
295
303
|
return navigationPlanner.plan({
|
|
296
304
|
routeManifest: options.routeManifest,
|
|
297
305
|
state: {
|
|
298
|
-
nextOperationToken: token,
|
|
306
|
+
nextOperationToken: options.token,
|
|
299
307
|
traceFields: options.traceFields,
|
|
300
308
|
visibleCommitVersion: options.currentState.visibleCommitVersion,
|
|
301
309
|
visibleSnapshot: createVisibleRouteSnapshot(options.currentState)
|
|
@@ -304,10 +312,10 @@ function planPendingRootBoundaryFlightResponse(options) {
|
|
|
304
312
|
kind: "flightResponseArrived",
|
|
305
313
|
result: {
|
|
306
314
|
...cacheEntryReuseProof ? { cacheEntryReuseProof } : {},
|
|
307
|
-
href: options.targetHref ?? targetSnapshot.displayUrl,
|
|
308
|
-
targetSnapshot
|
|
315
|
+
href: options.targetHref ?? options.targetSnapshot.displayUrl,
|
|
316
|
+
targetSnapshot: options.targetSnapshot
|
|
309
317
|
},
|
|
310
|
-
token
|
|
318
|
+
token: options.token
|
|
311
319
|
}
|
|
312
320
|
});
|
|
313
321
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouteManifest } from "../routing/app-route-graph.js";
|
|
2
2
|
import { AppElements } from "./app-elements-wire.js";
|
|
3
3
|
import { NavigationTrace } from "./navigation-trace.js";
|
|
4
|
-
import { OperationLane } from "./
|
|
4
|
+
import { OperationLane } from "./operation-token.js";
|
|
5
5
|
import { ClientNavigationRenderSnapshot } from "../shims/navigation.js";
|
|
6
6
|
import { AppNavigationPayloadOrigin, AppRouterAction, AppRouterState, PendingNavigationCommit } from "./app-browser-state.js";
|
|
7
7
|
|
|
@@ -5,7 +5,9 @@ type PagesEntry = {
|
|
|
5
5
|
handleApiRoute?: (request: Request, url: string) => Promise<Response> | Response;
|
|
6
6
|
matchApiRoute?: (url: string, request: Request) => PagesRouteMatch | null;
|
|
7
7
|
matchPageRoute?: (url: string, request: Request) => PagesRouteMatch | null;
|
|
8
|
-
renderPage?: (request: Request, url: string, query: Record<string, unknown>, parsedUrl: unknown, middlewareRequestHeaders?: Headers | null
|
|
8
|
+
renderPage?: (request: Request, url: string, query: Record<string, unknown>, parsedUrl: unknown, middlewareRequestHeaders?: Headers | null, options?: {
|
|
9
|
+
isDataReq?: boolean;
|
|
10
|
+
}) => Promise<Response> | Response;
|
|
9
11
|
};
|
|
10
12
|
type PagesRouteMatch = {
|
|
11
13
|
route: {
|
|
@@ -44,10 +46,12 @@ type RenderPagesFallbackDependencies = {
|
|
|
44
46
|
type RenderPagesFallbackOptions = {
|
|
45
47
|
allowRscDocumentFallback?: boolean;
|
|
46
48
|
appRouteMatch?: AppRouteMatch | null;
|
|
49
|
+
isDataRequest?: boolean;
|
|
47
50
|
isRscRequest: boolean;
|
|
48
51
|
matchKind?: "dynamic" | "static";
|
|
49
52
|
middlewareContext: AppMiddlewareContext;
|
|
50
53
|
pathname?: string;
|
|
54
|
+
pagesDataRequest?: Request | null;
|
|
51
55
|
request: Request;
|
|
52
56
|
url: URL;
|
|
53
57
|
};
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
+
import { cloneRequestWithHeaders, cloneRequestWithUrl } from "./request-pipeline.js";
|
|
1
2
|
import { pagesRouteHasPriorityOverAppRoute } from "./hybrid-route-priority.js";
|
|
2
3
|
//#region src/server/app-pages-bridge.ts
|
|
3
4
|
/**
|
|
4
5
|
* Fallback handler to route App Router requests to the Pages Router when no App Router route matches.
|
|
5
6
|
*/
|
|
6
7
|
async function renderPagesFallback(options, dependencies) {
|
|
7
|
-
const { allowRscDocumentFallback = false, appRouteMatch = null, isRscRequest, matchKind, middlewareContext, pathname = options.url.pathname, request, url } = options;
|
|
8
|
+
const { allowRscDocumentFallback = false, appRouteMatch = null, isDataRequest = false, isRscRequest, matchKind, middlewareContext, pathname = options.url.pathname, pagesDataRequest = null, request, url } = options;
|
|
8
9
|
const { loadPagesEntry, buildRequestHeaders, decodePathParams, applyRouteHandlerMiddlewareContext, getDraftModeCookieHeader } = dependencies;
|
|
9
10
|
if (isRscRequest && !allowRscDocumentFallback) return null;
|
|
10
11
|
const pagesEntry = await loadPagesEntry();
|
|
11
12
|
const pagesRequestHeaders = middlewareContext.requestHeaders ? buildRequestHeaders(request.headers, middlewareContext.requestHeaders) : null;
|
|
12
13
|
let pagesRequest = request;
|
|
13
|
-
if (pagesRequestHeaders)
|
|
14
|
-
const pagesRequestInit = {
|
|
15
|
-
method: request.method,
|
|
16
|
-
headers: pagesRequestHeaders
|
|
17
|
-
};
|
|
18
|
-
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
19
|
-
pagesRequestInit.body = request.body;
|
|
20
|
-
pagesRequestInit.duplex = "half";
|
|
21
|
-
}
|
|
22
|
-
pagesRequest = new Request(request.url, pagesRequestInit);
|
|
23
|
-
}
|
|
14
|
+
if (pagesRequestHeaders) pagesRequest = cloneRequestWithHeaders(request, pagesRequestHeaders);
|
|
24
15
|
const queryIndex = pathname.indexOf("?");
|
|
25
16
|
const pagesPathname = queryIndex === -1 ? pathname : pathname.slice(0, queryIndex);
|
|
26
17
|
const pagesSearch = queryIndex === -1 ? url.search || "" : pathname.slice(queryIndex);
|
|
@@ -46,7 +37,8 @@ async function renderPagesFallback(options, dependencies) {
|
|
|
46
37
|
if (pageMatch !== null && matchKind === "static" && pageMatch.route.isDynamic) return null;
|
|
47
38
|
if (pageMatch !== null && matchKind === "dynamic" && !pageMatch.route.isDynamic) return null;
|
|
48
39
|
if (appRouteMatch !== null && (pageMatch === null || !pagesRouteHasPriorityOverAppRoute(pageMatch.route, appRouteMatch.route))) return null;
|
|
49
|
-
const
|
|
40
|
+
const renderRequest = pagesDataRequest ? cloneRequestWithUrl(pagesRequest, pagesDataRequest.url) : pagesRequest;
|
|
41
|
+
const pagesRes = isDataRequest ? await pagesEntry.renderPage(renderRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders, { isDataReq: true }) : await pagesEntry.renderPage(renderRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders);
|
|
50
42
|
if (pagesRes.status === 404 && pageMatch === null) return null;
|
|
51
43
|
return applyDraftModeCookie(pagesRes, getDraftModeCookieHeader());
|
|
52
44
|
}
|
|
@@ -130,10 +130,12 @@ type RenderPagesFallbackOptions = {
|
|
|
130
130
|
pattern: string;
|
|
131
131
|
};
|
|
132
132
|
} | null;
|
|
133
|
+
isDataRequest?: boolean;
|
|
133
134
|
isRscRequest: boolean;
|
|
134
135
|
matchKind?: "dynamic" | "static";
|
|
135
136
|
middlewareContext: AppRscMiddlewareContext;
|
|
136
137
|
pathname?: string;
|
|
138
|
+
pagesDataRequest?: Request | null;
|
|
137
139
|
request: Request;
|
|
138
140
|
url: URL;
|
|
139
141
|
};
|
|
@@ -144,6 +146,7 @@ type NavigationContextValue = {
|
|
|
144
146
|
};
|
|
145
147
|
type CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {
|
|
146
148
|
basePath: string;
|
|
149
|
+
buildId: string | null;
|
|
147
150
|
cacheComponents?: boolean;
|
|
148
151
|
clearRequestContext: () => void;
|
|
149
152
|
configHeaders: NextHeader[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
2
|
-
import { hasBasePath } from "../utils/base-path.js";
|
|
2
|
+
import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
3
3
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
4
4
|
import { ACTION_REVALIDATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER } from "./headers.js";
|
|
5
5
|
import { isExternalUrl, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
|
|
@@ -14,6 +14,7 @@ import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
|
14
14
|
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, resolveDevImageRedirect } from "./image-optimization.js";
|
|
15
15
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
16
16
|
import "./app-page-response.js";
|
|
17
|
+
import { buildNextDataNotFoundResponse, normalizePagesDataRequest } from "./pages-data-route.js";
|
|
17
18
|
import { createAppPprFallbackShells } from "./app-ppr-fallback-shell.js";
|
|
18
19
|
import { matchPrerenderRouteParamsPayload, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
|
|
19
20
|
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
@@ -108,7 +109,7 @@ function requestWithoutRscSuffix(request) {
|
|
|
108
109
|
url.pathname = pathname;
|
|
109
110
|
return cloneRequestWithUrl(request.body ? request.clone() : request, url.toString());
|
|
110
111
|
}
|
|
111
|
-
async function handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest) {
|
|
112
|
+
async function handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest, pagesDataRequest) {
|
|
112
113
|
const handlerStart = process.env.NODE_ENV !== "production" ? performance.now() : 0;
|
|
113
114
|
if (process.env.NODE_ENV !== "production") {
|
|
114
115
|
const originBlock = options.validateDevRequestOrigin?.(request);
|
|
@@ -119,6 +120,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
119
120
|
const { url, isRscRequest, interceptionContextHeader, mountedSlotsHeader, renderMode, clientReuseManifest } = normalized;
|
|
120
121
|
let { pathname, cleanPathname } = normalized;
|
|
121
122
|
let resolvedUrl = cleanPathname + url.search;
|
|
123
|
+
const originalResolvedUrl = resolvedUrl;
|
|
122
124
|
const getResolvedSearchParams = () => new URL(resolvedUrl, url).searchParams;
|
|
123
125
|
const canonicalPathname = cleanPathname;
|
|
124
126
|
const basePathState = {
|
|
@@ -142,6 +144,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
142
144
|
if (redirect) {
|
|
143
145
|
const destination = sanitizeDestination(redirectDestinationWithBasePath(redirect.destination, options.basePath));
|
|
144
146
|
const location = isRscRequest && request.headers.get("RSC") === "1" ? await createRscRedirectLocation(destination, request) : preserveRedirectDestinationQuery(destination, url.search);
|
|
147
|
+
if (isDataRequest) return new Response(null, {
|
|
148
|
+
status: 200,
|
|
149
|
+
headers: { "x-nextjs-redirect": location }
|
|
150
|
+
});
|
|
145
151
|
return new Response(null, {
|
|
146
152
|
status: redirect.permanent ? 308 : 307,
|
|
147
153
|
headers: { Location: location }
|
|
@@ -269,16 +275,28 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
269
275
|
});
|
|
270
276
|
if (serverActionResponse) return serverActionResponse;
|
|
271
277
|
let match = preActionMatch;
|
|
272
|
-
const renderPagesForMatchKind = async (matchKind) =>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
const renderPagesForMatchKind = async (matchKind) => {
|
|
279
|
+
const response = match === null || match.route.isDynamic ? await options.renderPagesFallback?.({
|
|
280
|
+
appRouteMatch: match ?? null,
|
|
281
|
+
allowRscDocumentFallback: didMiddlewareRewrite,
|
|
282
|
+
isDataRequest,
|
|
283
|
+
isRscRequest,
|
|
284
|
+
matchKind,
|
|
285
|
+
middlewareContext,
|
|
286
|
+
pathname: resolvedUrl,
|
|
287
|
+
pagesDataRequest,
|
|
288
|
+
request,
|
|
289
|
+
url
|
|
290
|
+
}) ?? null : null;
|
|
291
|
+
if (!response || !pagesDataRequest || resolvedUrl === originalResolvedUrl) return response;
|
|
292
|
+
const headers = new Headers(response.headers);
|
|
293
|
+
headers.set("x-nextjs-rewrite", resolvedUrl);
|
|
294
|
+
return new Response(response.body, {
|
|
295
|
+
headers,
|
|
296
|
+
status: response.status,
|
|
297
|
+
statusText: response.statusText
|
|
298
|
+
});
|
|
299
|
+
};
|
|
282
300
|
const staticPagesFallbackResponse = await renderPagesForMatchKind("static");
|
|
283
301
|
if (staticPagesFallbackResponse) {
|
|
284
302
|
options.clearRequestContext();
|
|
@@ -339,6 +357,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
339
357
|
}
|
|
340
358
|
if (match) break;
|
|
341
359
|
}
|
|
360
|
+
if (pagesDataRequest) {
|
|
361
|
+
options.clearRequestContext();
|
|
362
|
+
return buildNextDataNotFoundResponse();
|
|
363
|
+
}
|
|
342
364
|
if (!match) {
|
|
343
365
|
if (process.env.NODE_ENV !== "production" && canonicalPathname === "/favicon.ico") {
|
|
344
366
|
options.clearRequestContext();
|
|
@@ -461,13 +483,27 @@ function createAppRscHandler(options) {
|
|
|
461
483
|
options.registerCacheAdapters();
|
|
462
484
|
await options.ensureInstrumentation?.();
|
|
463
485
|
const mwCtx = rawRequest.headers.get(VINEXT_MW_CTX_HEADER);
|
|
464
|
-
const
|
|
486
|
+
const hasDataRequestHeader = rawRequest.headers.get("x-nextjs-data") === "1";
|
|
487
|
+
const pagesDataUrl = new URL(rawRequest.url);
|
|
488
|
+
const pagesDataInScope = !options.basePath || hasBasePath(pagesDataUrl.pathname, options.basePath);
|
|
489
|
+
if (pagesDataInScope) pagesDataUrl.pathname = stripBasePath(pagesDataUrl.pathname, options.basePath);
|
|
490
|
+
const pagesDataCandidate = pagesDataInScope ? cloneRequestWithUrl(rawRequest, pagesDataUrl.toString()) : null;
|
|
491
|
+
const pagesDataNormalization = options.renderPagesFallback && pagesDataCandidate ? normalizePagesDataRequest(pagesDataCandidate, options.buildId) : null;
|
|
492
|
+
if (pagesDataNormalization?.notFoundResponse) return pagesDataNormalization.notFoundResponse;
|
|
493
|
+
const isDataRequest = hasDataRequestHeader || pagesDataNormalization?.isDataReq === true;
|
|
465
494
|
const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(rawRequest);
|
|
466
495
|
const filteredHeaders = filterInternalHeaders(rawRequest.headers);
|
|
467
496
|
if (mwCtx !== null) filteredHeaders.set(VINEXT_MW_CTX_HEADER, mwCtx);
|
|
468
497
|
const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParamsPayload);
|
|
469
498
|
if (prerenderRouteParamsHeader !== null) filteredHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
|
|
470
|
-
|
|
499
|
+
let appRequest = rawRequest;
|
|
500
|
+
if (pagesDataNormalization?.isDataReq) {
|
|
501
|
+
const appRequestUrl = new URL(pagesDataNormalization.request.url);
|
|
502
|
+
appRequestUrl.pathname = addBasePathToPathname(appRequestUrl.pathname, options.basePath);
|
|
503
|
+
appRequest = cloneRequestWithUrl(pagesDataCandidate, appRequestUrl.toString());
|
|
504
|
+
}
|
|
505
|
+
const request = cloneRequestWithHeaders(appRequest, filteredHeaders);
|
|
506
|
+
const pagesDataRequest = pagesDataNormalization?.isDataReq ? cloneRequestWithHeaders(pagesDataCandidate, filteredHeaders) : null;
|
|
471
507
|
const executionContext = isExecutionContextLike(ctx) ? ctx : getRequestExecutionContext() ?? null;
|
|
472
508
|
return runWithRequestContext(createRequestContext({
|
|
473
509
|
headersContext: headersContextFromRequest(request, { draftModeSecret: options.draftModeSecret }),
|
|
@@ -478,7 +514,7 @@ function createAppRscHandler(options) {
|
|
|
478
514
|
const preMiddlewareRequestContext = requestContextFromRequest(request);
|
|
479
515
|
let response;
|
|
480
516
|
try {
|
|
481
|
-
response = await handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest);
|
|
517
|
+
response = await handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest, pagesDataRequest);
|
|
482
518
|
} catch (error) {
|
|
483
519
|
if (process.env.NODE_ENV !== "production") flattenErrorCauses(error);
|
|
484
520
|
throw error;
|
|
@@ -18,6 +18,7 @@ function appRscPathnameParts(pathname) {
|
|
|
18
18
|
function createAppRscRouteMatcher(routes) {
|
|
19
19
|
const routeTrie = buildRouteTrie(routes);
|
|
20
20
|
const interceptLookup = createInterceptLookup(routes);
|
|
21
|
+
const routeIndexes = new Map(routes.map((route, index) => [route, index]));
|
|
21
22
|
return {
|
|
22
23
|
matchRoute(url) {
|
|
23
24
|
return trieMatch(routeTrie, appRscPathnameParts(url));
|
|
@@ -26,16 +27,19 @@ function createAppRscRouteMatcher(routes) {
|
|
|
26
27
|
if (sourcePathname === null) return null;
|
|
27
28
|
const urlParts = appRscPathnameParts(pathname);
|
|
28
29
|
const sourceParts = appRscPathnameParts(sourcePathname);
|
|
30
|
+
const matchedSourceRoute = trieMatch(routeTrie, sourceParts);
|
|
29
31
|
for (const entry of interceptLookup) {
|
|
30
32
|
if (!matchInterceptSource(sourceParts, entry)) continue;
|
|
31
33
|
const params = matchAppRscRoutePattern(urlParts, entry.targetPatternParts);
|
|
32
34
|
if (params === null) continue;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
+
const concreteSourceRouteIndex = matchedSourceRoute && entry.sourceMatchPatternParts !== null ? routeIndexes.get(matchedSourceRoute.route) ?? entry.sourceRouteIndex : entry.sourceRouteIndex;
|
|
36
|
+
const sourceRoute = routes[concreteSourceRouteIndex];
|
|
37
|
+
const matchedSourceParams = matchedSourceRoute && entry.sourceMatchPatternParts !== null ? matchedSourceRoute.params : sourceRoute ? matchAppRscRoutePattern(sourceParts, sourceRoute.patternParts) : null;
|
|
35
38
|
if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
|
|
36
39
|
const sourceParams = matchedSourceParams ?? createRouteParams();
|
|
37
40
|
return {
|
|
38
41
|
...entry,
|
|
42
|
+
sourceRouteIndex: concreteSourceRouteIndex,
|
|
39
43
|
matchedParams: mergeMatchedParams(sourceParams, params)
|
|
40
44
|
};
|
|
41
45
|
}
|
|
@@ -177,7 +177,7 @@ async function readActionFormDataWithLimit(request, maxBytes) {
|
|
|
177
177
|
if (result.done) break;
|
|
178
178
|
totalSize += result.value.byteLength;
|
|
179
179
|
if (totalSize > maxBytes) {
|
|
180
|
-
|
|
180
|
+
reader.cancel();
|
|
181
181
|
throw new Error("Request body too large");
|
|
182
182
|
}
|
|
183
183
|
chunks.push(result.value);
|
|
@@ -485,7 +485,10 @@ async function handleServerActionRscRequest(options) {
|
|
|
485
485
|
if (options.request.method.toUpperCase() !== "POST" || !options.actionId) return null;
|
|
486
486
|
const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
|
|
487
487
|
if (csrfResponse) return csrfResponse;
|
|
488
|
-
if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize)
|
|
488
|
+
if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) {
|
|
489
|
+
if (options.request.body) options.request.body.cancel().catch(() => {});
|
|
490
|
+
return renderFetchActionBodyExceededResponse(options);
|
|
491
|
+
}
|
|
489
492
|
try {
|
|
490
493
|
let action;
|
|
491
494
|
if (options.contentType.startsWith("multipart/form-data")) {
|
|
@@ -21,6 +21,7 @@ import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./ap
|
|
|
21
21
|
import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
|
|
22
22
|
import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
|
|
23
23
|
import { deferUntilStreamConsumed } from "./app-page-stream.js";
|
|
24
|
+
import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
|
|
24
25
|
import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
|
|
25
26
|
import { Fragment, createElement, use } from "react";
|
|
26
27
|
import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
|
|
@@ -107,35 +108,6 @@ function renderInsertedHtml(insertedElements) {
|
|
|
107
108
|
} catch {}
|
|
108
109
|
return insertedHTML;
|
|
109
110
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Render captured `<Script strategy="beforeInteractive">` inline scripts to
|
|
112
|
-
* HTML, ready to splice immediately after `<head ...>` opens. Each entry has
|
|
113
|
-
* already had its inline content escaped via `escapeInlineContent(..., "script")`
|
|
114
|
-
* inside the Script shim, so this function only quotes the attributes that
|
|
115
|
-
* actually go on the tag (id, nonce, plus the residual passthroughs).
|
|
116
|
-
*
|
|
117
|
-
* Keeping this function colocated with the rest of the head-injection
|
|
118
|
-
* helpers makes it obvious where the boundary is: anything passed through
|
|
119
|
-
* here is being concatenated directly into HTML; treat the inputs
|
|
120
|
-
* accordingly.
|
|
121
|
-
*/
|
|
122
|
-
const VALID_ATTR_NAME = /^[a-zA-Z][\w.-]*$/;
|
|
123
|
-
function renderBeforeInteractiveInlineScripts(scripts) {
|
|
124
|
-
if (scripts.length === 0) return "";
|
|
125
|
-
let html = "";
|
|
126
|
-
for (const script of scripts) {
|
|
127
|
-
let attrs = "";
|
|
128
|
-
if (script.id) attrs += ` id="${escapeHtmlAttr(script.id)}"`;
|
|
129
|
-
attrs += createNonceAttribute(script.nonce);
|
|
130
|
-
if (script.attributes) for (const [key, value] of Object.entries(script.attributes)) {
|
|
131
|
-
if (!VALID_ATTR_NAME.test(key)) continue;
|
|
132
|
-
if (value === true) attrs += ` ${key}`;
|
|
133
|
-
else if (typeof value === "string") attrs += ` ${key}="${escapeHtmlAttr(value)}"`;
|
|
134
|
-
}
|
|
135
|
-
html += `<script${attrs}>${script.innerHTML}<\/script>`;
|
|
136
|
-
}
|
|
137
|
-
return html;
|
|
138
|
-
}
|
|
139
111
|
function renderFontHtml(fontData, nonce, options = {}) {
|
|
140
112
|
if (!fontData) return "";
|
|
141
113
|
let fontHTML = "";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BeforeInteractiveInlineScript } from "../shims/before-interactive-context.js";
|
|
2
|
+
|
|
3
|
+
//#region src/server/before-interactive-head.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
|
|
6
|
+
* ready to splice immediately after `<head ...>` opens. Each entry has already
|
|
7
|
+
* had its inline content escaped via `escapeInlineContent(..., "script")`
|
|
8
|
+
* inside the Script shim, so this function only quotes the attributes that
|
|
9
|
+
* actually go on the tag (id, src, nonce, plus the residual passthroughs).
|
|
10
|
+
*
|
|
11
|
+
* Keeping this function in its own module makes the boundary obvious: anything
|
|
12
|
+
* passed through here is being concatenated directly into HTML; treat the
|
|
13
|
+
* inputs accordingly.
|
|
14
|
+
*/
|
|
15
|
+
declare function renderBeforeInteractiveInlineScripts(scripts: readonly BeforeInteractiveInlineScript[]): string;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { renderBeforeInteractiveInlineScripts };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
|
|
2
|
+
//#region src/server/before-interactive-head.ts
|
|
3
|
+
const VALID_ATTR_NAME = /^[a-zA-Z][\w.-]*$/;
|
|
4
|
+
/**
|
|
5
|
+
* Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
|
|
6
|
+
* ready to splice immediately after `<head ...>` opens. Each entry has already
|
|
7
|
+
* had its inline content escaped via `escapeInlineContent(..., "script")`
|
|
8
|
+
* inside the Script shim, so this function only quotes the attributes that
|
|
9
|
+
* actually go on the tag (id, src, nonce, plus the residual passthroughs).
|
|
10
|
+
*
|
|
11
|
+
* Keeping this function in its own module makes the boundary obvious: anything
|
|
12
|
+
* passed through here is being concatenated directly into HTML; treat the
|
|
13
|
+
* inputs accordingly.
|
|
14
|
+
*/
|
|
15
|
+
function renderBeforeInteractiveInlineScripts(scripts) {
|
|
16
|
+
if (scripts.length === 0) return "";
|
|
17
|
+
let html = "";
|
|
18
|
+
for (const script of scripts) {
|
|
19
|
+
let attrs = "";
|
|
20
|
+
if (script.id) attrs += ` id="${escapeHtmlAttr(script.id)}"`;
|
|
21
|
+
if (script.src) attrs += ` src="${escapeHtmlAttr(script.src)}"`;
|
|
22
|
+
attrs += createNonceAttribute(script.nonce);
|
|
23
|
+
if (script.attributes) for (const [key, value] of Object.entries(script.attributes)) {
|
|
24
|
+
if (!VALID_ATTR_NAME.test(key)) continue;
|
|
25
|
+
if (key === "data-nscript") continue;
|
|
26
|
+
if (value === true) attrs += ` ${key}`;
|
|
27
|
+
else if (typeof value === "string") attrs += ` ${key}="${escapeHtmlAttr(value)}"`;
|
|
28
|
+
}
|
|
29
|
+
attrs += ` data-nscript="beforeInteractive"`;
|
|
30
|
+
html += `<script${attrs}>${script.innerHTML ?? ""}<\/script>`;
|
|
31
|
+
}
|
|
32
|
+
return html;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { renderBeforeInteractiveInlineScripts };
|
package/dist/server/csp.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
//#region src/server/csp.ts
|
|
2
2
|
const ESCAPE_REGEX = /[&><\u2028\u2029]/;
|
|
3
|
-
function matchesDirectiveName(directive, name) {
|
|
4
|
-
return directive === name || directive.startsWith(`${name} `);
|
|
5
|
-
}
|
|
6
3
|
function getNodeHeaderValue(headers, key) {
|
|
7
4
|
const value = headers?.[key];
|
|
8
5
|
if (Array.isArray(value)) return value.join(", ");
|
|
@@ -11,7 +8,7 @@ function getNodeHeaderValue(headers, key) {
|
|
|
11
8
|
}
|
|
12
9
|
function getScriptNonceFromHeader(cspHeaderValue) {
|
|
13
10
|
const directives = cspHeaderValue.split(";").map((directive) => directive.trim());
|
|
14
|
-
const directive = directives.find((value) =>
|
|
11
|
+
const directive = directives.find((value) => value.startsWith("script-src")) ?? directives.find((value) => value.startsWith("default-src"));
|
|
15
12
|
if (!directive) return;
|
|
16
13
|
const nonce = directive.split(" ").slice(1).map((source) => source.trim()).find((source) => source.startsWith("'nonce-") && source.length > 8 && source.endsWith("'"))?.slice(7, -1);
|
|
17
14
|
if (!nonce) return;
|