vinext 1.2.1 → 1.3.0
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 +12 -9
- package/dist/build/client-build-config.d.ts +64 -3
- package/dist/build/client-build-config.js +131 -4
- package/dist/build/client-reference-groups.d.ts +163 -0
- package/dist/build/client-reference-groups.js +534 -0
- package/dist/build/module-dependency-cache.d.ts +31 -0
- package/dist/build/module-dependency-cache.js +51 -1
- package/dist/build/next-client-runtime-manifests.d.ts +10 -1
- package/dist/build/next-client-runtime-manifests.js +19 -4
- package/dist/build/nitro-public-files.d.ts +24 -0
- package/dist/build/nitro-public-files.js +60 -0
- package/dist/build/pages-client-assets-module.js +1 -1
- package/dist/build/prerender-paths.js +5 -2
- package/dist/build/server-actions-encryption-key.d.ts +23 -0
- package/dist/build/server-actions-encryption-key.js +101 -0
- package/dist/build/ssr-manifest.d.ts +3 -0
- package/dist/build/ssr-manifest.js +10 -1
- package/dist/client/client-rewrite-params.d.ts +9 -0
- package/dist/client/client-rewrite-params.js +20 -0
- package/dist/client/dev-error-overlay.js +2 -1
- package/dist/client/navigation-runtime.d.ts +25 -2
- package/dist/client/navigation-runtime.js +42 -11
- package/dist/client/vinext-next-data.d.ts +0 -1
- package/dist/config/config-matchers.d.ts +15 -40
- package/dist/config/config-matchers.js +183 -255
- package/dist/config/config-source.d.ts +34 -0
- package/dist/config/config-source.js +141 -0
- package/dist/config/header-params.d.ts +29 -0
- package/dist/config/header-params.js +106 -0
- package/dist/config/next-config.d.ts +7 -5
- package/dist/config/next-config.js +120 -7
- package/dist/entries/app-browser-entry.d.ts +14 -1
- package/dist/entries/app-browser-entry.js +26 -36
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +92 -57
- package/dist/entries/app-rsc-manifest.d.ts +10 -0
- package/dist/entries/app-rsc-manifest.js +20 -1
- package/dist/entries/app-ssr-entry.d.ts +4 -1
- package/dist/entries/app-ssr-entry.js +9 -2
- package/dist/entries/pages-client-entry.d.ts +1 -0
- package/dist/entries/pages-client-entry.js +6 -6
- package/dist/entries/pages-server-entry.js +17 -14
- package/dist/index.d.ts +11 -0
- package/dist/index.js +239 -83
- package/dist/init.js +17 -0
- package/dist/plugins/optimize-imports.d.ts +25 -2
- package/dist/plugins/optimize-imports.js +106 -20
- package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
- package/dist/plugins/rsc-client-reference-loaders.js +336 -18
- package/dist/plugins/use-cache-callable.d.ts +5 -0
- package/dist/plugins/use-cache-callable.js +30 -8
- package/dist/plugins/worker-image-imports.d.ts +1 -0
- package/dist/plugins/worker-image-imports.js +1 -1
- package/dist/routing/app-route-graph.d.ts +1 -1
- package/dist/routing/browser-route-manifest.d.ts +43 -0
- package/dist/routing/browser-route-manifest.js +195 -0
- package/dist/routing/hashed-route-patterns.d.ts +4 -0
- package/dist/routing/hashed-route-patterns.js +22 -0
- package/dist/routing/optimistic-route-trie.d.ts +27 -0
- package/dist/routing/optimistic-route-trie.js +102 -0
- package/dist/routing/route-validation.d.ts +6 -1
- package/dist/routing/route-validation.js +26 -10
- package/dist/server/app-action-forwarding.d.ts +8 -0
- package/dist/server/app-action-forwarding.js +15 -1
- package/dist/server/app-browser-entry.js +147 -60
- package/dist/server/app-browser-error.d.ts +3 -1
- package/dist/server/app-browser-error.js +7 -2
- package/dist/server/app-browser-interception-context.d.ts +2 -2
- package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
- package/dist/server/app-browser-mpa-navigation.js +6 -5
- package/dist/server/app-browser-navigation-controller.d.ts +17 -6
- package/dist/server/app-browser-navigation-controller.js +88 -26
- package/dist/server/app-browser-state.d.ts +2 -2
- package/dist/server/app-browser-visible-commit.d.ts +4 -4
- package/dist/server/app-client-reference-preloader.d.ts +23 -0
- package/dist/server/app-client-reference-preloader.js +30 -7
- package/dist/server/app-fallback-renderer.js +2 -1
- package/dist/server/app-middleware.d.ts +6 -0
- package/dist/server/app-middleware.js +8 -10
- package/dist/server/app-optimistic-routing.d.ts +8 -10
- package/dist/server/app-optimistic-routing.js +4 -95
- package/dist/server/app-page-boundary-render.js +8 -1
- package/dist/server/app-page-cache-render.js +1 -0
- package/dist/server/app-page-dispatch.js +2 -0
- package/dist/server/app-page-render.js +3 -1
- package/dist/server/app-page-rsc-render-status.d.ts +1 -0
- package/dist/server/app-page-rsc-render-status.js +1 -0
- package/dist/server/app-page-search-params-observation.d.ts +1 -4
- package/dist/server/app-page-search-params-observation.js +8 -10
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-pages-bridge.d.ts +2 -1
- package/dist/server/app-pages-bridge.js +11 -7
- package/dist/server/app-prerender-static-params.js +3 -2
- package/dist/server/app-render-dependency.js +42 -2
- package/dist/server/app-request-stage-independent-entry.js +2 -2
- package/dist/server/app-response-stage-entry.js +3 -3
- package/dist/server/app-route-handler-dispatch.js +3 -1
- package/dist/server/app-route-handler-execution.d.ts +2 -0
- package/dist/server/app-route-handler-execution.js +9 -2
- package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
- package/dist/server/app-route-handler-middleware-context.js +3 -3
- package/dist/server/app-route-handler-response.d.ts +2 -1
- package/dist/server/app-route-handler-response.js +2 -2
- package/dist/server/app-route-handler-runtime.d.ts +2 -0
- package/dist/server/app-route-handler-runtime.js +20 -3
- package/dist/server/app-route-module-loader.js +7 -6
- package/dist/server/app-route-tree.d.ts +20 -0
- package/dist/server/app-route-tree.js +18 -0
- package/dist/server/app-router-entry.js +2 -2
- package/dist/server/app-rsc-combined-handler.js +8 -1
- package/dist/server/app-rsc-errors.d.ts +7 -0
- package/dist/server/app-rsc-errors.js +9 -2
- package/dist/server/app-rsc-handler.d.ts +4 -1
- package/dist/server/app-rsc-handler.js +146 -130
- package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.js +3 -1
- package/dist/server/app-rsc-route-matching.d.ts +1 -0
- package/dist/server/app-rsc-route-matching.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +23 -6
- package/dist/server/app-server-action-execution.js +50 -56
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +16 -7
- package/dist/server/app-ssr-stream.d.ts +7 -0
- package/dist/server/app-ssr-stream.js +13 -1
- package/dist/server/config-headers.js +12 -3
- package/dist/server/isolate-shared-promise.d.ts +10 -0
- package/dist/server/isolate-shared-promise.js +33 -0
- package/dist/server/isr-cache.js +3 -2
- package/dist/server/middleware-matcher-pattern.d.ts +0 -1
- package/dist/server/middleware-matcher-pattern.js +11 -140
- package/dist/server/middleware-matcher.js +5 -6
- package/dist/server/middleware-response-headers.d.ts +31 -0
- package/dist/server/middleware-response-headers.js +57 -1
- package/dist/server/middleware-runtime.js +26 -13
- package/dist/server/navigation-planner.d.ts +2 -2
- package/dist/server/nitro-public-files-plugin.d.ts +38 -0
- package/dist/server/nitro-public-files-plugin.js +106 -0
- package/dist/server/nitro-public-files.d.ts +21 -0
- package/dist/server/nitro-public-files.js +39 -0
- package/dist/server/pages-asset-tags.d.ts +5 -0
- package/dist/server/pages-asset-tags.js +9 -2
- package/dist/server/pages-client-assets.d.ts +4 -1
- package/dist/server/pages-i18n.d.ts +18 -0
- package/dist/server/pages-i18n.js +18 -2
- package/dist/server/pages-node-compat.d.ts +5 -3
- package/dist/server/pages-node-compat.js +35 -16
- package/dist/server/pages-request-pipeline.d.ts +6 -1
- package/dist/server/pages-request-pipeline.js +31 -35
- package/dist/server/pages-request-stage-entry.js +16 -6
- package/dist/server/pages-response-stage-entry.js +1 -1
- package/dist/server/pages-revalidate.js +1 -0
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +71 -33
- package/dist/server/proxy-trust.d.ts +6 -0
- package/dist/server/proxy-trust.js +12 -6
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +12 -1
- package/dist/server/server-reference-loader.js +10 -1
- package/dist/server/static-file-cache.js +15 -3
- package/dist/server/worker-utils.js +1 -1
- package/dist/shims/cache-handler.js +3 -2
- package/dist/shims/cache-request-state.d.ts +40 -3
- package/dist/shims/cache-request-state.js +70 -5
- package/dist/shims/cache-runtime.js +47 -19
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +17 -8
- package/dist/shims/cdn-purge-routes.d.ts +24 -0
- package/dist/shims/cdn-purge-routes.js +77 -0
- package/dist/shims/dynamic.js +17 -4
- package/dist/shims/form.js +8 -3
- package/dist/shims/image-props.d.ts +93 -0
- package/dist/shims/image-props.js +323 -0
- package/dist/shims/image.d.ts +4 -18
- package/dist/shims/image.js +4 -319
- package/dist/shims/internal/headers-state.d.ts +7 -0
- package/dist/shims/internal/headers-state.js +14 -2
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +31 -14
- package/dist/shims/internal/hybrid-client-route-owner.js +8 -8
- package/dist/shims/link.js +5 -3
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.js +17 -5
- package/dist/shims/og.js +35 -10
- package/dist/shims/request-context.d.ts +13 -0
- package/dist/shims/router.js +19 -40
- package/dist/shims/server.d.ts +31 -4
- package/dist/shims/server.js +58 -10
- package/dist/shims/thenable-params.d.ts +6 -1
- package/dist/shims/thenable-params.js +11 -2
- package/dist/shims/unified-request-context.d.ts +5 -0
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/utils/asset-prefix.d.ts +7 -0
- package/dist/utils/asset-prefix.js +10 -1
- package/dist/utils/client-build-manifest.js +12 -5
- package/dist/utils/client-runtime-metadata.d.ts +1 -0
- package/dist/utils/client-runtime-metadata.js +15 -2
- package/dist/utils/lazy-chunks.d.ts +4 -0
- package/dist/utils/manifest-paths.d.ts +14 -0
- package/dist/utils/manifest-paths.js +22 -1
- package/dist/utils/path-to-regexp.d.ts +21 -0
- package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
- package/dist/utils/plugin-react-version.d.ts +15 -0
- package/dist/utils/plugin-react-version.js +64 -0
- package/dist/utils/public-routes.d.ts +2 -0
- package/dist/utils/public-routes.js +18 -7
- package/package.json +1 -1
- package/dist/server/middleware-path-to-regexp.d.ts +0 -13
- package/dist/utils/regex-safety.d.ts +0 -17
- package/dist/utils/regex-safety.js +0 -789
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { normalizePathTokens, parsePathPattern, pathTokensToRegExpSource } from "../utils/path-to-regexp.js";
|
|
2
|
+
//#region src/config/config-source.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compiles and matches next.config.js redirect, rewrite and header sources.
|
|
5
|
+
*
|
|
6
|
+
* Kept apart from config-matchers so the Pages router can match sources
|
|
7
|
+
* synchronously with the same code as the server, without bundling the rest
|
|
8
|
+
* of config-matchers.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Cache for compiled redirect/rewrite/header sources.
|
|
12
|
+
*
|
|
13
|
+
* Sources come from next.config.js and never change at runtime. Without
|
|
14
|
+
* caching, every request re-parses and recompiles every rule. On apps with
|
|
15
|
+
* many locale-prefixed rules this dominated profiling at ~2.4 seconds of CPU
|
|
16
|
+
* self-time.
|
|
17
|
+
*
|
|
18
|
+
* Value is `null` when the source is invalid, so we skip it on subsequent
|
|
19
|
+
* requests too without re-parsing it.
|
|
20
|
+
*/
|
|
21
|
+
const _compiledSourceCache = /* @__PURE__ */ new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Compile a redirect/rewrite/header source the way Next.js does at runtime:
|
|
24
|
+
* path-to-regexp 6 with `strict: true`, `delimiter: "/"` and case-insensitive
|
|
25
|
+
* matching, then `modifyRouteRegex` appends an optional trailing slash.
|
|
26
|
+
*
|
|
27
|
+
* Returns an error for sources path-to-regexp rejects. Like Next.js, custom
|
|
28
|
+
* param patterns are not checked for catastrophic backtracking.
|
|
29
|
+
*
|
|
30
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/server/lib/router-utils/filesystem.ts
|
|
31
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/path-match.ts
|
|
32
|
+
*/
|
|
33
|
+
function compileConfigSourceUncached(source) {
|
|
34
|
+
const parsed = parseConfigSource(source);
|
|
35
|
+
if ("kind" in parsed) return parsed;
|
|
36
|
+
const { tokens, re } = parsed;
|
|
37
|
+
return {
|
|
38
|
+
re,
|
|
39
|
+
keys: tokens.filter((token) => typeof token !== "string" && !!token.pattern)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseConfigSource(source) {
|
|
43
|
+
try {
|
|
44
|
+
const tokens = parsePathPattern(source, { delimiter: "/" });
|
|
45
|
+
return {
|
|
46
|
+
tokens,
|
|
47
|
+
re: new RegExp(`${pathTokensToRegExpSource(tokens)}(?:\\/)?$`, "i")
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "invalid",
|
|
52
|
+
error: error instanceof Error ? error.message : String(error)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Length of the RegExp Next.js builds when validating a source
|
|
58
|
+
* (tryToParsePath: path-to-regexp defaults, retried with normalized repeat
|
|
59
|
+
* tokens), or null when it cannot be built.
|
|
60
|
+
*/
|
|
61
|
+
function validationRegExpLength(source) {
|
|
62
|
+
try {
|
|
63
|
+
const tokens = parsePathPattern(source);
|
|
64
|
+
const build = (value) => new RegExp(`${pathTokensToRegExpSource(value)}[\\/#\\?]?$`).source.length;
|
|
65
|
+
try {
|
|
66
|
+
return build(tokens);
|
|
67
|
+
} catch {
|
|
68
|
+
return build(normalizePathTokens(tokens));
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Why `source` is not a valid redirects/rewrites/headers source, or null.
|
|
76
|
+
* Mirrors the source checks in Next.js's checkCustomRoutes, which fail the
|
|
77
|
+
* build; config loading calls this to do the same.
|
|
78
|
+
*
|
|
79
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/lib/load-custom-routes.ts
|
|
80
|
+
*/
|
|
81
|
+
function configSourceError(source) {
|
|
82
|
+
if (!source) return "`source` is missing";
|
|
83
|
+
if (typeof source !== "string") return "`source` is not a string";
|
|
84
|
+
if (!source.startsWith("/")) return "`source` does not start with /";
|
|
85
|
+
const parsed = parseConfigSource(source);
|
|
86
|
+
if ("kind" in parsed) return parsed.error;
|
|
87
|
+
const length = validationRegExpLength(source);
|
|
88
|
+
if (length !== null && length > 4096) return "`source` exceeds max built length of 4096";
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function compileConfigSource(source) {
|
|
92
|
+
let compiled = _compiledSourceCache.get(source);
|
|
93
|
+
if (compiled !== void 0) return compiled;
|
|
94
|
+
const result = compileConfigSourceUncached(source);
|
|
95
|
+
if ("kind" in result) {
|
|
96
|
+
console.warn(`[vinext] Ignoring invalid next.config.js source "${source}": ${result.error}`);
|
|
97
|
+
compiled = null;
|
|
98
|
+
} else compiled = result;
|
|
99
|
+
_compiledSourceCache.set(source, compiled);
|
|
100
|
+
return compiled;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Match a Next.js config source (from redirects/rewrites/headers) against a
|
|
104
|
+
* pathname. Returns the named params or null.
|
|
105
|
+
*
|
|
106
|
+
* Unnamed groups are dropped, as Next.js does with `removeUnnamedParams`.
|
|
107
|
+
* Repeated params (`:path*`, `:path+`) keep the captured string (`a/b`), and
|
|
108
|
+
* an optional param that did not match is `""`.
|
|
109
|
+
*/
|
|
110
|
+
function matchConfigPattern(pathname, pattern) {
|
|
111
|
+
return execConfigSource(pathname, pattern)?.params ?? null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* matchConfigPattern, also reporting the optional params that did not match.
|
|
115
|
+
* Next.js leaves those out of its params entirely.
|
|
116
|
+
*/
|
|
117
|
+
function execConfigSource(pathname, source) {
|
|
118
|
+
const compiled = compileConfigSource(source);
|
|
119
|
+
if (!compiled) return null;
|
|
120
|
+
const match = compiled.re.exec(pathname);
|
|
121
|
+
if (!match) return null;
|
|
122
|
+
const params = Object.create(null);
|
|
123
|
+
const matched = /* @__PURE__ */ new Set();
|
|
124
|
+
for (let i = 0; i < compiled.keys.length; i++) {
|
|
125
|
+
const key = compiled.keys[i].name;
|
|
126
|
+
if (typeof key !== "string") continue;
|
|
127
|
+
const value = match[i + 1];
|
|
128
|
+
if (value === void 0) {
|
|
129
|
+
if (!matched.has(key)) params[key] = "";
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
matched.add(key);
|
|
133
|
+
params[key] = value;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
params,
|
|
137
|
+
unmatched: Object.keys(params).filter((key) => !matched.has(key))
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { compileConfigSource, configSourceError, execConfigSource, matchConfigPattern };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/config/header-params.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Param substitution for next.config.js `headers()` keys and values.
|
|
4
|
+
*
|
|
5
|
+
* Next.js compiles each header key and value with `compileNonPath`: only
|
|
6
|
+
* `:name` markers for captured params are substituted, every other
|
|
7
|
+
* path-to-regexp metacharacter stays literal, repeated params are joined with
|
|
8
|
+
* their template prefix and suffix, and params are inserted verbatim (no
|
|
9
|
+
* encoding or validation).
|
|
10
|
+
*
|
|
11
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
12
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/route-match-utils.ts
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Params captured by a header rule: path-to-regexp source params (`*`/`+`
|
|
16
|
+
* params are arrays) merged with Next.js `matchHas` captures. A has capture
|
|
17
|
+
* can be `undefined` when its named group did not participate in the match.
|
|
18
|
+
*/
|
|
19
|
+
export type HeaderParams = Record<string, string | string[] | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Substitute `params` into a header key or value like Next.js's
|
|
22
|
+
* `compileNonPath`. Throws where Next.js throws: a repeated param referenced
|
|
23
|
+
* without `*`/`+`, an `undefined` has capture, or a template path-to-regexp
|
|
24
|
+
* cannot parse.
|
|
25
|
+
*/
|
|
26
|
+
export declare function compileHeaderParamTemplate(value: string, params: HeaderParams): string;
|
|
27
|
+
/** Whether a substituted header pair can be sent as a response header. */
|
|
28
|
+
export declare function isValidHeaderPair(key: string, value: string): boolean;
|
|
29
|
+
//#endregion
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { parsePathPattern } from "../utils/path-to-regexp.js";
|
|
2
|
+
//#region src/config/header-params.ts
|
|
3
|
+
/**
|
|
4
|
+
* Param substitution for next.config.js `headers()` keys and values.
|
|
5
|
+
*
|
|
6
|
+
* Next.js compiles each header key and value with `compileNonPath`: only
|
|
7
|
+
* `:name` markers for captured params are substituted, every other
|
|
8
|
+
* path-to-regexp metacharacter stays literal, repeated params are joined with
|
|
9
|
+
* their template prefix and suffix, and params are inserted verbatim (no
|
|
10
|
+
* encoding or validation).
|
|
11
|
+
*
|
|
12
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
13
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/route-match-utils.ts
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Substitute `params` into a header key or value like Next.js's
|
|
17
|
+
* `compileNonPath`. Throws where Next.js throws: a repeated param referenced
|
|
18
|
+
* without `*`/`+`, an `undefined` has capture, or a template path-to-regexp
|
|
19
|
+
* cannot parse.
|
|
20
|
+
*/
|
|
21
|
+
function compileHeaderParamTemplate(value, params) {
|
|
22
|
+
if (!value.includes(":")) return value;
|
|
23
|
+
const keys = Object.keys(params);
|
|
24
|
+
const cacheKey = JSON.stringify([value, keys]);
|
|
25
|
+
let compiled = _compiledTemplateCache.get(cacheKey);
|
|
26
|
+
if (!compiled) {
|
|
27
|
+
try {
|
|
28
|
+
compiled = { compile: compileTemplate(value, keys) };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
compiled = { error };
|
|
31
|
+
}
|
|
32
|
+
_compiledTemplateCache.set(cacheKey, compiled);
|
|
33
|
+
}
|
|
34
|
+
if ("error" in compiled) throw compiled.error;
|
|
35
|
+
return compiled.compile(params);
|
|
36
|
+
}
|
|
37
|
+
const _compiledTemplateCache = /* @__PURE__ */ new Map();
|
|
38
|
+
function compileTemplate(value, keys) {
|
|
39
|
+
for (const key of keys) if (value.includes(`:${key}`)) value = value.replace(new RegExp(`:${key}\\*`, "g"), `:${key}--ESCAPED_PARAM_ASTERISKS`).replace(new RegExp(`:${key}\\?`, "g"), `:${key}--ESCAPED_PARAM_QUESTION`).replace(new RegExp(`:${key}\\+`, "g"), `:${key}--ESCAPED_PARAM_PLUS`).replace(new RegExp(`:${key}(?!\\w)`, "g"), `--ESCAPED_PARAM_COLON${key}`);
|
|
40
|
+
value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, "\\$1").replace(/--ESCAPED_PARAM_PLUS/g, "+").replace(/--ESCAPED_PARAM_COLON/g, ":").replace(/--ESCAPED_PARAM_QUESTION/g, "?").replace(/--ESCAPED_PARAM_ASTERISKS/g, "*");
|
|
41
|
+
const compiler = safeCompile(`/${value}`);
|
|
42
|
+
return (params) => compiler(params).slice(1);
|
|
43
|
+
}
|
|
44
|
+
const PARAM_SEPARATOR = "_NEXTSEP_";
|
|
45
|
+
function safeCompile(route) {
|
|
46
|
+
const needsNormalization = /\/\(\.{1,3}\):[^/\s]+/.test(route) || ADJACENT_PARAMS_RE.test(route);
|
|
47
|
+
const routeToUse = needsNormalization ? normalizeAdjacentParameters(route) : route;
|
|
48
|
+
try {
|
|
49
|
+
const compiler = compile(routeToUse);
|
|
50
|
+
return needsNormalization ? (params) => stripNormalizedSeparators(compiler(params)) : compiler;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (needsNormalization) throw error;
|
|
53
|
+
try {
|
|
54
|
+
const compiler = compile(normalizeAdjacentParameters(route));
|
|
55
|
+
return (params) => stripNormalizedSeparators(compiler(params));
|
|
56
|
+
} catch {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const ADJACENT_PARAMS_RE = /:[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z_][a-zA-Z0-9_]*/;
|
|
62
|
+
function normalizeAdjacentParameters(route) {
|
|
63
|
+
return route.replace(/(\([^)]*\)):([^/\s]+)/g, `$1${PARAM_SEPARATOR}:$2`).replace(/:([^:/\s)]+)(?=:)/g, `:$1${PARAM_SEPARATOR}`);
|
|
64
|
+
}
|
|
65
|
+
function stripNormalizedSeparators(pathname) {
|
|
66
|
+
return pathname.replace(new RegExp(`\\)${PARAM_SEPARATOR}`, "g"), ")");
|
|
67
|
+
}
|
|
68
|
+
function compile(route) {
|
|
69
|
+
const tokens = parsePathPattern(route);
|
|
70
|
+
return (params) => {
|
|
71
|
+
let path = "";
|
|
72
|
+
for (const token of tokens) {
|
|
73
|
+
if (typeof token === "string") {
|
|
74
|
+
path += token;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const value = params[token.name];
|
|
78
|
+
const optional = token.modifier === "?" || token.modifier === "*";
|
|
79
|
+
const repeat = token.modifier === "*" || token.modifier === "+";
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
if (!repeat) throw new TypeError(`Expected "${token.name}" to not repeat, but got an array`);
|
|
82
|
+
if (value.length === 0) {
|
|
83
|
+
if (optional) continue;
|
|
84
|
+
throw new TypeError(`Expected "${token.name}" to not be empty`);
|
|
85
|
+
}
|
|
86
|
+
for (const segment of value) path += token.prefix + segment + token.suffix;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === "string") {
|
|
90
|
+
path += token.prefix + value + token.suffix;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (optional) continue;
|
|
94
|
+
throw new TypeError(`Expected "${token.name}" to be ${repeat ? "an array" : "a string"}`);
|
|
95
|
+
}
|
|
96
|
+
return path;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const HTTP_TOKEN_RE = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
|
|
100
|
+
const INVALID_HEADER_VALUE_CHAR_RE = /[^\t\x20-\x7e\x80-\xff]/;
|
|
101
|
+
/** Whether a substituted header pair can be sent as a response header. */
|
|
102
|
+
function isValidHeaderPair(key, value) {
|
|
103
|
+
return HTTP_TOKEN_RE.test(key) && !INVALID_HEADER_VALUE_CHAR_RE.test(value);
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { compileHeaderParamTemplate, isValidHeaderPair };
|
|
@@ -609,11 +609,13 @@ export declare function normalizeAssetPrefix(value: unknown): string;
|
|
|
609
609
|
/**
|
|
610
610
|
* Resolve the App Router RSC compatibility identity for a build.
|
|
611
611
|
*
|
|
612
|
-
*
|
|
613
|
-
* `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
|
|
614
|
-
* RSC payloads whose token differs (deploy skew)
|
|
615
|
-
*
|
|
616
|
-
*
|
|
612
|
+
* The server sends this token with each page (the navigation bootstrap) and in
|
|
613
|
+
* the `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
|
|
614
|
+
* rejects RSC payloads whose token differs from its page's (deploy skew)
|
|
615
|
+
* without exposing the raw build ID. It is never inlined into the client
|
|
616
|
+
* bundle, where a per-build value would rename chunks on every build. When
|
|
617
|
+
* the user pins a `deploymentId` we reuse it (already stable across plugin
|
|
618
|
+
* instances); otherwise we mint a random UUID.
|
|
617
619
|
*
|
|
618
620
|
* NOTE: like `resolveBuildId`, this is non-deterministic in the no-deploymentId
|
|
619
621
|
* case, so a single `vite build` that instantiates the plugin more than once
|
|
@@ -5,6 +5,7 @@ import { isUnknownRecord } from "../utils/record.js";
|
|
|
5
5
|
import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
|
|
6
6
|
import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
|
|
7
7
|
import { flattenPluginOptions } from "../utils/plugin-options.js";
|
|
8
|
+
import { configSourceError } from "./config-source.js";
|
|
8
9
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
9
10
|
import { applyLocaleToRoutes } from "./config-matchers.js";
|
|
10
11
|
import { loadTsconfigResolutionForRoot } from "./tsconfig-paths.js";
|
|
@@ -534,11 +535,13 @@ function resolveDeploymentId(configDeploymentId) {
|
|
|
534
535
|
/**
|
|
535
536
|
* Resolve the App Router RSC compatibility identity for a build.
|
|
536
537
|
*
|
|
537
|
-
*
|
|
538
|
-
* `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
|
|
539
|
-
* RSC payloads whose token differs (deploy skew)
|
|
540
|
-
*
|
|
541
|
-
*
|
|
538
|
+
* The server sends this token with each page (the navigation bootstrap) and in
|
|
539
|
+
* the `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
|
|
540
|
+
* rejects RSC payloads whose token differs from its page's (deploy skew)
|
|
541
|
+
* without exposing the raw build ID. It is never inlined into the client
|
|
542
|
+
* bundle, where a per-build value would rename chunks on every build. When
|
|
543
|
+
* the user pins a `deploymentId` we reuse it (already stable across plugin
|
|
544
|
+
* instances); otherwise we mint a random UUID.
|
|
542
545
|
*
|
|
543
546
|
* NOTE: like `resolveBuildId`, this is non-deterministic in the no-deploymentId
|
|
544
547
|
* case, so a single `vite build` that instantiates the plugin more than once
|
|
@@ -750,6 +753,93 @@ function normalizeI18nConfig(value) {
|
|
|
750
753
|
};
|
|
751
754
|
}
|
|
752
755
|
/**
|
|
756
|
+
* Fail config loading on an invalid source, as Next.js fails the build
|
|
757
|
+
* (load-custom-routes.ts checkCustomRoutes), instead of silently skipping a
|
|
758
|
+
* redirect or security header per request.
|
|
759
|
+
*/
|
|
760
|
+
function validateConfigSources(type, routes) {
|
|
761
|
+
const invalid = [];
|
|
762
|
+
for (const route of routes) {
|
|
763
|
+
const error = configSourceError(route?.source);
|
|
764
|
+
if (!error) continue;
|
|
765
|
+
invalid.push(` ${describeSource(route?.source)}: ${error}`);
|
|
766
|
+
}
|
|
767
|
+
if (invalid.length > 0) throw new Error(`Invalid next.config.js ${type} source${invalid.length === 1 ? "" : "s"}:\n${invalid.join("\n")}`);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* A route source for an error message. Only a string is shown as is; any
|
|
771
|
+
* other value, such as a BigInt, may not serialize.
|
|
772
|
+
*/
|
|
773
|
+
function describeSource(source) {
|
|
774
|
+
return typeof source === "string" ? JSON.stringify(source) : `(${source === null ? "null" : typeof source})`;
|
|
775
|
+
}
|
|
776
|
+
const CONDITION_TYPES = /* @__PURE__ */ new Set([
|
|
777
|
+
"header",
|
|
778
|
+
"cookie",
|
|
779
|
+
"query",
|
|
780
|
+
"host"
|
|
781
|
+
]);
|
|
782
|
+
/**
|
|
783
|
+
* Fail config loading on a malformed has/missing list or item, with the rules
|
|
784
|
+
* of Next.js's checkCustomRoutes (load-custom-routes.ts). The matchers rely on
|
|
785
|
+
* these shapes.
|
|
786
|
+
*/
|
|
787
|
+
function validateConfigConditions(type, routes) {
|
|
788
|
+
const invalid = [];
|
|
789
|
+
for (const route of routes) {
|
|
790
|
+
const source = describeSource(route?.source);
|
|
791
|
+
for (const field of ["has", "missing"]) {
|
|
792
|
+
const items = route?.[field];
|
|
793
|
+
if (items === void 0) continue;
|
|
794
|
+
if (!Array.isArray(items)) {
|
|
795
|
+
invalid.push(` ${source}: \`${field}\` must be undefined or an array`);
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
for (const item of items) {
|
|
799
|
+
const condition = item ?? {};
|
|
800
|
+
const parts = [];
|
|
801
|
+
if (!CONDITION_TYPES.has(condition.type)) parts.push("invalid type");
|
|
802
|
+
if (typeof condition.key !== "string" && condition.type !== "host") parts.push("invalid key");
|
|
803
|
+
if (condition.value !== void 0 && typeof condition.value !== "string") parts.push("invalid value");
|
|
804
|
+
if (condition.value === void 0 && condition.type === "host") parts.push("value is required for \"host\" type");
|
|
805
|
+
if (parts.length > 0) invalid.push(` ${source}: \`${field}\` item: ${parts.join(", ")} for ${describeConfigValue(item)}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (invalid.length > 0) throw new Error(`Invalid next.config.js ${type} has/missing condition${invalid.length === 1 ? "" : "s"}:\n${invalid.join("\n")}\nEach item must be { type: header, cookie, query or host, key: string, value?: string }, with a value and no key required for host.`);
|
|
810
|
+
}
|
|
811
|
+
/** JSON for a config value, or its type when JSON cannot serialize it. */
|
|
812
|
+
function describeConfigValue(value) {
|
|
813
|
+
try {
|
|
814
|
+
return JSON.stringify(value) ?? `(${typeof value})`;
|
|
815
|
+
} catch {
|
|
816
|
+
return `(${typeof value})`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* The sources Next.js's processRoutes builds from `routes` by adding the
|
|
821
|
+
* basePath and locale prefixes, which checkCustomRoutes then checks again
|
|
822
|
+
* (load-custom-routes.ts). vinext adds basePath at match time instead, so
|
|
823
|
+
* these exist only for that check.
|
|
824
|
+
*/
|
|
825
|
+
function processedRouteSources(routes, type, options) {
|
|
826
|
+
const { basePath, i18n, trailingSlash } = options;
|
|
827
|
+
const escape = (value) => value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
|
|
828
|
+
const suffixFor = (source) => source === "/" && !trailingSlash ? "" : source;
|
|
829
|
+
const defaultLocales = i18n && type === "redirect" ? [...(i18n.domains ?? []).map((domain) => domain.defaultLocale), i18n.defaultLocale] : [];
|
|
830
|
+
const sources = [];
|
|
831
|
+
for (const route of routes) {
|
|
832
|
+
const sourceBasePath = basePath && route.basePath !== false ? basePath : "";
|
|
833
|
+
let source = route.source;
|
|
834
|
+
if (i18n && route.locale !== false) {
|
|
835
|
+
if ("destination" in route && route.destination?.startsWith("/")) for (const locale of defaultLocales) sources.push({ source: `${sourceBasePath}/${locale}${suffixFor(source)}` });
|
|
836
|
+
source = `/:nextInternalLocale(${i18n.locales.map(escape).join("|")})${suffixFor(source)}`;
|
|
837
|
+
}
|
|
838
|
+
sources.push({ source: `${sourceBasePath}${source === "/" && sourceBasePath ? "" : source}` });
|
|
839
|
+
}
|
|
840
|
+
return sources;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
753
843
|
* Resolve a NextConfig into a fully-resolved ResolvedNextConfig.
|
|
754
844
|
* Awaits async functions for redirects/rewrites/headers.
|
|
755
845
|
*/
|
|
@@ -852,11 +942,18 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
852
942
|
};
|
|
853
943
|
}
|
|
854
944
|
{
|
|
855
|
-
const
|
|
945
|
+
const allRewrites = [
|
|
856
946
|
...rewrites.beforeFiles,
|
|
857
947
|
...rewrites.afterFiles,
|
|
858
948
|
...rewrites.fallback
|
|
859
|
-
]
|
|
949
|
+
];
|
|
950
|
+
validateConfigSources("redirect", redirects);
|
|
951
|
+
validateConfigSources("rewrite", allRewrites);
|
|
952
|
+
validateConfigConditions("redirect", redirects);
|
|
953
|
+
validateConfigConditions("rewrite", allRewrites);
|
|
954
|
+
const invalidBasePathRewrites = allRewrites.filter((rewrite) => rewrite.basePath === false && !(rewrite.destination.startsWith("http://") || rewrite.destination.startsWith("https://")));
|
|
955
|
+
if (invalidBasePathRewrites.length > 0) throw new Error(invalidBasePathRewrites.map((rewrite) => `The route ${rewrite.source} rewrites urls outside of the basePath. Please use a destination that starts with \`http://\` or \`https://\` https://nextjs.org/docs/messages/invalid-external-rewrite`).concat(`Invalid rewrite${invalidBasePathRewrites.length === 1 ? "" : "s"} found`).join("\n"));
|
|
956
|
+
const externalRewrites = allRewrites.filter((rewrite) => isExternalUrl(rewrite.destination));
|
|
860
957
|
if (externalRewrites.length > 0) {
|
|
861
958
|
const noun = externalRewrites.length === 1 ? "external rewrite" : "external rewrites";
|
|
862
959
|
const listing = externalRewrites.map((rewrite) => ` ${rewrite.source} → ${rewrite.destination}`).join("\n");
|
|
@@ -865,6 +962,22 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
865
962
|
}
|
|
866
963
|
let headers = [];
|
|
867
964
|
if (config.headers) headers = await config.headers();
|
|
965
|
+
validateConfigSources("header", headers);
|
|
966
|
+
validateConfigConditions("header", headers);
|
|
967
|
+
if (config.basePath || i18n) {
|
|
968
|
+
const processOptions = {
|
|
969
|
+
basePath: config.basePath ?? "",
|
|
970
|
+
i18n,
|
|
971
|
+
trailingSlash: config.trailingSlash ?? false
|
|
972
|
+
};
|
|
973
|
+
validateConfigSources("redirect", processedRouteSources(redirects, "redirect", processOptions));
|
|
974
|
+
validateConfigSources("rewrite", processedRouteSources([
|
|
975
|
+
...rewrites.beforeFiles,
|
|
976
|
+
...rewrites.afterFiles,
|
|
977
|
+
...rewrites.fallback
|
|
978
|
+
], "rewrite", processOptions));
|
|
979
|
+
validateConfigSources("header", processedRouteSources(headers, "header", processOptions));
|
|
980
|
+
}
|
|
868
981
|
const webpackProbe = await probeWebpackConfig(config, root, options.dev ?? false);
|
|
869
982
|
const mdx = webpackProbe.mdx;
|
|
870
983
|
const aliases = {
|
|
@@ -9,13 +9,26 @@ import { VinextLinkPrefetchRoute, VinextPagesLinkPrefetchRoute } from "../client
|
|
|
9
9
|
* embedded RSC payload and handles client-side navigation by re-fetching
|
|
10
10
|
* RSC streams.
|
|
11
11
|
*/
|
|
12
|
-
export declare function generateBrowserEntry(routes?: readonly AppRoute[],
|
|
12
|
+
export declare function generateBrowserEntry(routes?: readonly AppRoute[], pagesPrefetchRoutes?: readonly VinextPagesLinkPrefetchRoute[], rewrites?: {
|
|
13
13
|
afterFiles: NextRewrite[];
|
|
14
14
|
beforeFiles: NextRewrite[];
|
|
15
15
|
fallback: NextRewrite[];
|
|
16
16
|
}, locales?: readonly string[]): string;
|
|
17
|
+
/**
|
|
18
|
+
* App route handlers for hybrid App/Pages ownership, with their static
|
|
19
|
+
* segments hashed so the client never receives their paths in plain text.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toHashedRouteHandlerRoutes(routes: readonly AppRoute[]): VinextLinkPrefetchRoute[];
|
|
17
22
|
/** Project an `AppRoute` down to the public `VinextLinkPrefetchRoute` shape. */
|
|
18
23
|
export declare function toLinkPrefetchRoute(route: AppRoute, hasSiblingInterceptLoading?: boolean): VinextLinkPrefetchRoute;
|
|
19
24
|
/** Project App routes together so sibling-intercept loading is applied to its target route. */
|
|
20
25
|
export declare function toLinkPrefetchRoutes(routes: readonly AppRoute[]): VinextLinkPrefetchRoute[];
|
|
26
|
+
/**
|
|
27
|
+
* Generate the virtual module that holds the browser RouteManifest.
|
|
28
|
+
*
|
|
29
|
+
* The App browser runtime imports it dynamically when hydration starts, so the
|
|
30
|
+
* manifest, which grows with every route, stays out of the entry chunk. The
|
|
31
|
+
* JSON string keeps the parse on V8's fast JSON path.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateBrowserRouteManifestModule(routeManifest: RouteManifest | null): string;
|
|
21
34
|
//#endregion
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { patternsStructurallyEquivalent } from "../routing/app-route-graph.js";
|
|
2
2
|
import { resolveClientRuntimeModule, resolveRuntimeEntryModule } from "./runtime-entry-module.js";
|
|
3
3
|
import { toClientRewrites } from "../client/client-rewrites.js";
|
|
4
|
+
import { hashRoutePatternParts } from "../routing/hashed-route-patterns.js";
|
|
5
|
+
import { toBrowserRouteManifestWire } from "../routing/browser-route-manifest.js";
|
|
4
6
|
//#region src/entries/app-browser-entry.ts
|
|
5
7
|
/**
|
|
6
8
|
* Generate the virtual browser entry module.
|
|
@@ -9,20 +11,20 @@ import { toClientRewrites } from "../client/client-rewrites.js";
|
|
|
9
11
|
* embedded RSC payload and handles client-side navigation by re-fetching
|
|
10
12
|
* RSC streams.
|
|
11
13
|
*/
|
|
12
|
-
function generateBrowserEntry(routes = [],
|
|
14
|
+
function generateBrowserEntry(routes = [], pagesPrefetchRoutes = [], rewrites = {
|
|
13
15
|
afterFiles: [],
|
|
14
16
|
beforeFiles: [],
|
|
15
17
|
fallback: []
|
|
16
18
|
}, locales = []) {
|
|
17
19
|
const entryPath = resolveRuntimeEntryModule("app-browser-entry");
|
|
18
20
|
const reactInstanceBootstrapPath = resolveClientRuntimeModule("react-instance-bootstrap");
|
|
19
|
-
const navigationRuntimePath = resolveClientRuntimeModule("navigation-runtime");
|
|
20
21
|
const prefetchRoutes = toLinkPrefetchRoutes(routes);
|
|
22
|
+
const routeHandlers = pagesPrefetchRoutes.length > 0 ? toHashedRouteHandlerRoutes(routes) : [];
|
|
21
23
|
const clientRewrites = toClientRewrites(rewrites);
|
|
22
24
|
return `import ${JSON.stringify(reactInstanceBootstrapPath)};
|
|
23
|
-
import { registerNavigationRuntimeBootstrap } from ${JSON.stringify(navigationRuntimePath)};
|
|
24
25
|
|
|
25
26
|
window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(prefetchRoutes)};
|
|
27
|
+
window.__VINEXT_APP_ROUTE_HANDLERS__ = ${JSON.stringify(routeHandlers)};
|
|
26
28
|
// Pages route manifest for hybrid ownership decisions. In a hybrid
|
|
27
29
|
// app+pages build the user can land on an App page, so the App browser
|
|
28
30
|
// entry must also expose the Pages manifest (the Pages client entry does
|
|
@@ -30,9 +32,6 @@ window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(prefetchRoutes)};
|
|
|
30
32
|
window.__VINEXT_PAGES_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(pagesPrefetchRoutes)};
|
|
31
33
|
window.__VINEXT_CLIENT_REWRITES__ = ${JSON.stringify(clientRewrites)};
|
|
32
34
|
window.__VINEXT_LOCALES__ = ${JSON.stringify(locales)};
|
|
33
|
-
registerNavigationRuntimeBootstrap({
|
|
34
|
-
routeManifest: ${buildRouteManifestExpression(routeManifest)}
|
|
35
|
-
});
|
|
36
35
|
import ${JSON.stringify(entryPath)};`;
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
@@ -45,13 +44,17 @@ function isLinkPrefetchRoute(route) {
|
|
|
45
44
|
if (route.pagePath !== null) return true;
|
|
46
45
|
return route.routePath === null && route.layouts.length > 0;
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
/**
|
|
48
|
+
* App route handlers for hybrid App/Pages ownership, with their static
|
|
49
|
+
* segments hashed so the client never receives their paths in plain text.
|
|
50
|
+
*/
|
|
51
|
+
function toHashedRouteHandlerRoutes(routes) {
|
|
52
|
+
return routes.flatMap((route) => isLinkPrefetchRoute(route) ? [] : [{
|
|
50
53
|
canPrefetchLoadingShell: false,
|
|
51
54
|
documentOnly: true,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
+
isDynamic: route.isDynamic,
|
|
56
|
+
patternParts: hashRoutePatternParts(route.patternParts)
|
|
57
|
+
}]);
|
|
55
58
|
}
|
|
56
59
|
function requiresDynamicNavigationRequest(route) {
|
|
57
60
|
return route.isDynamic && route.parallelSlots.length > 0;
|
|
@@ -79,31 +82,18 @@ function toLinkPrefetchRoute(route, hasSiblingInterceptLoading = route.siblingIn
|
|
|
79
82
|
function toLinkPrefetchRoutes(routes) {
|
|
80
83
|
const siblingInterceptLoadingTargets = [];
|
|
81
84
|
for (const route of routes) for (const intercept of route.siblingIntercepts) if ((intercept.loadingPaths?.length ?? 0) > 0) siblingInterceptLoadingTargets.push(splitPatternParts(intercept.targetPattern));
|
|
82
|
-
return routes.map((route) =>
|
|
83
|
-
}
|
|
84
|
-
function buildRouteManifestExpression(routeManifest) {
|
|
85
|
-
if (routeManifest === null) return "null";
|
|
86
|
-
const graph = routeManifest.segmentGraph;
|
|
87
|
-
return `{
|
|
88
|
-
graphVersion: ${JSON.stringify(routeManifest.graphVersion)},
|
|
89
|
-
segmentGraph: {
|
|
90
|
-
routes: ${buildMapExpression(graph.routes)},
|
|
91
|
-
pages: ${buildMapExpression(graph.pages)},
|
|
92
|
-
routeHandlers: ${buildMapExpression(graph.routeHandlers)},
|
|
93
|
-
layouts: ${buildMapExpression(graph.layouts)},
|
|
94
|
-
templates: ${buildMapExpression(graph.templates)},
|
|
95
|
-
slots: ${buildMapExpression(graph.slots)},
|
|
96
|
-
defaults: ${buildMapExpression(graph.defaults)},
|
|
97
|
-
slotBindings: ${buildMapExpression(graph.slotBindings)},
|
|
98
|
-
interceptions: ${buildMapExpression(graph.interceptions)},
|
|
99
|
-
interceptionsBySlotId: ${buildMapExpression(graph.interceptionsBySlotId)},
|
|
100
|
-
boundaries: ${buildMapExpression(graph.boundaries)},
|
|
101
|
-
rootBoundaries: ${buildMapExpression(graph.rootBoundaries)}
|
|
102
|
-
}
|
|
103
|
-
}`;
|
|
85
|
+
return routes.filter(isLinkPrefetchRoute).map((route) => toLinkPrefetchRoute(route, siblingInterceptLoadingTargets.some((targetParts) => patternsStructurallyEquivalent(targetParts, route.patternParts))));
|
|
104
86
|
}
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Generate the virtual module that holds the browser RouteManifest.
|
|
89
|
+
*
|
|
90
|
+
* The App browser runtime imports it dynamically when hydration starts, so the
|
|
91
|
+
* manifest, which grows with every route, stays out of the entry chunk. The
|
|
92
|
+
* JSON string keeps the parse on V8's fast JSON path.
|
|
93
|
+
*/
|
|
94
|
+
function generateBrowserRouteManifestModule(routeManifest) {
|
|
95
|
+
if (routeManifest === null) return "export default null;";
|
|
96
|
+
return `export default JSON.parse(${JSON.stringify(JSON.stringify(toBrowserRouteManifestWire(routeManifest)))});`;
|
|
107
97
|
}
|
|
108
98
|
//#endregion
|
|
109
|
-
export { generateBrowserEntry, toLinkPrefetchRoute, toLinkPrefetchRoutes };
|
|
99
|
+
export { generateBrowserEntry, generateBrowserRouteManifestModule, toHashedRouteHandlerRoutes, toLinkPrefetchRoute, toLinkPrefetchRoutes };
|
|
@@ -93,6 +93,8 @@ type AppRouterConfig = {
|
|
|
93
93
|
draftModeSecret?: string;
|
|
94
94
|
/** Server-only token used to authorize build-time prerender endpoints. */
|
|
95
95
|
prerenderSecret?: string;
|
|
96
|
+
/** next.config `skipProxyUrlNormalize`. */
|
|
97
|
+
skipProxyUrlNormalize?: boolean;
|
|
96
98
|
/** next.config `skipTrailingSlashRedirect`. */
|
|
97
99
|
skipTrailingSlashRedirect?: boolean;
|
|
98
100
|
/** Dev-only key authenticating the hybrid Pages handler's forwarded middleware context. */
|