vinext 0.1.5 → 0.1.6
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/README.md +2 -2
- package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
- package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
- package/dist/build/report.d.ts +1 -2
- package/dist/build/report.js +2 -11
- package/dist/build/run-prerender.js +1 -1
- package/dist/check.js +3 -3
- package/dist/client/pages-router-link-navigation.d.ts +10 -0
- package/dist/client/pages-router-link-navigation.js +3 -3
- package/dist/deploy.js +3 -3
- package/dist/entries/app-rsc-entry.js +1 -0
- package/dist/entries/app-rsc-manifest.js +76 -46
- package/dist/entries/pages-server-entry.js +1 -0
- package/dist/index.js +69 -54
- package/dist/init.d.ts +5 -4
- package/dist/init.js +27 -8
- package/dist/plugins/fonts.js +3 -1
- package/dist/plugins/middleware-export-validation.d.ts +5 -0
- package/dist/plugins/middleware-export-validation.js +36 -0
- package/dist/plugins/og-asset-ownership.d.ts +27 -0
- package/dist/plugins/og-asset-ownership.js +285 -0
- package/dist/plugins/og-assets.js +22 -4
- package/dist/server/accept-encoding.d.ts +29 -0
- package/dist/server/accept-encoding.js +95 -0
- package/dist/server/app-browser-entry.js +8 -5
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
- package/dist/server/app-browser-rsc-redirect.js +8 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-middleware.d.ts +1 -0
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-page-dispatch.d.ts +2 -2
- package/dist/server/app-page-head.js +2 -2
- package/dist/server/app-page-request.d.ts +7 -0
- package/dist/server/app-page-request.js +19 -1
- package/dist/server/app-prerender-endpoints.d.ts +3 -3
- package/dist/server/app-prerender-endpoints.js +3 -4
- package/dist/server/app-route-module-loader.d.ts +62 -14
- package/dist/server/app-route-module-loader.js +61 -10
- package/dist/server/app-rsc-handler.d.ts +7 -6
- package/dist/server/app-rsc-handler.js +31 -26
- package/dist/server/app-rsc-route-matching.d.ts +9 -0
- package/dist/server/app-rsc-route-matching.js +13 -0
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/metadata-route-response.d.ts +3 -3
- package/dist/server/middleware-runtime.d.ts +2 -1
- package/dist/server/middleware-runtime.js +19 -8
- package/dist/server/middleware.d.ts +2 -2
- package/dist/server/middleware.js +26 -11
- package/dist/server/pages-request-pipeline.d.ts +3 -1
- package/dist/server/pages-request-pipeline.js +4 -2
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +85 -72
- package/dist/shims/font-google-base.d.ts +1 -0
- package/dist/shims/font-google-base.js +6 -21
- package/dist/shims/internal/interpolate-as.d.ts +25 -0
- package/dist/shims/internal/interpolate-as.js +196 -0
- package/dist/shims/link.js +24 -1
- package/dist/shims/navigation.js +1 -1
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/router.d.ts +1 -3
- package/dist/shims/router.js +62 -18
- package/dist/typegen.js +2 -10
- package/dist/utils/project.d.ts +7 -1
- package/dist/utils/project.js +16 -2
- package/package.json +3 -3
|
@@ -16,12 +16,12 @@ import DefaultGlobalError from "../shims/default-global-error.js";
|
|
|
16
16
|
import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
|
|
17
17
|
import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
|
|
18
18
|
import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
|
|
19
|
+
import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
|
|
20
|
+
import { runWithRootParamsScope } from "../shims/root-params.js";
|
|
19
21
|
import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
|
|
20
22
|
import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
|
|
21
23
|
import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
|
|
22
24
|
import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
|
|
23
|
-
import { runWithRootParamsScope } from "../shims/root-params.js";
|
|
24
|
-
import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
|
|
25
25
|
import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
|
|
26
26
|
import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
|
|
27
27
|
import { ssrAppRouterInstance } from "./app-ssr-router-instance.js";
|
package/dist/server/headers.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ declare const VINEXT_TIMING_HEADER = "x-vinext-timing";
|
|
|
23
23
|
declare const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
|
|
24
24
|
/** URL-encoded JSON route params for build-time prerender renders. */
|
|
25
25
|
declare const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
|
|
26
|
+
/** Internal endpoint used to evaluate App Router generateStaticParams exports. */
|
|
27
|
+
declare const VINEXT_PRERENDER_STATIC_PARAMS_PATH = "/__vinext/prerender/static-params";
|
|
28
|
+
/** Internal endpoint used to evaluate Pages Router getStaticPaths exports. */
|
|
29
|
+
declare const VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH = "/__vinext/prerender/pages-static-paths";
|
|
26
30
|
/** TPR (Tailored Per-Request) revalidation interval in seconds. */
|
|
27
31
|
declare const VINEXT_REVALIDATE_HEADER = "x-vinext-revalidate";
|
|
28
32
|
/** Marker on cached ISR entries indicating RSC payload (value "1"). */
|
|
@@ -107,4 +111,4 @@ declare const INTERNAL_HEADERS: string[];
|
|
|
107
111
|
/** Vinext-only internal headers stripped alongside Next.js protocol internals. */
|
|
108
112
|
declare const VINEXT_INTERNAL_HEADERS: string[];
|
|
109
113
|
//#endregion
|
|
110
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
114
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
package/dist/server/headers.js
CHANGED
|
@@ -23,6 +23,10 @@ const VINEXT_TIMING_HEADER = "x-vinext-timing";
|
|
|
23
23
|
const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
|
|
24
24
|
/** URL-encoded JSON route params for build-time prerender renders. */
|
|
25
25
|
const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
|
|
26
|
+
/** Internal endpoint used to evaluate App Router generateStaticParams exports. */
|
|
27
|
+
const VINEXT_PRERENDER_STATIC_PARAMS_PATH = "/__vinext/prerender/static-params";
|
|
28
|
+
/** Internal endpoint used to evaluate Pages Router getStaticPaths exports. */
|
|
29
|
+
const VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH = "/__vinext/prerender/pages-static-paths";
|
|
26
30
|
/** TPR (Tailored Per-Request) revalidation interval in seconds. */
|
|
27
31
|
const VINEXT_REVALIDATE_HEADER = "x-vinext-revalidate";
|
|
28
32
|
/** Marker on cached ISR entries indicating RSC payload (value "1"). */
|
|
@@ -129,4 +133,4 @@ const INTERNAL_HEADERS = [
|
|
|
129
133
|
/** Vinext-only internal headers stripped alongside Next.js protocol internals. */
|
|
130
134
|
const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
|
|
131
135
|
//#endregion
|
|
132
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
136
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
@@ -2,15 +2,15 @@ import { MetadataFileRoute } from "./metadata-routes.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/server/metadata-route-response.d.ts
|
|
4
4
|
type AppPageParams = Record<string, string | string[]>;
|
|
5
|
-
type
|
|
5
|
+
type MetadataRouteMakeThenableParams = (params: AppPageParams) => unknown;
|
|
6
6
|
type MetadataRuntimeRoute = MetadataFileRoute & {
|
|
7
7
|
fileDataBase64?: string;
|
|
8
8
|
};
|
|
9
9
|
type MetadataRouteRequestOptions = {
|
|
10
10
|
metadataRoutes: readonly MetadataRuntimeRoute[];
|
|
11
11
|
cleanPathname: string;
|
|
12
|
-
makeThenableParams:
|
|
12
|
+
makeThenableParams: MetadataRouteMakeThenableParams;
|
|
13
13
|
};
|
|
14
14
|
declare function handleMetadataRouteRequest(options: MetadataRouteRequestOptions): Promise<Response | null>;
|
|
15
15
|
//#endregion
|
|
16
|
-
export { handleMetadataRouteRequest };
|
|
16
|
+
export { MetadataRouteMakeThenableParams, MetadataRuntimeRoute, handleMetadataRouteRequest };
|
|
@@ -56,6 +56,7 @@ type ExecuteMiddlewareOptions = {
|
|
|
56
56
|
type RunGeneratedMiddlewareOptions = ExecuteMiddlewareOptions & {
|
|
57
57
|
ctx?: ExecutionContextLike;
|
|
58
58
|
};
|
|
59
|
+
declare function createMiddlewareMissingExportError(filePath: string | undefined, isProxy: boolean): Error;
|
|
59
60
|
declare function resolveMiddlewareModuleHandler(mod: MiddlewareModule, options: {
|
|
60
61
|
filePath?: string;
|
|
61
62
|
isProxy: boolean;
|
|
@@ -63,4 +64,4 @@ declare function resolveMiddlewareModuleHandler(mod: MiddlewareModule, options:
|
|
|
63
64
|
declare function executeMiddleware(options: ExecuteMiddlewareOptions): Promise<MiddlewareResult>;
|
|
64
65
|
declare function runGeneratedMiddleware(options: RunGeneratedMiddlewareOptions): Promise<MiddlewareResult>;
|
|
65
66
|
//#endregion
|
|
66
|
-
export { MiddlewareModule, MiddlewareResult, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
|
|
67
|
+
export { MiddlewareModule, MiddlewareResult, createMiddlewareMissingExportError, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
|
|
@@ -9,6 +9,7 @@ import { normalizePath } from "./normalize-path.js";
|
|
|
9
9
|
import { matchesMiddleware } from "./middleware-matcher.js";
|
|
10
10
|
import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
|
|
11
11
|
import { processMiddlewareHeaders } from "./request-pipeline.js";
|
|
12
|
+
import path from "node:path";
|
|
12
13
|
//#region src/server/middleware-runtime.ts
|
|
13
14
|
function isMiddlewareHandler(value) {
|
|
14
15
|
return typeof value === "function";
|
|
@@ -16,19 +17,29 @@ function isMiddlewareHandler(value) {
|
|
|
16
17
|
function isMiddlewareConfigExport(value) {
|
|
17
18
|
return !!value && typeof value === "object";
|
|
18
19
|
}
|
|
19
|
-
function middlewareFileLabel(isProxy) {
|
|
20
|
-
return isProxy ? "Proxy" : "Middleware";
|
|
21
|
-
}
|
|
22
20
|
function middlewareExpectedExport(isProxy) {
|
|
23
21
|
return isProxy ? "proxy" : "middleware";
|
|
24
22
|
}
|
|
23
|
+
function middlewareDisplayPath(filePath) {
|
|
24
|
+
const fileName = path.basename(filePath);
|
|
25
|
+
return path.basename(path.dirname(filePath)) === "src" ? `./src/${fileName}` : `./${fileName}`;
|
|
26
|
+
}
|
|
27
|
+
function createMiddlewareMissingExportError(filePath, isProxy) {
|
|
28
|
+
const expectedExport = middlewareExpectedExport(isProxy);
|
|
29
|
+
const displayPath = filePath ? middlewareDisplayPath(filePath) : void 0;
|
|
30
|
+
const resolvedPath = displayPath ? ` "${displayPath}"` : "";
|
|
31
|
+
const migrationReason = isProxy ? "- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.\n" : "";
|
|
32
|
+
return /* @__PURE__ */ new Error(`The file${resolvedPath} must export a function, either as a default export or as a named "${expectedExport}" export.\nThis function is what Next.js runs for every request handled by this ${isProxy ? "proxy (previously called middleware)" : "middleware"}.\n\nWhy this happens:\n` + migrationReason + `- The file exists but doesn't export a function.
|
|
33
|
+
- The export is not a function (e.g., an object or constant).
|
|
34
|
+
- There's a syntax error preventing the export from being recognized.
|
|
35
|
+
|
|
36
|
+
To fix it:
|
|
37
|
+
- Ensure this file has either a default or "${expectedExport}" function export.\n\nLearn more: https://nextjs.org/docs/messages/middleware-to-proxy`);
|
|
38
|
+
}
|
|
25
39
|
function resolveMiddlewareModuleHandler(mod, options) {
|
|
26
40
|
const handler = options.isProxy ? mod.proxy ?? mod.default : mod.middleware ?? mod.default;
|
|
27
41
|
if (isMiddlewareHandler(handler)) return handler;
|
|
28
|
-
|
|
29
|
-
const expectedExport = middlewareExpectedExport(options.isProxy);
|
|
30
|
-
const fileSuffix = options.filePath ? ` "${options.filePath}"` : "";
|
|
31
|
-
throw new Error(`The ${fileLabel} file${fileSuffix} must export a function named \`${expectedExport}\` or a \`default\` function.`);
|
|
42
|
+
throw createMiddlewareMissingExportError(options.filePath, options.isProxy);
|
|
32
43
|
}
|
|
33
44
|
function middlewareMatcher(mod) {
|
|
34
45
|
const config = mod.config;
|
|
@@ -226,4 +237,4 @@ async function runGeneratedMiddleware(options) {
|
|
|
226
237
|
return options.ctx ? runWithExecutionContext(options.ctx, run) : run();
|
|
227
238
|
}
|
|
228
239
|
//#endregion
|
|
229
|
-
export { executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
|
|
240
|
+
export { createMiddlewareMissingExportError, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
|
|
@@ -28,7 +28,7 @@ declare function isProxyFile(filePath: string): boolean;
|
|
|
28
28
|
*/
|
|
29
29
|
declare function resolveMiddlewareHandler(mod: MiddlewareModule, filePath: string): (request: NextRequest, event: NextFetchEvent) => Response | undefined | void | Promise<Response | undefined | void>;
|
|
30
30
|
/**
|
|
31
|
-
* Find the proxy or middleware file
|
|
31
|
+
* Find the proxy or middleware file at the selected app/pages convention level.
|
|
32
32
|
* Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
|
|
33
33
|
* If middleware.ts is found, logs a deprecation warning.
|
|
34
34
|
*
|
|
@@ -44,7 +44,7 @@ declare function resolveMiddlewareHandler(mod: MiddlewareModule, filePath: strin
|
|
|
44
44
|
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts
|
|
45
45
|
* (search for "MIDDLEWARE_FILENAME" + "file convention is deprecated")
|
|
46
46
|
*/
|
|
47
|
-
declare function findMiddlewareFile(root: string, fileMatcher: ValidFileMatcher): string | null;
|
|
47
|
+
declare function findMiddlewareFile(root: string, fileMatcher: ValidFileMatcher, conventionDir?: string): string | null;
|
|
48
48
|
/**
|
|
49
49
|
* Load and execute middleware for a given request.
|
|
50
50
|
*
|
|
@@ -13,7 +13,8 @@ import path from "node:path";
|
|
|
13
13
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/middleware.ts
|
|
14
14
|
*/
|
|
15
15
|
function isProxyFile(filePath) {
|
|
16
|
-
|
|
16
|
+
const base = path.basename(filePath);
|
|
17
|
+
return base === "proxy" || base.startsWith("proxy.");
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Resolve the middleware/proxy handler function from a module's exports.
|
|
@@ -32,9 +33,8 @@ function resolveMiddlewareHandler(mod, filePath) {
|
|
|
32
33
|
isProxy: isProxyFile(filePath)
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
|
-
const MIDDLEWARE_LOCATIONS = ["", "src/"];
|
|
36
36
|
/**
|
|
37
|
-
* Find the proxy or middleware file
|
|
37
|
+
* Find the proxy or middleware file at the selected app/pages convention level.
|
|
38
38
|
* Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
|
|
39
39
|
* If middleware.ts is found, logs a deprecation warning.
|
|
40
40
|
*
|
|
@@ -50,18 +50,33 @@ const MIDDLEWARE_LOCATIONS = ["", "src/"];
|
|
|
50
50
|
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts
|
|
51
51
|
* (search for "MIDDLEWARE_FILENAME" + "file convention is deprecated")
|
|
52
52
|
*/
|
|
53
|
-
function findMiddlewareFile(root, fileMatcher) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
function findMiddlewareFile(root, fileMatcher, conventionDir = root) {
|
|
54
|
+
let proxyPath = null;
|
|
55
|
+
for (const ext of fileMatcher.dottedExtensions) {
|
|
56
|
+
const fullPath = path.join(conventionDir, `proxy${ext}`);
|
|
57
|
+
if (fs.existsSync(fullPath)) {
|
|
58
|
+
proxyPath = fullPath;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
let middlewarePath = null;
|
|
63
|
+
for (const ext of fileMatcher.dottedExtensions) {
|
|
64
|
+
const fullPath = path.join(conventionDir, `middleware${ext}`);
|
|
60
65
|
if (fs.existsSync(fullPath)) {
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
middlewarePath = fullPath;
|
|
67
|
+
break;
|
|
63
68
|
}
|
|
64
69
|
}
|
|
70
|
+
if (proxyPath && middlewarePath) {
|
|
71
|
+
const relativeProxyPath = `./${path.relative(root, proxyPath)}`;
|
|
72
|
+
const relativeMiddlewarePath = `./${path.relative(root, middlewarePath)}`;
|
|
73
|
+
throw new Error(`Both middleware file "${relativeMiddlewarePath}" and proxy file "${relativeProxyPath}" are detected. Please use "${relativeProxyPath}" only. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`);
|
|
74
|
+
}
|
|
75
|
+
if (proxyPath) return proxyPath;
|
|
76
|
+
if (middlewarePath) {
|
|
77
|
+
console.warn("The \"middleware\" file convention is deprecated. Please use \"proxy\" instead.\n\n To migrate automatically, run:\n npx @next/codemod@canary middleware-to-proxy .\n\n Learn more: https://nextjs.org/docs/messages/middleware-to-proxy");
|
|
78
|
+
return middlewarePath;
|
|
79
|
+
}
|
|
65
80
|
return null;
|
|
66
81
|
}
|
|
67
82
|
function isMiddlewareModule(value) {
|
|
@@ -106,7 +106,9 @@ type PagesPipelineResult = {
|
|
|
106
106
|
* ASSUMPTION: request already has internal headers filtered and basePath stripped.
|
|
107
107
|
* The adapter is responsible for that pre-processing before calling runPagesRequest.
|
|
108
108
|
* The adapter also handles: open-redirect guard, _next/static 404, image optimization,
|
|
109
|
-
* _next/data normalization
|
|
109
|
+
* _next/data normalization and classification: adapters must rewrite the data
|
|
110
|
+
* URL to its page pathname and set `isDataReq` (the source of truth here), Node
|
|
111
|
+
* decode/normalize/400, public-file serving.
|
|
110
112
|
* runPagesRequest receives a "clean" request with basePath-stripped URL.
|
|
111
113
|
*/
|
|
112
114
|
declare function runPagesRequest(request: Request, deps: PagesPipelineDeps): Promise<PagesPipelineResult>;
|
|
@@ -40,7 +40,9 @@ function wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) {
|
|
|
40
40
|
* ASSUMPTION: request already has internal headers filtered and basePath stripped.
|
|
41
41
|
* The adapter is responsible for that pre-processing before calling runPagesRequest.
|
|
42
42
|
* The adapter also handles: open-redirect guard, _next/static 404, image optimization,
|
|
43
|
-
* _next/data normalization
|
|
43
|
+
* _next/data normalization and classification: adapters must rewrite the data
|
|
44
|
+
* URL to its page pathname and set `isDataReq` (the source of truth here), Node
|
|
45
|
+
* decode/normalize/400, public-file serving.
|
|
44
46
|
* runPagesRequest receives a "clean" request with basePath-stripped URL.
|
|
45
47
|
*/
|
|
46
48
|
async function runPagesRequest(request, deps) {
|
|
@@ -54,7 +56,7 @@ async function runPagesRequest(request, deps) {
|
|
|
54
56
|
hadBasePath
|
|
55
57
|
};
|
|
56
58
|
{
|
|
57
|
-
const trailingSlashRedirect = normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
|
|
59
|
+
const trailingSlashRedirect = isDataReq ? null : normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
|
|
58
60
|
if (trailingSlashRedirect) return {
|
|
59
61
|
type: "response",
|
|
60
62
|
response: trailingSlashRedirect
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { negotiateEncoding } from "./accept-encoding.js";
|
|
1
2
|
import { StaticFileCache } from "./static-file-cache.js";
|
|
2
3
|
import { resolveRequestHost, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
3
4
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -59,7 +60,6 @@ type ProdServerOptions = {
|
|
|
59
60
|
declare const COMPRESSIBLE_TYPES: Set<string>;
|
|
60
61
|
/** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
|
|
61
62
|
declare const COMPRESS_THRESHOLD = 1024;
|
|
62
|
-
declare function negotiateEncoding(req: IncomingMessage): "zstd" | "br" | "gzip" | "deflate" | null;
|
|
63
63
|
/**
|
|
64
64
|
* Merge middleware headers and a Web Response's headers into a single
|
|
65
65
|
* record suitable for Node.js `res.writeHead()`. Uses `getSetCookie()`
|
|
@@ -19,6 +19,7 @@ import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRoutePara
|
|
|
19
19
|
import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
|
|
20
20
|
import { readPrerenderSecret } from "../build/server-manifest.js";
|
|
21
21
|
import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
|
|
22
|
+
import { negotiateEncoding, parseAcceptedEncodings, selectContentEncoding } from "./accept-encoding.js";
|
|
22
23
|
import fs from "node:fs";
|
|
23
24
|
import path from "node:path";
|
|
24
25
|
import fs$1 from "node:fs/promises";
|
|
@@ -167,25 +168,6 @@ const COMPRESSIBLE_TYPES = new Set([
|
|
|
167
168
|
/** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
|
|
168
169
|
const COMPRESS_THRESHOLD = 1024;
|
|
169
170
|
/**
|
|
170
|
-
* Parse the Accept-Encoding header and return the best supported encoding.
|
|
171
|
-
* Preference order: zstd > br > gzip > deflate > identity.
|
|
172
|
-
*
|
|
173
|
-
* zstd decompresses ~3-5x faster than brotli at similar compression ratios.
|
|
174
|
-
* Supported in Chrome 123+, Firefox 126+. Safari can decompress but doesn't
|
|
175
|
-
* send zstd in Accept-Encoding, so it transparently falls back to br/gzip.
|
|
176
|
-
*/
|
|
177
|
-
const HAS_ZSTD = typeof zlib.createZstdCompress === "function";
|
|
178
|
-
function negotiateEncoding(req) {
|
|
179
|
-
const accept = req.headers["accept-encoding"];
|
|
180
|
-
if (!accept || typeof accept !== "string") return null;
|
|
181
|
-
const lower = accept.toLowerCase();
|
|
182
|
-
if (HAS_ZSTD && lower.includes("zstd")) return "zstd";
|
|
183
|
-
if (lower.includes("br")) return "br";
|
|
184
|
-
if (lower.includes("gzip")) return "gzip";
|
|
185
|
-
if (lower.includes("deflate")) return "deflate";
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
171
|
* Create a compression stream for the given encoding.
|
|
190
172
|
*/
|
|
191
173
|
function createCompressor(encoding, mode = "default") {
|
|
@@ -265,6 +247,23 @@ function omitHeadersCaseInsensitive(headersRecord, targets) {
|
|
|
265
247
|
}
|
|
266
248
|
return filtered;
|
|
267
249
|
}
|
|
250
|
+
function mergeVaryHeader(headers, value) {
|
|
251
|
+
const merged = { ...headers };
|
|
252
|
+
const existingKey = Object.keys(merged).find((key) => key.toLowerCase() === "vary");
|
|
253
|
+
if (!existingKey) {
|
|
254
|
+
merged.Vary = value;
|
|
255
|
+
return merged;
|
|
256
|
+
}
|
|
257
|
+
const rawVary = merged[existingKey];
|
|
258
|
+
const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
|
|
259
|
+
if (existingVary.trim().length === 0) {
|
|
260
|
+
merged[existingKey] = value;
|
|
261
|
+
return merged;
|
|
262
|
+
}
|
|
263
|
+
const values = existingVary.split(",").map((entry) => entry.trim().toLowerCase());
|
|
264
|
+
if (!values.includes("*") && !values.includes(value.toLowerCase())) merged[existingKey] = `${existingVary}, ${value}`;
|
|
265
|
+
return merged;
|
|
266
|
+
}
|
|
268
267
|
function matchesIfNoneMatchHeader(ifNoneMatch, etag) {
|
|
269
268
|
if (!ifNoneMatch) return false;
|
|
270
269
|
if (ifNoneMatch === "*") return true;
|
|
@@ -318,33 +317,37 @@ function mergeWebResponse(middlewareHeaders, response, statusOverride) {
|
|
|
318
317
|
function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText) {
|
|
319
318
|
const buf = typeof body === "string" ? Buffer.from(body) : body;
|
|
320
319
|
const baseType = contentType.split(";")[0].trim();
|
|
321
|
-
const
|
|
320
|
+
const varyByEncoding = compress && COMPRESSIBLE_TYPES.has(baseType);
|
|
321
|
+
const encoding = compress ? negotiateEncoding(req) : "identity";
|
|
322
322
|
const headersWithoutBodyHeaders = omitHeadersCaseInsensitive(extraHeaders, OMIT_BODY_HEADERS);
|
|
323
|
-
const writeHead = (headers) => {
|
|
324
|
-
if (
|
|
325
|
-
else res.writeHead(
|
|
323
|
+
const writeHead = (headers, responseStatus = statusCode, responseStatusText = statusText) => {
|
|
324
|
+
if (responseStatusText) res.writeHead(responseStatus, responseStatusText, headers);
|
|
325
|
+
else res.writeHead(responseStatus, headers);
|
|
326
326
|
};
|
|
327
|
-
if (encoding &&
|
|
328
|
-
|
|
329
|
-
const rawVary = extraHeaders["Vary"] ?? extraHeaders["vary"];
|
|
330
|
-
const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
|
|
331
|
-
let varyValue;
|
|
332
|
-
if (existingVary) varyValue = existingVary.toLowerCase().includes("accept-encoding") ? existingVary : existingVary + ", Accept-Encoding";
|
|
333
|
-
else varyValue = "Accept-Encoding";
|
|
334
|
-
writeHead({
|
|
327
|
+
if (encoding !== "identity" && varyByEncoding && buf.length >= 1024) {
|
|
328
|
+
writeHead(mergeVaryHeader({
|
|
335
329
|
...headersWithoutBodyHeaders,
|
|
336
330
|
"Content-Type": contentType,
|
|
337
|
-
"Content-Encoding": encoding
|
|
338
|
-
|
|
339
|
-
|
|
331
|
+
"Content-Encoding": encoding
|
|
332
|
+
}, "Accept-Encoding"));
|
|
333
|
+
if (req.method === "HEAD") {
|
|
334
|
+
res.end();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const compressor = createCompressor(encoding);
|
|
340
338
|
compressor.end(buf);
|
|
341
339
|
pipeline(compressor, res, () => {});
|
|
342
340
|
} else {
|
|
343
|
-
|
|
341
|
+
const identityHeaders = {
|
|
344
342
|
...headersWithoutBodyHeaders,
|
|
345
343
|
"Content-Type": contentType,
|
|
346
344
|
"Content-Length": String(buf.length)
|
|
347
|
-
}
|
|
345
|
+
};
|
|
346
|
+
writeHead(varyByEncoding ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
|
|
347
|
+
if (req.method === "HEAD") {
|
|
348
|
+
res.end();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
348
351
|
res.end(buf);
|
|
349
352
|
}
|
|
350
353
|
}
|
|
@@ -378,24 +381,35 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
378
381
|
}
|
|
379
382
|
const entry = cache.lookup(lookupPath);
|
|
380
383
|
if (!entry) return false;
|
|
384
|
+
const rawAe = compress ? req.headers["accept-encoding"] : void 0;
|
|
385
|
+
const parsed = typeof rawAe === "string" ? parseAcceptedEncodings(rawAe) : void 0;
|
|
386
|
+
const availableVariants = [
|
|
387
|
+
...entry.zst ? ["zstd"] : [],
|
|
388
|
+
...entry.br ? ["br"] : [],
|
|
389
|
+
...entry.gz ? ["gzip"] : []
|
|
390
|
+
];
|
|
391
|
+
const variesByEncoding = compress && availableVariants.length > 0;
|
|
392
|
+
const selected = parsed ? selectContentEncoding(parsed, availableVariants) : "identity";
|
|
393
|
+
const variant = selected === "zstd" ? entry.zst : selected === "br" ? entry.br : selected === "gzip" ? entry.gz : entry.original;
|
|
381
394
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
382
395
|
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
|
|
383
|
-
|
|
396
|
+
const notModifiedHeaders = variesByEncoding ? mergeVaryHeader({
|
|
384
397
|
...entry.notModifiedHeaders,
|
|
385
398
|
...extraHeaders
|
|
386
|
-
})
|
|
387
|
-
|
|
399
|
+
}, "Accept-Encoding") : {
|
|
400
|
+
...entry.notModifiedHeaders,
|
|
401
|
+
...extraHeaders
|
|
402
|
+
};
|
|
403
|
+
if (selected !== "identity") notModifiedHeaders["Content-Encoding"] = selected;
|
|
404
|
+
res.writeHead(304, notModifiedHeaders);
|
|
388
405
|
res.end();
|
|
389
406
|
return true;
|
|
390
407
|
}
|
|
391
|
-
const
|
|
392
|
-
const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
|
|
393
|
-
const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
|
|
394
|
-
if (extraHeaders) res.writeHead(responseStatus, {
|
|
408
|
+
const responseHeaders = {
|
|
395
409
|
...variant.headers,
|
|
396
410
|
...extraHeaders
|
|
397
|
-
}
|
|
398
|
-
|
|
411
|
+
};
|
|
412
|
+
res.writeHead(responseStatus, variesByEncoding ? mergeVaryHeader(responseHeaders, "Accept-Encoding") : responseHeaders);
|
|
399
413
|
if (omitBody || req.method === "HEAD") {
|
|
400
414
|
res.end();
|
|
401
415
|
return true;
|
|
@@ -428,18 +442,6 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
428
442
|
const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
|
|
429
443
|
const baseType = ct.split(";")[0].trim();
|
|
430
444
|
const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
|
|
431
|
-
const ifNoneMatch = req.headers["if-none-match"];
|
|
432
|
-
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
433
|
-
const notModifiedHeaders = {
|
|
434
|
-
ETag: etag,
|
|
435
|
-
"Cache-Control": cacheControl,
|
|
436
|
-
...isCompressible ? { Vary: "Accept-Encoding" } : void 0,
|
|
437
|
-
...extraHeaders
|
|
438
|
-
};
|
|
439
|
-
res.writeHead(304, notModifiedHeaders);
|
|
440
|
-
res.end();
|
|
441
|
-
return true;
|
|
442
|
-
}
|
|
443
445
|
const baseHeaders = {
|
|
444
446
|
"Content-Type": ct,
|
|
445
447
|
"Cache-Control": cacheControl,
|
|
@@ -448,12 +450,19 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
448
450
|
};
|
|
449
451
|
if (isCompressible) {
|
|
450
452
|
const encoding = negotiateEncoding(req);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
454
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
455
|
+
const notModifiedHeaders = mergeVaryHeader(baseHeaders, "Accept-Encoding");
|
|
456
|
+
if (encoding !== "identity") notModifiedHeaders["Content-Encoding"] = encoding;
|
|
457
|
+
res.writeHead(304, notModifiedHeaders);
|
|
458
|
+
res.end();
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
if (encoding !== "identity") {
|
|
462
|
+
res.writeHead(responseStatus, mergeVaryHeader({
|
|
453
463
|
...baseHeaders,
|
|
454
|
-
"Content-Encoding": encoding
|
|
455
|
-
|
|
456
|
-
});
|
|
464
|
+
"Content-Encoding": encoding
|
|
465
|
+
}, "Accept-Encoding"));
|
|
457
466
|
if (omitBody || req.method === "HEAD") {
|
|
458
467
|
res.end();
|
|
459
468
|
return true;
|
|
@@ -468,10 +477,17 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
468
477
|
return true;
|
|
469
478
|
}
|
|
470
479
|
}
|
|
471
|
-
|
|
480
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
481
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
482
|
+
res.writeHead(304, isCompressible ? mergeVaryHeader(baseHeaders, "Accept-Encoding") : baseHeaders);
|
|
483
|
+
res.end();
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
const identityHeaders = {
|
|
472
487
|
...baseHeaders,
|
|
473
488
|
"Content-Length": String(resolved.size)
|
|
474
|
-
}
|
|
489
|
+
};
|
|
490
|
+
res.writeHead(responseStatus, isCompressible ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
|
|
475
491
|
if (omitBody || req.method === "HEAD") {
|
|
476
492
|
res.end();
|
|
477
493
|
return true;
|
|
@@ -568,25 +584,22 @@ async function sendWebResponse(webResponse, req, res, compress) {
|
|
|
568
584
|
if (existing !== void 0) nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
569
585
|
else nodeHeaders[key] = value;
|
|
570
586
|
});
|
|
587
|
+
const alreadyEncoded = webResponse.headers.get("content-encoding") !== null;
|
|
571
588
|
if (!webResponse.body) {
|
|
572
589
|
writeHead(nodeHeaders);
|
|
573
590
|
res.end();
|
|
574
591
|
return;
|
|
575
592
|
}
|
|
576
|
-
const alreadyEncoded = webResponse.headers.has("content-encoding");
|
|
577
593
|
const baseType = (webResponse.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
578
|
-
const
|
|
579
|
-
const
|
|
594
|
+
const varyByEncoding = compress && !alreadyEncoded && COMPRESSIBLE_TYPES.has(baseType);
|
|
595
|
+
const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : "identity";
|
|
596
|
+
const shouldCompress = encoding !== "identity" && COMPRESSIBLE_TYPES.has(baseType);
|
|
580
597
|
if (shouldCompress) {
|
|
581
598
|
delete nodeHeaders["content-length"];
|
|
582
599
|
delete nodeHeaders["Content-Length"];
|
|
583
600
|
nodeHeaders["Content-Encoding"] = encoding;
|
|
584
|
-
const existingVary = nodeHeaders["Vary"] ?? nodeHeaders["vary"];
|
|
585
|
-
if (existingVary) {
|
|
586
|
-
if (!String(existingVary).toLowerCase().includes("accept-encoding")) nodeHeaders["Vary"] = existingVary + ", Accept-Encoding";
|
|
587
|
-
} else nodeHeaders["Vary"] = "Accept-Encoding";
|
|
588
601
|
}
|
|
589
|
-
writeHead(nodeHeaders);
|
|
602
|
+
writeHead(varyByEncoding ? mergeVaryHeader(nodeHeaders, "Accept-Encoding") : nodeHeaders);
|
|
590
603
|
if (req.method === "HEAD") {
|
|
591
604
|
cancelResponseBody(webResponse);
|
|
592
605
|
res.end();
|
|
@@ -206,27 +206,12 @@ function getSSRFontPreloads() {
|
|
|
206
206
|
return [...ssrFontPreloads];
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
|
-
*
|
|
210
|
-
* Parses url('...') references from the CSS text.
|
|
211
|
-
*/
|
|
212
|
-
function extractFontUrlsFromCSS(css) {
|
|
213
|
-
const urls = [];
|
|
214
|
-
const urlRegex = /url\(['"]?([^'")]+)['"]?\)/g;
|
|
215
|
-
let match;
|
|
216
|
-
while ((match = urlRegex.exec(css)) !== null) {
|
|
217
|
-
const url = match[1];
|
|
218
|
-
if (url && url.startsWith("/")) urls.push(url);
|
|
219
|
-
}
|
|
220
|
-
return urls;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Collect font file URLs from self-hosted CSS for preload link generation.
|
|
209
|
+
* Collect build-selected font file URLs for preload link generation.
|
|
224
210
|
* Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.
|
|
225
211
|
*/
|
|
226
|
-
function
|
|
212
|
+
function collectFontPreloads(urls) {
|
|
227
213
|
if (typeof document !== "undefined") return;
|
|
228
|
-
const urls
|
|
229
|
-
for (const href of urls) if (!ssrFontPreloadHrefs.has(href)) {
|
|
214
|
+
for (const href of urls) if (href.startsWith("/") && !ssrFontPreloadHrefs.has(href)) {
|
|
230
215
|
ssrFontPreloadHrefs.add(href);
|
|
231
216
|
ssrFontPreloads.push({
|
|
232
217
|
href,
|
|
@@ -240,10 +225,10 @@ const injectedSelfHosted = /* @__PURE__ */ new Set();
|
|
|
240
225
|
* Inject self-hosted @font-face CSS (from the build plugin).
|
|
241
226
|
* This replaces the CDN <link> tag with inline CSS.
|
|
242
227
|
*/
|
|
243
|
-
function injectSelfHostedCSS(css) {
|
|
228
|
+
function injectSelfHostedCSS(css, preloadUrls = []) {
|
|
229
|
+
collectFontPreloads(preloadUrls);
|
|
244
230
|
if (injectedSelfHosted.has(css)) return;
|
|
245
231
|
injectedSelfHosted.add(css);
|
|
246
|
-
collectFontPreloadsFromCSS(css);
|
|
247
232
|
if (typeof document === "undefined") {
|
|
248
233
|
ssrFontStyles.push(css);
|
|
249
234
|
return;
|
|
@@ -277,7 +262,7 @@ function createFontLoader(family) {
|
|
|
277
262
|
internalStyle: internal?.fontStyle,
|
|
278
263
|
google: true
|
|
279
264
|
});
|
|
280
|
-
if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS);
|
|
265
|
+
if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS, internal.preloadUrls);
|
|
281
266
|
else {
|
|
282
267
|
const url = buildGoogleFontsUrl(family, options);
|
|
283
268
|
injectFontStylesheet(url);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { UrlQuery } from "../../utils/query.js";
|
|
2
|
+
|
|
3
|
+
//#region src/shims/internal/interpolate-as.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wire-compatible alias for Node's `querystring.ParsedUrlQuery`. Inlined here
|
|
6
|
+
* so this module has no dependency on the `querystring` types package.
|
|
7
|
+
*/
|
|
8
|
+
type ParsedUrlQuery = {
|
|
9
|
+
[key: string]: string | string[] | undefined;
|
|
10
|
+
};
|
|
11
|
+
type DynamicRouteHrefProjection = {
|
|
12
|
+
href: string;
|
|
13
|
+
params: string[];
|
|
14
|
+
query: ParsedUrlQuery;
|
|
15
|
+
routePathname: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a bracket-pattern route href against its displayed href. Query
|
|
19
|
+
* values can be supplied directly (object-form hrefs) or parsed from the route
|
|
20
|
+
* href (string-form hrefs). A `?` after `#` is part of the fragment, not a
|
|
21
|
+
* query delimiter.
|
|
22
|
+
*/
|
|
23
|
+
declare function interpolateDynamicRouteHref(routeHref: string, asHref: string, queryInput?: UrlQuery): DynamicRouteHrefProjection | null;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { DynamicRouteHrefProjection, interpolateDynamicRouteHref };
|