@opennextjs/aws 4.1.15 → 4.1.16
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/adapters/cache.js +2 -1
- package/dist/core/requestHandler.d.ts +17 -0
- package/dist/core/requestHandler.js +48 -18
- package/dist/core/revalidate.js +1 -0
- package/dist/core/routing/cacheInterceptor.js +1 -20
- package/dist/core/routing/matcher.d.ts +14 -1
- package/dist/core/routing/matcher.js +25 -5
- package/dist/core/routing/middleware.js +3 -0
- package/dist/core/routing/util.js +10 -2
- package/dist/core/routingHandler.d.ts +2 -0
- package/dist/core/routingHandler.js +31 -27
- package/dist/minimize-js.d.ts +9 -0
- package/dist/minimize-js.js +27 -26
- package/dist/types/global.d.ts +12 -0
- package/dist/utils/normalize-path.d.ts +15 -0
- package/dist/utils/normalize-path.js +32 -0
- package/package.json +3 -3
package/dist/adapters/cache.js
CHANGED
|
@@ -167,8 +167,9 @@ export default class Cache {
|
|
|
167
167
|
const store = globalThis.__openNextAls.getStore();
|
|
168
168
|
if (store) {
|
|
169
169
|
// The sentinel is kept as is in the store so that the revalidation queue keeps
|
|
170
|
-
// deduplicating every stale request for the same path.
|
|
170
|
+
// deduplicating every stale request for the same path. The queue gets the real time.
|
|
171
171
|
store.lastModified = _isStale ? 1 : _lastModified;
|
|
172
|
+
store.entryLastModified = _lastModified;
|
|
172
173
|
}
|
|
173
174
|
if (_isStale) {
|
|
174
175
|
_lastModified = getStaleLastModified(cacheData.revalidate);
|
|
@@ -2,6 +2,22 @@ import type { OpenNextNodeResponse } from "../http/index.js";
|
|
|
2
2
|
import { IncomingMessage } from "../http/index.js";
|
|
3
3
|
import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next.js";
|
|
4
4
|
import type { OpenNextHandlerOptions } from "../types/overrides.js";
|
|
5
|
+
type HeaderRecord = InternalEvent["headers"];
|
|
6
|
+
/**
|
|
7
|
+
* Splits the headers of a routed event into the request and response headers.
|
|
8
|
+
*
|
|
9
|
+
* The routing adds the middleware and `next.config` response headers to the event with a prefix each (see
|
|
10
|
+
* `applyResponseHeaders`). As in Next.js, both go on the response but only the middleware headers go on the request:
|
|
11
|
+
* a `Content-Security-Policy` from `next.config` must not replace the one, holding the script nonce, that the
|
|
12
|
+
* middleware set on the request.
|
|
13
|
+
*
|
|
14
|
+
* @param eventHeaders The headers of the routed event, not modified
|
|
15
|
+
* @returns The headers to send to Next.js and the headers to set on the response
|
|
16
|
+
*/
|
|
17
|
+
export declare function splitRoutingHeaders(eventHeaders: HeaderRecord): {
|
|
18
|
+
requestHeaders: HeaderRecord;
|
|
19
|
+
responseHeaders: HeaderRecord;
|
|
20
|
+
};
|
|
5
21
|
export declare function openNextHandler(internalEvent: InternalEvent, options?: OpenNextHandlerOptions): Promise<InternalResult>;
|
|
6
22
|
/**
|
|
7
23
|
* Renders the next resolved route after a `fallback: false` route threw a `NoFallbackError`.
|
|
@@ -26,3 +42,4 @@ export declare function openNextHandler(internalEvent: InternalEvent, options?:
|
|
|
26
42
|
* @return A promise that resolves after rendering a route or an error response
|
|
27
43
|
*/
|
|
28
44
|
export declare function handleNoFallbackError(req: IncomingMessage, res: OpenNextNodeResponse, routingResult: RoutingResult, metadata: Record<string, unknown>, index?: number): Promise<void>;
|
|
45
|
+
export {};
|
|
@@ -8,13 +8,45 @@ import { debug, error } from "../adapters/logger";
|
|
|
8
8
|
import { patchAsyncStorage } from "./patchAsyncStorage";
|
|
9
9
|
import { getRouteMatchMetadata } from "./routeMatchMetadata";
|
|
10
10
|
import { constructNextUrl, convertRes, convertToQuery, createServerResponse, } from "./routing/util";
|
|
11
|
-
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
11
|
+
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, NEXT_CONFIG_HEADER_PREFIX, NEXT_CONFIG_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
12
12
|
import { requestHandler, setNextjsPrebundledReact } from "./util";
|
|
13
13
|
// This is used to identify requests in the cache
|
|
14
14
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
15
15
|
//#override patchAsyncStorage
|
|
16
16
|
patchAsyncStorage();
|
|
17
17
|
//#endOverride
|
|
18
|
+
/**
|
|
19
|
+
* Splits the headers of a routed event into the request and response headers.
|
|
20
|
+
*
|
|
21
|
+
* The routing adds the middleware and `next.config` response headers to the event with a prefix each (see
|
|
22
|
+
* `applyResponseHeaders`). As in Next.js, both go on the response but only the middleware headers go on the request:
|
|
23
|
+
* a `Content-Security-Policy` from `next.config` must not replace the one, holding the script nonce, that the
|
|
24
|
+
* middleware set on the request.
|
|
25
|
+
*
|
|
26
|
+
* @param eventHeaders The headers of the routed event, not modified
|
|
27
|
+
* @returns The headers to send to Next.js and the headers to set on the response
|
|
28
|
+
*/
|
|
29
|
+
export function splitRoutingHeaders(eventHeaders) {
|
|
30
|
+
const requestHeaders = {};
|
|
31
|
+
const middlewareHeaders = {};
|
|
32
|
+
const nextConfigHeaders = {};
|
|
33
|
+
for (const [key, value] of Object.entries(eventHeaders)) {
|
|
34
|
+
if (key.startsWith(MIDDLEWARE_HEADER_PREFIX)) {
|
|
35
|
+
middlewareHeaders[key.slice(MIDDLEWARE_HEADER_PREFIX_LEN)] = value;
|
|
36
|
+
}
|
|
37
|
+
else if (key.startsWith(NEXT_CONFIG_HEADER_PREFIX)) {
|
|
38
|
+
nextConfigHeaders[key.slice(NEXT_CONFIG_HEADER_PREFIX_LEN)] = value;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
requestHeaders[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
requestHeaders: { ...requestHeaders, ...middlewareHeaders },
|
|
46
|
+
// The routing already dropped the `next.config` headers that must not take precedence.
|
|
47
|
+
responseHeaders: { ...middlewareHeaders, ...nextConfigHeaders },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
18
50
|
export async function openNextHandler(internalEvent, options) {
|
|
19
51
|
const initialHeaders = internalEvent.headers;
|
|
20
52
|
// We only use the requestId header if we are using an external middleware
|
|
@@ -60,22 +92,20 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
60
92
|
assetResolver: globalThis.assetResolver,
|
|
61
93
|
});
|
|
62
94
|
//#endOverride
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
headers[key] = value;
|
|
78
|
-
delete headers[rawKey];
|
|
95
|
+
// The headers of a result are not prefixed, they are the response headers.
|
|
96
|
+
let requestHeaders = {};
|
|
97
|
+
let overwrittenResponseHeaders = {};
|
|
98
|
+
if (!("type" in routingResult)) {
|
|
99
|
+
const split = splitRoutingHeaders(routingResult.internalEvent.headers);
|
|
100
|
+
requestHeaders = split.requestHeaders;
|
|
101
|
+
overwrittenResponseHeaders = split.responseHeaders;
|
|
102
|
+
routingResult = {
|
|
103
|
+
...routingResult,
|
|
104
|
+
internalEvent: {
|
|
105
|
+
...routingResult.internalEvent,
|
|
106
|
+
headers: requestHeaders,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
79
109
|
}
|
|
80
110
|
if ("isExternalRewrite" in routingResult &&
|
|
81
111
|
routingResult.isExternalRewrite === true) {
|
|
@@ -138,7 +168,7 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
138
168
|
// 2. We could alter the lastModified time of our cache to make next believe that the cache is fresh, but this could cause issues with stale data since the cdn will cache the stale data as if it was fresh
|
|
139
169
|
// 3. OUR CHOICE: We could pass a purpose prefetch header to the serverless function to make next believe that the request is a prefetch request and not trigger revalidation (This could potentially break in the future if next changes the behavior of prefetch requests)
|
|
140
170
|
headers: {
|
|
141
|
-
...
|
|
171
|
+
...requestHeaders,
|
|
142
172
|
//#override appendPrefetch
|
|
143
173
|
purpose: "prefetch",
|
|
144
174
|
//#endOverride
|
package/dist/core/revalidate.js
CHANGED
|
@@ -121,6 +121,7 @@ export function createRevalidate(revalidateInProcess) {
|
|
|
121
121
|
? globalThis.__openNextAls.run({
|
|
122
122
|
...store,
|
|
123
123
|
lastModified: undefined,
|
|
124
|
+
entryLastModified: undefined,
|
|
124
125
|
writtenTags: new Set(),
|
|
125
126
|
requestCache: new RequestCache(),
|
|
126
127
|
}, renderInProcess)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { AppPathsManifest, NextConfig, PagesManifest, PrerenderManifest, } from "../../adapters/config/index.js";
|
|
3
|
+
import { decodePathParams } from "../../utils/normalize-path.js";
|
|
3
4
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream.js";
|
|
4
5
|
import { isBinaryContentType } from "../../utils/binary.js";
|
|
5
6
|
import { getTagsFromValue, hasBeenRevalidated, isStale } from "../../utils/cache.js";
|
|
@@ -255,26 +256,6 @@ function computeStatusCode(rewriteStatusCode, cachedStatusCode) {
|
|
|
255
256
|
}
|
|
256
257
|
return rewriteStatusCode ?? cachedStatusCode ?? 200;
|
|
257
258
|
}
|
|
258
|
-
/**
|
|
259
|
-
*
|
|
260
|
-
* https://github.com/vercel/next.js/blob/34039551d2e5f611c0abde31a197d9985918adaf/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts#L2-L10
|
|
261
|
-
*/
|
|
262
|
-
function escapePathDelimiters(segment, escapeEncoded) {
|
|
263
|
-
return segment.replace(new RegExp(`([/#?]${escapeEncoded ? "|%(2f|23|3f|5c)" : ""})`, "gi"), (char) => encodeURIComponent(char));
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
*
|
|
267
|
-
* SSG cache key needs to be decoded, but some characters needs to be properly escaped
|
|
268
|
-
* https://github.com/vercel/next.js/blob/34039551d2e5f611c0abde31a197d9985918adaf/packages/next/src/server/lib/router-utils/decode-path-params.ts#L11-L26
|
|
269
|
-
* Decoding must be atomic, as in Next.js. Partially decoding a malformed path
|
|
270
|
-
* could select a different cache route than the one evaluated by middleware.
|
|
271
|
-
*/
|
|
272
|
-
function decodePathParams(pathname) {
|
|
273
|
-
return pathname
|
|
274
|
-
.split("/")
|
|
275
|
-
.map((segment) => escapePathDelimiters(decodeURIComponent(segment), true))
|
|
276
|
-
.join("/");
|
|
277
|
-
}
|
|
278
259
|
let routeCacheManifests;
|
|
279
260
|
function getRouteCacheManifests() {
|
|
280
261
|
routeCacheManifests ??= {
|
|
@@ -20,9 +20,12 @@ export declare function getCompiledRegExp(pattern: string, flags?: string): RegE
|
|
|
20
20
|
* interpolation, with condition parameters taking precedence. See
|
|
21
21
|
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/server/lib/router-utils/resolve-routes.ts#L400-L414
|
|
22
22
|
*
|
|
23
|
+
* Header names are lowercased. As Next.js applies them with the case-insensitive `res.setHeader()`, the last matching
|
|
24
|
+
* route wins when several set the same header, whatever its case.
|
|
25
|
+
*
|
|
23
26
|
* @param event The request used to match configured headers
|
|
24
27
|
* @param configHeaders The configured header routes
|
|
25
|
-
* @returns The response headers produced by every matching route
|
|
28
|
+
* @returns The response headers produced by every matching route, keyed by lowercase name
|
|
26
29
|
* @throws {SyntaxError} When a configured route pattern is invalid
|
|
27
30
|
*/
|
|
28
31
|
export declare function getNextConfigHeaders(event: InternalEvent, configHeaders?: Header[] | undefined): Record<string, string | undefined>;
|
|
@@ -78,6 +81,16 @@ export declare function handleRewrites<T extends RewriteDefinition>(event: Inter
|
|
|
78
81
|
export declare function handleBeforeFilesRewrites<T extends RewriteDefinition>(event: InternalEvent, rewrites: T[]): ReturnType<typeof handleRewrites<T>>;
|
|
79
82
|
export declare function handleRedirects(event: InternalEvent, redirects: RedirectDefinition[]): InternalResult | undefined;
|
|
80
83
|
export declare function fixDataPage(internalEvent: InternalEvent, buildId: string): InternalEvent | InternalResult;
|
|
84
|
+
/**
|
|
85
|
+
* Rewrites the event to `/404` for paths that `fallback: false` routes do not pregenerate.
|
|
86
|
+
*
|
|
87
|
+
* A path is pregenerated when its decoded form is a key of the prerender manifest routes.
|
|
88
|
+
* The rewrite only applies when no other static or dynamic route can serve the path.
|
|
89
|
+
*
|
|
90
|
+
* @param internalEvent The incoming event
|
|
91
|
+
* @param prerenderManifest The prerender manifest
|
|
92
|
+
* @returns The (possibly rewritten) event and whether the path is pregenerated
|
|
93
|
+
*/
|
|
81
94
|
export declare function handleFallbackFalse(internalEvent: InternalEvent, prerenderManifest?: PrerenderManifest): {
|
|
82
95
|
event: InternalEvent;
|
|
83
96
|
isISR: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextConfig } from "../../adapters/config/index.js";
|
|
2
2
|
import { compile, match, parse } from "path-to-regexp";
|
|
3
|
-
import { normalizeRepeatedSlashes } from "../../utils/normalize-path.js";
|
|
3
|
+
import { decodePathParams, normalizeRepeatedSlashes, } from "../../utils/normalize-path.js";
|
|
4
4
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream.js";
|
|
5
5
|
import { debug } from "../../adapters/logger";
|
|
6
6
|
import { handleLocaleRedirect, localizePath } from "./i18n";
|
|
@@ -223,9 +223,12 @@ function compileQueryString(queryString, params) {
|
|
|
223
223
|
* interpolation, with condition parameters taking precedence. See
|
|
224
224
|
* https://github.com/vercel/next.js/blob/ae745ba/packages/next/src/server/lib/router-utils/resolve-routes.ts#L400-L414
|
|
225
225
|
*
|
|
226
|
+
* Header names are lowercased. As Next.js applies them with the case-insensitive `res.setHeader()`, the last matching
|
|
227
|
+
* route wins when several set the same header, whatever its case.
|
|
228
|
+
*
|
|
226
229
|
* @param event The request used to match configured headers
|
|
227
230
|
* @param configHeaders The configured header routes
|
|
228
|
-
* @returns The response headers produced by every matching route
|
|
231
|
+
* @returns The response headers produced by every matching route, keyed by lowercase name
|
|
229
232
|
* @throws {SyntaxError} When a configured route pattern is invalid
|
|
230
233
|
*/
|
|
231
234
|
export function getNextConfigHeaders(event, configHeaders) {
|
|
@@ -254,11 +257,11 @@ export function getNextConfigHeaders(event, configHeaders) {
|
|
|
254
257
|
try {
|
|
255
258
|
const key = hasParams ? compileNonPath(h.key, params) : h.key;
|
|
256
259
|
const value = hasParams ? compileNonPath(h.value, params) : h.value;
|
|
257
|
-
requestHeaders[key] = value;
|
|
260
|
+
requestHeaders[key.toLowerCase()] = value;
|
|
258
261
|
}
|
|
259
262
|
catch {
|
|
260
263
|
debug(`Error matching header ${h.key} with value ${h.value}`);
|
|
261
|
-
requestHeaders[h.key] = h.value;
|
|
264
|
+
requestHeaders[h.key.toLowerCase()] = h.value;
|
|
262
265
|
}
|
|
263
266
|
});
|
|
264
267
|
}
|
|
@@ -540,6 +543,16 @@ export function fixDataPage(internalEvent, buildId) {
|
|
|
540
543
|
}
|
|
541
544
|
return internalEvent;
|
|
542
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Rewrites the event to `/404` for paths that `fallback: false` routes do not pregenerate.
|
|
548
|
+
*
|
|
549
|
+
* A path is pregenerated when its decoded form is a key of the prerender manifest routes.
|
|
550
|
+
* The rewrite only applies when no other static or dynamic route can serve the path.
|
|
551
|
+
*
|
|
552
|
+
* @param internalEvent The incoming event
|
|
553
|
+
* @param prerenderManifest The prerender manifest
|
|
554
|
+
* @returns The (possibly rewritten) event and whether the path is pregenerated
|
|
555
|
+
*/
|
|
543
556
|
export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
544
557
|
const { rawPath } = internalEvent;
|
|
545
558
|
const { dynamicRoutes = {}, routes = {} } = prerenderManifest ?? {};
|
|
@@ -563,7 +576,14 @@ export function handleFallbackFalse(internalEvent, prerenderManifest) {
|
|
|
563
576
|
const matchedStaticRoute = staticRouteMatcher(localizedPath);
|
|
564
577
|
const prerenderedFallbackRoutesName = prerenderedFallbackRoutes.map(([name]) => name);
|
|
565
578
|
const matchedDynamicRoute = dynamicRouteMatcher(localizedPath).filter(({ route }) => !prerenderedFallbackRoutesName.includes(route));
|
|
566
|
-
|
|
579
|
+
let isPregenerated = false;
|
|
580
|
+
try {
|
|
581
|
+
// The prerender manifest keys routes by their decoded path while rawPath is percent-encoded.
|
|
582
|
+
isPregenerated = Object.hasOwn(routes, decodePathParams(localizedPath));
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// A malformed escape sequence cannot name a prerendered route.
|
|
586
|
+
}
|
|
567
587
|
if (routeFallback &&
|
|
568
588
|
!isPregenerated &&
|
|
569
589
|
matchedStaticRoute.length === 0 &&
|
|
@@ -101,10 +101,13 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
101
101
|
let middlewareSetCookie;
|
|
102
102
|
// These are internal headers used by Next.js, we don't want to expose them to the client
|
|
103
103
|
// NOTE: keys must be in lowercase to match the normalized header keys.
|
|
104
|
+
// See `resolve-routes.ts` in Next.js for the headers it drops from the middleware response.
|
|
104
105
|
const filteredHeaders = [
|
|
105
106
|
"x-middleware-override-headers",
|
|
106
107
|
"x-middleware-next",
|
|
107
108
|
"x-middleware-rewrite",
|
|
109
|
+
"x-middleware-redirect",
|
|
110
|
+
"x-middleware-refresh",
|
|
108
111
|
// We need to drop `content-encoding` because it will be decoded
|
|
109
112
|
"content-encoding",
|
|
110
113
|
];
|
|
@@ -303,12 +303,20 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
|
303
303
|
// If data has the same etag during these 5 min dedup window, it will be deduplicated and not revalidated.
|
|
304
304
|
try {
|
|
305
305
|
const hash = (str) => crypto.createHash("md5").update(str).digest("hex");
|
|
306
|
-
const
|
|
306
|
+
const store = globalThis.__openNextAls.getStore();
|
|
307
|
+
// `1` when the entry is stale by tag, so that the deduplication id is the same for every copy of the entry
|
|
308
|
+
const lastModified = store?.lastModified ?? 0;
|
|
307
309
|
// For some weird cases, lastModified is not set, haven't been able to figure out yet why
|
|
308
310
|
// For those cases we add the etag to the deduplication id, it might help
|
|
309
311
|
const eTag = `${headers.etag ?? headers.ETag ?? ""}`;
|
|
310
312
|
await globalThis.queue.send({
|
|
311
|
-
MessageBody: {
|
|
313
|
+
MessageBody: {
|
|
314
|
+
host,
|
|
315
|
+
url: revalidateUrl,
|
|
316
|
+
eTag,
|
|
317
|
+
// The time of the entry, so that the queue can skip it when it was revalidated since
|
|
318
|
+
lastModified: store?.entryLastModified ?? lastModified,
|
|
319
|
+
},
|
|
312
320
|
MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${eTag}`),
|
|
313
321
|
MessageGroupId: generateMessageGroupId(rawPath),
|
|
314
322
|
});
|
|
@@ -3,6 +3,8 @@ import type { AssetResolver } from "../types/overrides.js";
|
|
|
3
3
|
export declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
4
4
|
export declare const MIDDLEWARE_HEADER_PREFIX_LEN: number;
|
|
5
5
|
export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
6
|
+
export declare const NEXT_CONFIG_HEADER_PREFIX = "x-opennext-next-config-response-";
|
|
7
|
+
export declare const NEXT_CONFIG_HEADER_PREFIX_LEN: number;
|
|
6
8
|
export declare const INTERNAL_HEADER_INITIAL_URL = "x-opennext-initial-url";
|
|
7
9
|
export declare const INTERNAL_HEADER_LOCALE = "x-opennext-locale";
|
|
8
10
|
export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
|
|
@@ -11,6 +11,8 @@ import { constructNextUrl, normalizeLocationHeader } from "./routing/util";
|
|
|
11
11
|
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
12
12
|
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
|
|
13
13
|
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
14
|
+
export const NEXT_CONFIG_HEADER_PREFIX = `${INTERNAL_HEADER_PREFIX}next-config-response-`;
|
|
15
|
+
export const NEXT_CONFIG_HEADER_PREFIX_LEN = NEXT_CONFIG_HEADER_PREFIX.length;
|
|
14
16
|
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
|
|
15
17
|
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
|
|
16
18
|
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
|
|
@@ -40,24 +42,34 @@ const NEXT_INTERNAL_HEADERS = [
|
|
|
40
42
|
"x-matched-path",
|
|
41
43
|
"x-nextjs-data",
|
|
42
44
|
"x-next-resume-state-length",
|
|
45
|
+
"next-resume",
|
|
43
46
|
];
|
|
44
47
|
/**
|
|
45
|
-
* Adds the middleware headers to an event or result.
|
|
48
|
+
* Adds the `next.config` and middleware response headers to an event or result.
|
|
46
49
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
50
|
+
* On an event, each kind gets its own prefix so that the request handler can tell them apart (see
|
|
51
|
+
* `splitRoutingHeaders`). On a result, the headers are the response and are not prefixed.
|
|
52
|
+
*
|
|
53
|
+
* `nextConfigHeaders` take precedence over `middlewareHeaders` on the response.
|
|
54
|
+
*
|
|
55
|
+
* @param eventOrResult The event or result to add the headers to, its headers are mutated
|
|
56
|
+
* @param nextConfigHeaders The response headers from the `next.config` `headers()`
|
|
57
|
+
* @param middlewareHeaders The response headers set by the middleware
|
|
49
58
|
*/
|
|
50
|
-
function
|
|
51
|
-
// Use the `MIDDLEWARE_HEADER_PREFIX` prefix for events, they will be processed by the request handler later.
|
|
52
|
-
// Results do not go through the request handler and should not be prefixed.
|
|
59
|
+
function applyResponseHeaders(eventOrResult, nextConfigHeaders, middlewareHeaders) {
|
|
53
60
|
const isResult = isInternalResult(eventOrResult);
|
|
54
61
|
const headers = eventOrResult.headers;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const addHeaders = (source, keyPrefix) => {
|
|
63
|
+
for (const [key, value] of Object.entries(source)) {
|
|
64
|
+
if (value) {
|
|
65
|
+
headers[keyPrefix + key] = Array.isArray(value)
|
|
66
|
+
? value.join(",")
|
|
67
|
+
: value;
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
|
-
}
|
|
70
|
+
};
|
|
71
|
+
addHeaders(middlewareHeaders, isResult ? "" : MIDDLEWARE_HEADER_PREFIX);
|
|
72
|
+
addHeaders(nextConfigHeaders, isResult ? "" : NEXT_CONFIG_HEADER_PREFIX);
|
|
61
73
|
}
|
|
62
74
|
export default async function routingHandler(event, { assetResolver }) {
|
|
63
75
|
try {
|
|
@@ -78,8 +90,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
78
90
|
delete event.headers[key];
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
let headers = getNextConfigHeaders(event, ConfigHeaders);
|
|
93
|
+
let nextConfigHeaders = getNextConfigHeaders(event, ConfigHeaders);
|
|
83
94
|
let eventOrResult = fixDataPage(event, BuildId);
|
|
84
95
|
if (isInternalResult(eventOrResult)) {
|
|
85
96
|
return eventOrResult;
|
|
@@ -101,17 +112,10 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
101
112
|
}
|
|
102
113
|
const middlewareHeadersPrioritized = globalThis.openNextConfig.dangerous
|
|
103
114
|
?.middlewareHeadersOverrideNextConfigHeaders ?? false;
|
|
115
|
+
const middlewareHeaders = middlewareEventOrResult.responseHeaders ?? {};
|
|
104
116
|
if (middlewareHeadersPrioritized) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
...middlewareEventOrResult.responseHeaders,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
headers = {
|
|
112
|
-
...middlewareEventOrResult.responseHeaders,
|
|
113
|
-
...headers,
|
|
114
|
-
};
|
|
117
|
+
// Both are keyed by lowercase name.
|
|
118
|
+
nextConfigHeaders = Object.fromEntries(Object.entries(nextConfigHeaders).filter(([key]) => !Object.hasOwn(middlewareHeaders, key)));
|
|
115
119
|
}
|
|
116
120
|
let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false;
|
|
117
121
|
eventOrResult = middlewareEventOrResult;
|
|
@@ -127,7 +131,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
127
131
|
if (!isExternalRewrite) {
|
|
128
132
|
const assetResult = await assetResolver?.maybeGetAssetResult?.(eventOrResult);
|
|
129
133
|
if (assetResult) {
|
|
130
|
-
|
|
134
|
+
applyResponseHeaders(assetResult, nextConfigHeaders, middlewareHeaders);
|
|
131
135
|
return assetResult;
|
|
132
136
|
}
|
|
133
137
|
}
|
|
@@ -192,12 +196,12 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
192
196
|
debug("Cache interception enabled");
|
|
193
197
|
eventOrResult = await cacheInterceptor(eventOrResult, resolvedRoutes);
|
|
194
198
|
if (isInternalResult(eventOrResult)) {
|
|
195
|
-
|
|
199
|
+
applyResponseHeaders(eventOrResult, nextConfigHeaders, middlewareHeaders);
|
|
196
200
|
return eventOrResult;
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
|
-
// We apply the headers
|
|
200
|
-
|
|
203
|
+
// We apply the response headers last
|
|
204
|
+
applyResponseHeaders(eventOrResult, nextConfigHeaders, middlewareHeaders);
|
|
201
205
|
debug("resolvedRoutes", resolvedRoutes);
|
|
202
206
|
return {
|
|
203
207
|
internalEvent: eventOrResult,
|
package/dist/minimize-js.d.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minifies the JS (and optionally JSON) files of a directory in place.
|
|
3
|
+
*
|
|
4
|
+
* Calls can run concurrently. Failed files are logged and do not throw.
|
|
5
|
+
*
|
|
6
|
+
* @param dir Directory to walk recursively.
|
|
7
|
+
* @param opts Minification options.
|
|
8
|
+
* @return A promise that resolves once all the files have been processed.
|
|
9
|
+
*/
|
|
1
10
|
export declare function minifyAll(dir: any, opts: any): Promise<void>;
|
package/dist/minimize-js.js
CHANGED
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
// @ts-nocheck
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import minify from "@node-minify/core";
|
|
8
|
-
import terser from "@node-minify/terser";
|
|
9
|
-
const failed_files = [];
|
|
10
|
-
let total_files = 0;
|
|
11
|
-
const options = {};
|
|
7
|
+
import { minify } from "@node-minify/core";
|
|
8
|
+
import { terser } from "@node-minify/terser";
|
|
12
9
|
const promiseSeries = async (tasks, initial) => {
|
|
13
10
|
if (!Array.isArray(tasks)) {
|
|
14
11
|
return Promise.reject(new TypeError("promise.series only accepts an array of functions"));
|
|
@@ -17,29 +14,28 @@ const promiseSeries = async (tasks, initial) => {
|
|
|
17
14
|
return current.then(next);
|
|
18
15
|
}, Promise.resolve(initial));
|
|
19
16
|
};
|
|
20
|
-
const minifyJS = async (file) => {
|
|
21
|
-
total_files++;
|
|
17
|
+
const minifyJS = async (file, ctx) => {
|
|
22
18
|
try {
|
|
23
19
|
await minify({
|
|
24
20
|
compressor: terser,
|
|
25
21
|
input: file,
|
|
26
22
|
output: file,
|
|
23
|
+
// node-minify 10 throws when the output is empty (i.e. empty or comment-only files)
|
|
24
|
+
allowEmptyOutput: true,
|
|
27
25
|
options: {
|
|
28
|
-
module: options.module,
|
|
29
|
-
mangle: options.mangle,
|
|
26
|
+
module: ctx.options.module,
|
|
27
|
+
mangle: ctx.options.mangle,
|
|
30
28
|
compress: { reduce_vars: false },
|
|
31
29
|
},
|
|
32
30
|
});
|
|
33
31
|
}
|
|
34
32
|
catch (e) {
|
|
35
|
-
failed_files.push(file);
|
|
33
|
+
ctx.failed_files.push(file);
|
|
36
34
|
}
|
|
37
|
-
//process.stdout.write(".");
|
|
38
35
|
};
|
|
39
|
-
const minifyJSON = async (file) => {
|
|
36
|
+
const minifyJSON = async (file, options) => {
|
|
40
37
|
try {
|
|
41
38
|
if (options.compress_json || options.packagejson) {
|
|
42
|
-
total_files++;
|
|
43
39
|
const is_package_json = file.indexOf("package.json") > -1;
|
|
44
40
|
const data = await fs.readFile(file, "utf8");
|
|
45
41
|
const json = JSON.parse(data);
|
|
@@ -63,9 +59,8 @@ const minifyJSON = async (file) => {
|
|
|
63
59
|
}
|
|
64
60
|
}
|
|
65
61
|
catch (e) { }
|
|
66
|
-
//process.stdout.write(".");
|
|
67
62
|
};
|
|
68
|
-
const walk = async (currentDirPath) => {
|
|
63
|
+
const walk = async (currentDirPath, ctx) => {
|
|
69
64
|
const js_files = [];
|
|
70
65
|
const json_files = [];
|
|
71
66
|
const dirs = [];
|
|
@@ -77,26 +72,32 @@ const walk = async (currentDirPath) => {
|
|
|
77
72
|
if (stat.isFile()) {
|
|
78
73
|
if (filePath.substr(-5) === ".json")
|
|
79
74
|
json_files.push(filePath);
|
|
80
|
-
else if (filePath.substr(-3) === ".js" || options.all_js)
|
|
75
|
+
else if (filePath.substr(-3) === ".js" || ctx.options.all_js)
|
|
81
76
|
js_files.push(filePath);
|
|
82
77
|
}
|
|
83
78
|
else if (stat.isDirectory() && !is_bin.test(filePath)) {
|
|
84
79
|
dirs.push(filePath);
|
|
85
80
|
}
|
|
86
81
|
}
|
|
87
|
-
const js_promise = Promise.all(js_files.map((f) => minifyJS(f)));
|
|
88
|
-
const json_promise = Promise.all(json_files.map((f) => minifyJSON(f)));
|
|
82
|
+
const js_promise = Promise.all(js_files.map((f) => minifyJS(f, ctx)));
|
|
83
|
+
const json_promise = Promise.all(json_files.map((f) => minifyJSON(f, ctx.options)));
|
|
89
84
|
await Promise.all([js_promise, json_promise]);
|
|
90
|
-
await promiseSeries(dirs.map((dir) => () => walk(dir)));
|
|
85
|
+
await promiseSeries(dirs.map((dir) => () => walk(dir, ctx)));
|
|
91
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Minifies the JS (and optionally JSON) files of a directory in place.
|
|
89
|
+
*
|
|
90
|
+
* Calls can run concurrently. Failed files are logged and do not throw.
|
|
91
|
+
*
|
|
92
|
+
* @param dir Directory to walk recursively.
|
|
93
|
+
* @param opts Minification options.
|
|
94
|
+
* @return A promise that resolves once all the files have been processed.
|
|
95
|
+
*/
|
|
92
96
|
export async function minifyAll(dir, opts) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
//process.stdout.write(".\n");
|
|
97
|
-
//console.log("Total found files: " + total_files);
|
|
98
|
-
if (failed_files.length) {
|
|
97
|
+
const ctx = { options: { ...opts }, failed_files: [] };
|
|
98
|
+
await walk(dir, ctx);
|
|
99
|
+
if (ctx.failed_files.length) {
|
|
99
100
|
console.log("\n\nFailed to minify files:");
|
|
100
|
-
failed_files.forEach((f) => console.log(`\t${f}`));
|
|
101
|
+
ctx.failed_files.forEach((f) => console.log(`\t${f}`));
|
|
101
102
|
}
|
|
102
103
|
}
|
package/dist/types/global.d.ts
CHANGED
|
@@ -52,7 +52,19 @@ interface OpenNextRequestContext {
|
|
|
52
52
|
pendingPromiseRunner: DetachedPromiseRunner;
|
|
53
53
|
isISRRevalidation?: boolean;
|
|
54
54
|
mergeHeadersPriority?: "middleware" | "handler";
|
|
55
|
+
/**
|
|
56
|
+
* Last modified time of the page (used in main functions, only available for ISR/SSG).
|
|
57
|
+
*
|
|
58
|
+
* `1` when the entry is stale by tag, so that the revalidation queue deduplicates every stale
|
|
59
|
+
* request for the same path. See `entryLastModified` for the time of the entry.
|
|
60
|
+
*/
|
|
55
61
|
lastModified?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Last modified time of the cache entry, also when it is stale by tag (`lastModified` is then `1`).
|
|
64
|
+
*
|
|
65
|
+
* Sent to the revalidation queue in `MessageBody.lastModified`.
|
|
66
|
+
*/
|
|
67
|
+
entryLastModified?: number;
|
|
56
68
|
waitUntil?: WaitUntil;
|
|
57
69
|
/** We use this to deduplicate write of the tags*/
|
|
58
70
|
writtenTags: Set<string>;
|
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
export declare function normalizePath(path: string): string;
|
|
2
2
|
export declare function normalizeRepeatedSlashes(url: URL): string;
|
|
3
|
+
/**
|
|
4
|
+
* Decodes a pathname the way Next.js does to key prerendered routes.
|
|
5
|
+
*
|
|
6
|
+
* Each segment is decoded while path delimiters (e.g. `%2F`) stay encoded,
|
|
7
|
+
* so that an encoded delimiter does not split a segment.
|
|
8
|
+
* Decoding must be atomic, as in Next.js: partially decoding a malformed path
|
|
9
|
+
* could select a different route than the one Next.js would select.
|
|
10
|
+
*
|
|
11
|
+
* See: https://github.com/vercel/next.js/blob/3403955/packages/next/src/server/lib/router-utils/decode-path-params.ts#L11-L26
|
|
12
|
+
*
|
|
13
|
+
* @param pathname The percent-encoded pathname
|
|
14
|
+
* @returns The decoded pathname
|
|
15
|
+
* @throws {URIError} When the pathname contains a malformed escape sequence
|
|
16
|
+
*/
|
|
17
|
+
export declare function decodePathParams(pathname: string): string;
|
|
3
18
|
export declare function getMonorepoRelativePath(relativePath?: string): string;
|
|
@@ -9,6 +9,38 @@ export function normalizeRepeatedSlashes(url) {
|
|
|
9
9
|
.replace(/\\/g, "/")
|
|
10
10
|
.replace(/\/\/+/g, "/")}${url.search}`;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Percent-encodes the path delimiters of a decoded path segment.
|
|
14
|
+
*
|
|
15
|
+
* See: https://github.com/vercel/next.js/blob/3403955/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts#L2-L10
|
|
16
|
+
*
|
|
17
|
+
* @param segment The decoded path segment
|
|
18
|
+
* @param escapeEncoded Whether to also escape already encoded delimiters (e.g. `%2F`)
|
|
19
|
+
* @returns The segment with its delimiters encoded
|
|
20
|
+
*/
|
|
21
|
+
function escapePathDelimiters(segment, escapeEncoded) {
|
|
22
|
+
return segment.replace(new RegExp(`([/#?]${escapeEncoded ? "|%(2f|23|3f|5c)" : ""})`, "gi"), (char) => encodeURIComponent(char));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decodes a pathname the way Next.js does to key prerendered routes.
|
|
26
|
+
*
|
|
27
|
+
* Each segment is decoded while path delimiters (e.g. `%2F`) stay encoded,
|
|
28
|
+
* so that an encoded delimiter does not split a segment.
|
|
29
|
+
* Decoding must be atomic, as in Next.js: partially decoding a malformed path
|
|
30
|
+
* could select a different route than the one Next.js would select.
|
|
31
|
+
*
|
|
32
|
+
* See: https://github.com/vercel/next.js/blob/3403955/packages/next/src/server/lib/router-utils/decode-path-params.ts#L11-L26
|
|
33
|
+
*
|
|
34
|
+
* @param pathname The percent-encoded pathname
|
|
35
|
+
* @returns The decoded pathname
|
|
36
|
+
* @throws {URIError} When the pathname contains a malformed escape sequence
|
|
37
|
+
*/
|
|
38
|
+
export function decodePathParams(pathname) {
|
|
39
|
+
return pathname
|
|
40
|
+
.split("/")
|
|
41
|
+
.map((segment) => escapePathDelimiters(decodeURIComponent(segment), true))
|
|
42
|
+
.join("/");
|
|
43
|
+
}
|
|
12
44
|
export function getMonorepoRelativePath(relativePath = "../..") {
|
|
13
45
|
return path.join(globalThis.monorepoPackagePath
|
|
14
46
|
.split("/")
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"access": "public"
|
|
4
4
|
},
|
|
5
5
|
"name": "@opennextjs/aws",
|
|
6
|
-
"version": "4.1.
|
|
6
|
+
"version": "4.1.16",
|
|
7
7
|
"bin": {
|
|
8
8
|
"open-next": "./dist/index.js"
|
|
9
9
|
},
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@aws-sdk/client-lambda": "3.984.0",
|
|
40
40
|
"@aws-sdk/client-s3": "3.984.0",
|
|
41
41
|
"@aws-sdk/client-sqs": "3.984.0",
|
|
42
|
-
"@node-minify/core": "^
|
|
43
|
-
"@node-minify/terser": "^
|
|
42
|
+
"@node-minify/core": "^10.5.0",
|
|
43
|
+
"@node-minify/terser": "^10.5.0",
|
|
44
44
|
"@tsconfig/node18": "^18.2.7",
|
|
45
45
|
"aws4fetch": "^1.0.20",
|
|
46
46
|
"chalk": "^5.6.2",
|