vinext 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/client-build-config.d.ts +11 -2
- package/dist/build/client-build-config.js +17 -6
- package/dist/build/prerender.d.ts +9 -1
- package/dist/build/prerender.js +42 -12
- package/dist/build/run-prerender.d.ts +10 -2
- package/dist/build/run-prerender.js +15 -1
- package/dist/client/app-nav-failure-handler.d.ts +8 -0
- package/dist/client/app-nav-failure-handler.js +44 -0
- package/dist/client/pages-router-link-navigation.d.ts +33 -7
- package/dist/client/pages-router-link-navigation.js +32 -2
- package/dist/client/vinext-next-data.d.ts +18 -1
- package/dist/client/vinext-next-data.js +2 -0
- package/dist/client/window-next.d.ts +2 -1
- package/dist/client/window-next.js +12 -1
- package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
- package/dist/config/config-matchers.d.ts +11 -1
- package/dist/config/config-matchers.js +87 -16
- package/dist/config/next-config.d.ts +46 -4
- package/dist/config/next-config.js +147 -48
- package/dist/config/tsconfig-paths.js +14 -1
- package/dist/deploy.d.ts +30 -11
- package/dist/deploy.js +200 -112
- package/dist/entries/app-browser-entry.d.ts +9 -3
- package/dist/entries/app-browser-entry.js +21 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +65 -5
- package/dist/entries/app-rsc-manifest.js +2 -0
- package/dist/entries/app-ssr-entry.js +1 -1
- package/dist/entries/pages-client-entry.js +66 -20
- package/dist/entries/pages-server-entry.js +47 -31
- package/dist/index.js +417 -102
- package/dist/plugins/dynamic-preload-metadata.js +2 -4
- package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
- package/dist/plugins/extensionless-dynamic-import.js +152 -0
- package/dist/plugins/fonts.js +5 -4
- package/dist/plugins/optimize-imports.d.ts +2 -1
- package/dist/plugins/optimize-imports.js +11 -9
- package/dist/plugins/postcss.js +7 -7
- package/dist/plugins/strip-server-exports.d.ts +9 -7
- package/dist/plugins/strip-server-exports.js +493 -46
- package/dist/plugins/typeof-window.d.ts +14 -0
- package/dist/plugins/typeof-window.js +150 -0
- package/dist/routing/app-route-graph.d.ts +2 -1
- package/dist/routing/app-route-graph.js +46 -16
- package/dist/routing/file-matcher.d.ts +10 -1
- package/dist/routing/file-matcher.js +22 -1
- package/dist/routing/pages-router.js +3 -3
- package/dist/routing/utils.d.ts +35 -6
- package/dist/routing/utils.js +59 -7
- package/dist/server/api-handler.d.ts +6 -1
- package/dist/server/api-handler.js +21 -15
- package/dist/server/app-browser-action-result.d.ts +19 -6
- package/dist/server/app-browser-action-result.js +20 -11
- package/dist/server/app-browser-entry.js +175 -91
- package/dist/server/app-browser-error.d.ts +10 -6
- package/dist/server/app-browser-error.js +43 -8
- package/dist/server/app-browser-hydration.d.ts +2 -0
- package/dist/server/app-browser-hydration.js +1 -0
- package/dist/server/app-browser-navigation-controller.d.ts +5 -3
- package/dist/server/app-browser-navigation-controller.js +23 -2
- package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
- package/dist/server/app-browser-server-action-navigation.js +9 -0
- package/dist/server/app-browser-state.d.ts +1 -1
- package/dist/server/app-browser-state.js +19 -11
- package/dist/server/app-browser-stream.js +86 -43
- package/dist/server/app-browser-visible-commit.d.ts +1 -1
- package/dist/server/app-elements-wire.d.ts +6 -1
- package/dist/server/app-elements-wire.js +14 -4
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +2 -2
- package/dist/server/app-fallback-renderer.d.ts +1 -0
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-optimistic-routing.js +2 -2
- package/dist/server/app-page-boundary-render.d.ts +1 -0
- package/dist/server/app-page-boundary-render.js +27 -14
- package/dist/server/app-page-cache-render.d.ts +53 -0
- package/dist/server/app-page-cache-render.js +91 -0
- package/dist/server/app-page-cache.d.ts +16 -2
- package/dist/server/app-page-cache.js +62 -1
- package/dist/server/app-page-dispatch.d.ts +26 -0
- package/dist/server/app-page-dispatch.js +149 -92
- package/dist/server/app-page-element-builder.d.ts +1 -0
- package/dist/server/app-page-element-builder.js +5 -2
- package/dist/server/app-page-execution.d.ts +6 -1
- package/dist/server/app-page-execution.js +21 -1
- package/dist/server/app-page-probe.d.ts +1 -0
- package/dist/server/app-page-probe.js +4 -0
- package/dist/server/app-page-render-observation.d.ts +3 -1
- package/dist/server/app-page-render-observation.js +17 -1
- package/dist/server/app-page-render.d.ts +12 -1
- package/dist/server/app-page-render.js +42 -4
- package/dist/server/app-page-request.d.ts +2 -0
- package/dist/server/app-page-request.js +2 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +14 -5
- package/dist/server/app-page-stream.d.ts +15 -3
- package/dist/server/app-page-stream.js +11 -5
- package/dist/server/app-pages-bridge.d.ts +23 -1
- package/dist/server/app-pages-bridge.js +26 -17
- package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
- package/dist/server/app-ppr-fallback-shell-render.js +26 -0
- package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
- package/dist/server/app-ppr-fallback-shell.js +8 -1
- package/dist/server/app-route-handler-dispatch.js +9 -2
- package/dist/server/app-route-handler-policy.d.ts +1 -0
- package/dist/server/app-router-entry.js +5 -0
- package/dist/server/app-rsc-cache-busting.js +2 -0
- package/dist/server/app-rsc-handler.d.ts +28 -0
- package/dist/server/app-rsc-handler.js +195 -59
- package/dist/server/app-rsc-route-matching.d.ts +3 -0
- package/dist/server/app-rsc-route-matching.js +8 -2
- package/dist/server/app-segment-config.d.ts +9 -1
- package/dist/server/app-segment-config.js +12 -3
- package/dist/server/app-server-action-execution.d.ts +1 -0
- package/dist/server/app-server-action-execution.js +47 -15
- package/dist/server/app-ssr-entry.d.ts +2 -0
- package/dist/server/app-ssr-entry.js +84 -39
- package/dist/server/before-interactive-head.d.ts +17 -0
- package/dist/server/before-interactive-head.js +35 -0
- package/dist/server/cache-control.js +4 -0
- package/dist/server/csp.js +1 -4
- package/dist/server/dev-server.d.ts +2 -2
- package/dist/server/dev-server.js +321 -83
- package/dist/server/hybrid-route-priority.d.ts +22 -0
- package/dist/server/hybrid-route-priority.js +33 -0
- package/dist/server/image-optimization.d.ts +18 -9
- package/dist/server/image-optimization.js +37 -23
- package/dist/server/implicit-tags.d.ts +2 -1
- package/dist/server/implicit-tags.js +4 -1
- package/dist/server/middleware-matcher.js +12 -3
- package/dist/server/middleware-runtime.d.ts +3 -4
- package/dist/server/middleware-runtime.js +2 -0
- package/dist/server/navigation-planner.d.ts +135 -41
- package/dist/server/navigation-planner.js +138 -0
- package/dist/server/navigation-trace.d.ts +9 -1
- package/dist/server/navigation-trace.js +9 -1
- package/dist/server/operation-token.d.ts +40 -0
- package/dist/server/operation-token.js +85 -0
- package/dist/server/pages-api-route.d.ts +6 -0
- package/dist/server/pages-api-route.js +13 -2
- package/dist/server/pages-asset-tags.d.ts +2 -1
- package/dist/server/pages-asset-tags.js +6 -2
- package/dist/server/pages-data-route.d.ts +9 -2
- package/dist/server/pages-data-route.js +18 -6
- package/dist/server/pages-dev-module-url.d.ts +4 -0
- package/dist/server/pages-dev-module-url.js +15 -0
- package/dist/server/pages-document-initial-props.d.ts +4 -15
- package/dist/server/pages-document-initial-props.js +27 -56
- package/dist/server/pages-get-initial-props.d.ts +54 -4
- package/dist/server/pages-get-initial-props.js +43 -1
- package/dist/server/pages-i18n.js +2 -2
- package/dist/server/pages-node-compat.js +2 -2
- package/dist/server/pages-page-data.d.ts +11 -2
- package/dist/server/pages-page-data.js +207 -34
- package/dist/server/pages-page-handler.d.ts +4 -2
- package/dist/server/pages-page-handler.js +62 -23
- package/dist/server/pages-page-response.d.ts +4 -1
- package/dist/server/pages-page-response.js +11 -8
- package/dist/server/pages-readiness.js +1 -1
- package/dist/server/pages-request-pipeline.d.ts +8 -7
- package/dist/server/pages-request-pipeline.js +126 -47
- package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
- package/dist/server/pregenerated-concrete-paths.js +2 -19
- package/dist/server/prerender-manifest.d.ts +33 -0
- package/dist/server/prerender-manifest.js +54 -0
- package/dist/server/prerender-route-params.d.ts +1 -2
- package/dist/server/prod-server.d.ts +3 -1
- package/dist/server/prod-server.js +50 -13
- package/dist/server/request-pipeline.d.ts +3 -15
- package/dist/server/request-pipeline.js +58 -47
- package/dist/server/rsc-stream-hints.d.ts +5 -1
- package/dist/server/rsc-stream-hints.js +6 -1
- package/dist/server/seed-cache.js +10 -18
- package/dist/server/static-file-cache.js +16 -4
- package/dist/shims/app-router-scroll-state.d.ts +3 -1
- package/dist/shims/app-router-scroll-state.js +14 -2
- package/dist/shims/app-router-scroll.d.ts +3 -0
- package/dist/shims/app-router-scroll.js +28 -18
- package/dist/shims/before-interactive-context.d.ts +14 -3
- package/dist/shims/cache-runtime.js +3 -2
- package/dist/shims/cache.d.ts +1 -0
- package/dist/shims/cache.js +1 -1
- package/dist/shims/cdn-cache.d.ts +5 -5
- package/dist/shims/document.d.ts +15 -20
- package/dist/shims/document.js +5 -8
- package/dist/shims/dynamic-preload-chunks.js +6 -4
- package/dist/shims/error-boundary.d.ts +2 -0
- package/dist/shims/error-boundary.js +7 -0
- package/dist/shims/error.js +3 -2
- package/dist/shims/error.react-server.d.ts +9 -0
- package/dist/shims/error.react-server.js +6 -0
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +45 -20
- package/dist/shims/hash-scroll.js +6 -1
- package/dist/shims/headers.js +29 -4
- package/dist/shims/image.js +9 -2
- package/dist/shims/internal/als-registry.js +28 -1
- package/dist/shims/internal/app-route-detection.js +8 -17
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
- package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
- package/dist/shims/internal/navigation-untracked.d.ts +35 -0
- package/dist/shims/internal/navigation-untracked.js +55 -0
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
- package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
- package/dist/shims/internal/pages-data-target.d.ts +7 -2
- package/dist/shims/internal/pages-data-target.js +17 -8
- package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
- package/dist/shims/internal/pages-router-accessor.js +13 -0
- package/dist/shims/internal/router-context.d.ts +2 -1
- package/dist/shims/internal/router-context.js +3 -1
- package/dist/shims/link.js +47 -19
- package/dist/shims/metadata.js +4 -4
- package/dist/shims/navigation.d.ts +8 -2
- package/dist/shims/navigation.js +63 -31
- package/dist/shims/ppr-fallback-shell.d.ts +5 -1
- package/dist/shims/ppr-fallback-shell.js +28 -7
- package/dist/shims/router.d.ts +18 -3
- package/dist/shims/router.js +512 -142
- package/dist/shims/script.js +8 -4
- package/dist/shims/server.d.ts +16 -1
- package/dist/shims/server.js +44 -12
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/built-asset-url.d.ts +4 -0
- package/dist/utils/built-asset-url.js +11 -0
- package/dist/utils/commonjs-loader.d.ts +16 -0
- package/dist/utils/commonjs-loader.js +100 -0
- package/dist/utils/deployment-id.d.ts +8 -0
- package/dist/utils/deployment-id.js +22 -0
- package/dist/utils/has-trailing-comma.d.ts +24 -0
- package/dist/utils/has-trailing-comma.js +62 -0
- package/dist/utils/html-limited-bots.d.ts +18 -1
- package/dist/utils/html-limited-bots.js +23 -1
- package/dist/utils/parse-cookie.d.ts +13 -0
- package/dist/utils/parse-cookie.js +52 -0
- package/dist/utils/path.d.ts +7 -1
- package/dist/utils/path.js +9 -1
- package/dist/utils/text-stream.d.ts +1 -1
- package/dist/utils/text-stream.js +2 -2
- package/dist/utils/vite-version.d.ts +12 -1
- package/dist/utils/vite-version.js +9 -1
- package/package.json +2 -2
- package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
- package/dist/shims/internal/parse-cookie-header.js +0 -30
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { collectBindingNames, forEachAstChild, hasRange, isAstRecord, isIdentifierNamed, nodeArray } from "./ast-utils.js";
|
|
2
|
+
import { parseAst } from "vite";
|
|
3
|
+
import MagicString from "magic-string";
|
|
4
|
+
//#region src/plugins/typeof-window.ts
|
|
5
|
+
function createScope(parent) {
|
|
6
|
+
return {
|
|
7
|
+
parent,
|
|
8
|
+
bindings: /* @__PURE__ */ new Set()
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function hasBinding(scope, name) {
|
|
12
|
+
for (let current = scope; current; current = current.parent) if (current.bindings.has(name)) return true;
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function collectScopeBindings(node, scope) {
|
|
16
|
+
forEachAstChild(node, (child) => {
|
|
17
|
+
if (child.type === "ExportNamedDeclaration" || child.type === "ExportDefaultDeclaration") {
|
|
18
|
+
if (isAstRecord(child.declaration)) collectScopeBindings(child, scope);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (child.type === "FunctionDeclaration" || child.type === "ClassDeclaration") {
|
|
22
|
+
collectBindingNames(child.id, scope.bindings);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (child.type === "VariableDeclaration") {
|
|
26
|
+
for (const declaration of nodeArray(child.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (child.type === "ImportDeclaration") {
|
|
30
|
+
for (const specifier of nodeArray(child.specifiers)) if (isAstRecord(specifier)) collectBindingNames(specifier.local, scope.bindings);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function collectVarBindings(node, scope, isRoot = true) {
|
|
35
|
+
if (!isRoot && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) return;
|
|
36
|
+
if (node.type === "VariableDeclaration" && node.kind === "var") {
|
|
37
|
+
for (const declaration of nodeArray(node.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
|
|
38
|
+
}
|
|
39
|
+
forEachAstChild(node, (child) => collectVarBindings(child, scope, false));
|
|
40
|
+
}
|
|
41
|
+
function isFunctionNode(node) {
|
|
42
|
+
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
43
|
+
}
|
|
44
|
+
function createChildScope(node, parent) {
|
|
45
|
+
if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "SwitchStatement" && node.type !== "CatchClause" && node.type !== "ForStatement" && node.type !== "ForInStatement" && node.type !== "ForOfStatement" && node.type !== "ClassDeclaration" && node.type !== "ClassExpression") return null;
|
|
46
|
+
const scope = createScope(parent);
|
|
47
|
+
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") collectBindingNames(node.id, scope.bindings);
|
|
48
|
+
else if (node.type === "CatchClause") collectBindingNames(node.param, scope.bindings);
|
|
49
|
+
collectScopeBindings(node, scope);
|
|
50
|
+
if (node.type === "SwitchStatement") {
|
|
51
|
+
for (const switchCase of nodeArray(node.cases)) if (isAstRecord(switchCase)) collectScopeBindings(switchCase, scope);
|
|
52
|
+
}
|
|
53
|
+
return scope;
|
|
54
|
+
}
|
|
55
|
+
function getTypeofWindowReplacement(environment) {
|
|
56
|
+
return environment.config.consumer === "client" ? "object" : "undefined";
|
|
57
|
+
}
|
|
58
|
+
function stringLiteralValue(node) {
|
|
59
|
+
if (!isAstRecord(node)) return null;
|
|
60
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function evaluateTypeofWindowComparison(node, replacement, scope) {
|
|
64
|
+
if (!isAstRecord(node) || node.type !== "BinaryExpression") return null;
|
|
65
|
+
if (![
|
|
66
|
+
"==",
|
|
67
|
+
"===",
|
|
68
|
+
"!=",
|
|
69
|
+
"!=="
|
|
70
|
+
].includes(String(node.operator))) return null;
|
|
71
|
+
const left = isAstRecord(node.left) ? node.left : null;
|
|
72
|
+
const right = isAstRecord(node.right) ? node.right : null;
|
|
73
|
+
const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !hasBinding(scope, "window");
|
|
74
|
+
const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !hasBinding(scope, "window");
|
|
75
|
+
const comparedValue = leftIsTypeofWindow ? stringLiteralValue(right) : rightIsTypeofWindow ? stringLiteralValue(left) : null;
|
|
76
|
+
if (comparedValue === null) return null;
|
|
77
|
+
const equal = replacement === comparedValue;
|
|
78
|
+
return node.operator === "==" || node.operator === "===" ? equal : !equal;
|
|
79
|
+
}
|
|
80
|
+
function replaceTypeofWindow(code, replacement) {
|
|
81
|
+
if (!/typeof\s+window/.test(code)) return null;
|
|
82
|
+
let ast;
|
|
83
|
+
try {
|
|
84
|
+
ast = parseAst(code);
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const output = new MagicString(code);
|
|
89
|
+
let changed = false;
|
|
90
|
+
if (!isAstRecord(ast)) return null;
|
|
91
|
+
const rootScope = createScope(null);
|
|
92
|
+
collectScopeBindings(ast, rootScope);
|
|
93
|
+
collectVarBindings(ast, rootScope);
|
|
94
|
+
function visit(node, parentScope) {
|
|
95
|
+
if (isFunctionNode(node)) {
|
|
96
|
+
const parameterScope = createScope(parentScope);
|
|
97
|
+
collectBindingNames(node.id, parameterScope.bindings);
|
|
98
|
+
for (const parameter of nodeArray(node.params)) {
|
|
99
|
+
collectBindingNames(parameter, parameterScope.bindings);
|
|
100
|
+
if (isAstRecord(parameter)) visit(parameter, parameterScope);
|
|
101
|
+
}
|
|
102
|
+
if (isAstRecord(node.body)) if (node.body.type === "BlockStatement") {
|
|
103
|
+
const bodyScope = createScope(parameterScope);
|
|
104
|
+
collectVarBindings(node.body, bodyScope);
|
|
105
|
+
visit(node.body, bodyScope);
|
|
106
|
+
} else visit(node.body, parameterScope);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const scope = createChildScope(node, parentScope) ?? parentScope;
|
|
110
|
+
if (node.type === "IfStatement" && hasRange(node)) {
|
|
111
|
+
const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
|
|
112
|
+
if (result !== null) {
|
|
113
|
+
const selected = result ? node.consequent : node.alternate;
|
|
114
|
+
if (isAstRecord(selected) && hasRange(selected)) {
|
|
115
|
+
output.remove(node.start, selected.start);
|
|
116
|
+
output.remove(selected.end, node.end);
|
|
117
|
+
visit(selected, scope);
|
|
118
|
+
} else output.overwrite(node.start, node.end, ";");
|
|
119
|
+
changed = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (node.type === "ConditionalExpression" && hasRange(node)) {
|
|
124
|
+
const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
|
|
125
|
+
const selected = result ? node.consequent : node.alternate;
|
|
126
|
+
if (result !== null && isAstRecord(selected) && hasRange(selected)) {
|
|
127
|
+
output.overwrite(node.start, selected.start, "(");
|
|
128
|
+
if (selected.end < node.end) output.overwrite(selected.end, node.end, ")");
|
|
129
|
+
else output.appendLeft(selected.end, ")");
|
|
130
|
+
visit(selected, scope);
|
|
131
|
+
changed = true;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !hasBinding(scope, "window") && hasRange(node)) {
|
|
136
|
+
output.overwrite(node.start, node.end, JSON.stringify(replacement));
|
|
137
|
+
changed = true;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
forEachAstChild(node, (child) => visit(child, scope));
|
|
141
|
+
}
|
|
142
|
+
for (const node of ast.body) if (isAstRecord(node)) visit(node, rootScope);
|
|
143
|
+
if (!changed) return null;
|
|
144
|
+
return {
|
|
145
|
+
code: output.toString(),
|
|
146
|
+
map: output.generateMap({ hires: "boundary" })
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { getTypeofWindowReplacement, replaceTypeofWindow };
|
|
@@ -21,7 +21,8 @@ type InterceptingRoute = {
|
|
|
21
21
|
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/interception-routes.ts
|
|
22
22
|
*/
|
|
23
23
|
sourceMatchPattern: string; /** Absolute path to the intercepting page component */
|
|
24
|
-
pagePath: string; /**
|
|
24
|
+
pagePath: string; /** Filesystem segments from app/ root to the intercepting page directory. */
|
|
25
|
+
sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
|
|
25
26
|
layoutPaths: string[]; /** Parameter names for dynamic segments */
|
|
26
27
|
params: string[];
|
|
27
28
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePathSeparators } from "../utils/path.js";
|
|
2
|
-
import {
|
|
2
|
+
import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
|
|
3
3
|
import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
|
|
4
4
|
import { validateRoutePatterns } from "./route-validation.js";
|
|
5
5
|
import { compareStrings } from "../utils/compare.js";
|
|
@@ -346,23 +346,25 @@ function createRouteManifestGraphVersion(segmentGraph) {
|
|
|
346
346
|
}
|
|
347
347
|
async function buildAppRouteGraph(appDir, matcher) {
|
|
348
348
|
const routes = [];
|
|
349
|
+
const scanMatcher = { ...matcher };
|
|
350
|
+
findFileProbeCache.set(scanMatcher, /* @__PURE__ */ new Map());
|
|
349
351
|
const excludeDir = (name) => name.startsWith("@") && name !== "@children" || name.startsWith("_") || isInterceptionMarkerDir(name);
|
|
350
|
-
for await (const file of scanWithExtensions("**/page", appDir,
|
|
351
|
-
const route = fileToAppRoute(file, appDir, "page",
|
|
352
|
+
for await (const file of scanWithExtensions("**/page", appDir, scanMatcher.extensions, excludeDir)) {
|
|
353
|
+
const route = fileToAppRoute(file, appDir, "page", scanMatcher);
|
|
352
354
|
if (route) routes.push(route);
|
|
353
355
|
}
|
|
354
|
-
for await (const file of scanWithExtensions("**/route", appDir,
|
|
355
|
-
const route = fileToAppRoute(file, appDir, "route",
|
|
356
|
+
for await (const file of scanWithExtensions("**/route", appDir, scanMatcher.extensions, excludeDir)) {
|
|
357
|
+
const route = fileToAppRoute(file, appDir, "route", scanMatcher);
|
|
356
358
|
if (route) routes.push(route);
|
|
357
359
|
}
|
|
358
360
|
const routePatterns = new Set(routes.map((route) => route.pattern));
|
|
359
361
|
const ghostParentRoutes = [];
|
|
360
|
-
for await (const file of scanWithExtensions("**/layout", appDir,
|
|
362
|
+
for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
|
|
361
363
|
const dir = path.dirname(file);
|
|
362
364
|
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
363
365
|
if (!hasParallelSlotDirectory(routeDir)) continue;
|
|
364
|
-
if (discoverParallelSlots(routeDir, appDir,
|
|
365
|
-
const route = directoryToAppRoute(dir, appDir,
|
|
366
|
+
if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
|
|
367
|
+
const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
|
|
366
368
|
if (!route) continue;
|
|
367
369
|
if (routePatterns.has(route.pattern)) {
|
|
368
370
|
ghostParentRoutes.push(route);
|
|
@@ -371,13 +373,13 @@ async function buildAppRouteGraph(appDir, matcher) {
|
|
|
371
373
|
routes.push(route);
|
|
372
374
|
routePatterns.add(route.pattern);
|
|
373
375
|
}
|
|
374
|
-
const slotSubRoutes = discoverSlotSubRoutes(routes,
|
|
376
|
+
const slotSubRoutes = discoverSlotSubRoutes(routes, scanMatcher, ghostParentRoutes);
|
|
375
377
|
routes.push(...slotSubRoutes);
|
|
376
|
-
discoverSiblingInterceptingRoutes(routes, appDir,
|
|
378
|
+
discoverSiblingInterceptingRoutes(routes, appDir, scanMatcher);
|
|
377
379
|
validatePageRouteConflicts(routes, appDir);
|
|
378
380
|
validateRoutePatterns(routes.map((route) => route.pattern));
|
|
379
381
|
validateRoutePatterns([...new Set(routes.flatMap((route) => [...route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern)), ...route.siblingIntercepts.map((intercept) => intercept.targetPattern)]))]);
|
|
380
|
-
routes
|
|
382
|
+
sortRoutes(routes);
|
|
381
383
|
return {
|
|
382
384
|
routes,
|
|
383
385
|
routeManifest: createRouteManifest(routes)
|
|
@@ -884,11 +886,11 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
884
886
|
const routeHasLayout = layoutIdx >= 0;
|
|
885
887
|
for (const { dir, layoutIdx: lvlLayoutIdx, segmentIndex } of dirsToCheck) {
|
|
886
888
|
if (lvlLayoutIdx < 0 && routeHasLayout) continue;
|
|
887
|
-
const isOwnDir = dir === routeDir;
|
|
888
889
|
const slotLayoutIdx = Math.max(lvlLayoutIdx, 0);
|
|
889
890
|
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
|
|
890
891
|
const segmentsBelow = segments.slice(segmentIndex);
|
|
891
|
-
|
|
892
|
+
const isActiveUrlLevel = dir === routeDir || segmentsBelow.every(isInvisibleSegment);
|
|
893
|
+
for (const slot of slotsAtLevel) if (isActiveUrlLevel) {
|
|
892
894
|
slot.layoutIndex = slotLayoutIdx;
|
|
893
895
|
slotMap.set(slot.key, slot);
|
|
894
896
|
} else {
|
|
@@ -1275,6 +1277,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
|
|
|
1275
1277
|
targetPattern: targetPattern.pattern,
|
|
1276
1278
|
sourceMatchPattern,
|
|
1277
1279
|
pagePath: page,
|
|
1280
|
+
sourcePageSegments: normalizePathSeparators(path.relative(appDir, path.dirname(page))).split("/").filter(Boolean),
|
|
1278
1281
|
params: targetPattern.params
|
|
1279
1282
|
});
|
|
1280
1283
|
}
|
|
@@ -1384,10 +1387,37 @@ function markerForInterceptionConvention(convention) {
|
|
|
1384
1387
|
}
|
|
1385
1388
|
}
|
|
1386
1389
|
/**
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1390
|
+
* Scan-scoped cache of convention-file probes, keyed by the per-scan matcher
|
|
1391
|
+
* created in `buildAppRouteGraph`. A single scan walks the appDir→leaf chain
|
|
1392
|
+
* separately for every route (layouts, templates, errors, boundaries, slots),
|
|
1393
|
+
* so shared ancestor directories — the `app/` root above all — get re-probed
|
|
1394
|
+
* once per descendant route. The probe result is deterministic within one scan
|
|
1395
|
+
* (the filesystem does not change mid-build), so memoizing it removes the
|
|
1396
|
+
* dominant cross-route redundancy.
|
|
1397
|
+
*
|
|
1398
|
+
* Keyed by matcher so the cache lifetime is exactly one `buildAppRouteGraph`
|
|
1399
|
+
* call: the scan registers a fresh matcher clone, and the entry is unreachable
|
|
1400
|
+
* (and GC-eligible) once the scan returns. A fresh key per scan is also what
|
|
1401
|
+
* makes this concurrency-safe — overlapping builds never share probe state.
|
|
1402
|
+
*/
|
|
1403
|
+
const findFileProbeCache = /* @__PURE__ */ new WeakMap();
|
|
1404
|
+
/**
|
|
1405
|
+
* Find a file by name (without extension) in a directory, checking configured
|
|
1406
|
+
* pageExtensions. Memoizes through `findFileProbeCache` when the matcher has a
|
|
1407
|
+
* registered per-scan cache; otherwise falls back to a direct probe (identical
|
|
1408
|
+
* result). The `null` "not found" outcome is cached too, so repeated misses on
|
|
1409
|
+
* shared ancestors cost a single set of `existsSync` calls per scan.
|
|
1389
1410
|
*/
|
|
1390
|
-
|
|
1411
|
+
function findFile(dir, name, matcher) {
|
|
1412
|
+
const cache = findFileProbeCache.get(matcher);
|
|
1413
|
+
if (!cache) return findFileWithExts(dir, name, matcher);
|
|
1414
|
+
const key = `${dir}\0${name}`;
|
|
1415
|
+
const cached = cache.get(key);
|
|
1416
|
+
if (cached !== void 0) return cached;
|
|
1417
|
+
const result = findFileWithExts(dir, name, matcher);
|
|
1418
|
+
cache.set(key, result);
|
|
1419
|
+
return result;
|
|
1420
|
+
}
|
|
1391
1421
|
/**
|
|
1392
1422
|
* Convert filesystem path segments to URL route parts, skipping invisible segments
|
|
1393
1423
|
* (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
|
|
@@ -43,9 +43,18 @@ declare function findFileWithExts(dir: string, name: string, matcher: ValidFileM
|
|
|
43
43
|
* See: cloudflare/vinext#1502
|
|
44
44
|
*/
|
|
45
45
|
declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Normalize an explicit Next.js resolver extension list for Vite.
|
|
48
|
+
*
|
|
49
|
+
* Unlike `pageExtensions`, both Turbopack's `resolveExtensions` and webpack's
|
|
50
|
+
* `resolve.extensions` replace their resolver defaults. The empty string is a
|
|
51
|
+
* webpack/Turbopack convention for trying the import exactly as written; Vite
|
|
52
|
+
* already does that before appending extensions, so it must be omitted here.
|
|
53
|
+
*/
|
|
54
|
+
declare function normalizeViteResolveExtensions(extensions: readonly string[]): string[];
|
|
46
55
|
/**
|
|
47
56
|
* Use function-form exclude for Node < 22.14 compatibility.
|
|
48
57
|
*/
|
|
49
58
|
declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
|
|
50
59
|
//#endregion
|
|
51
|
-
export { ValidFileMatcher, buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, scanWithExtensions };
|
|
60
|
+
export { ValidFileMatcher, buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
|
|
@@ -113,6 +113,27 @@ function buildViteResolveExtensions(pageExtensions, viteDefaults = [
|
|
|
113
113
|
return result;
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
+
* Normalize an explicit Next.js resolver extension list for Vite.
|
|
117
|
+
*
|
|
118
|
+
* Unlike `pageExtensions`, both Turbopack's `resolveExtensions` and webpack's
|
|
119
|
+
* `resolve.extensions` replace their resolver defaults. The empty string is a
|
|
120
|
+
* webpack/Turbopack convention for trying the import exactly as written; Vite
|
|
121
|
+
* already does that before appending extensions, so it must be omitted here.
|
|
122
|
+
*/
|
|
123
|
+
function normalizeViteResolveExtensions(extensions) {
|
|
124
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125
|
+
const result = [];
|
|
126
|
+
for (const extension of extensions) {
|
|
127
|
+
const trimmed = extension.trim();
|
|
128
|
+
if (!trimmed) continue;
|
|
129
|
+
const dotted = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
130
|
+
if (seen.has(dotted)) continue;
|
|
131
|
+
seen.add(dotted);
|
|
132
|
+
result.push(dotted);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
116
137
|
* Use function-form exclude for Node < 22.14 compatibility.
|
|
117
138
|
*/
|
|
118
139
|
async function* scanWithExtensions(stem, cwd, extensions, exclude) {
|
|
@@ -123,4 +144,4 @@ async function* scanWithExtensions(stem, cwd, extensions, exclude) {
|
|
|
123
144
|
})) yield file;
|
|
124
145
|
}
|
|
125
146
|
//#endregion
|
|
126
|
-
export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, scanWithExtensions };
|
|
147
|
+
export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decodeRouteSegment, sortRoutes } from "./utils.js";
|
|
2
2
|
import { createValidFileMatcher, scanWithExtensions } from "./file-matcher.js";
|
|
3
3
|
import { patternToNextFormat, validateRoutePatterns } from "./route-validation.js";
|
|
4
4
|
import { createRouteTrieCache, matchRouteWithTrie } from "./route-matching.js";
|
|
@@ -54,7 +54,7 @@ async function scanPageRoutes(pagesDir, matcher) {
|
|
|
54
54
|
if (route) routes.push(route);
|
|
55
55
|
}
|
|
56
56
|
validateRoutePatterns(routes.map((route) => route.pattern));
|
|
57
|
-
routes
|
|
57
|
+
sortRoutes(routes);
|
|
58
58
|
return routes;
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
@@ -156,7 +156,7 @@ async function scanApiRoutes(pagesDir, matcher) {
|
|
|
156
156
|
if (route) routes.push(route);
|
|
157
157
|
}
|
|
158
158
|
validateRoutePatterns(routes.map((route) => route.pattern));
|
|
159
|
-
routes
|
|
159
|
+
sortRoutes(routes);
|
|
160
160
|
return routes;
|
|
161
161
|
}
|
|
162
162
|
//#endregion
|
package/dist/routing/utils.d.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
//#region src/routing/utils.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Sort
|
|
4
|
-
*
|
|
3
|
+
* Sort routes by precedence — lower score sorts first (higher priority), with a
|
|
4
|
+
* lexicographic tiebreaker on the pattern for determinism. Sorts in place and
|
|
5
|
+
* returns the same array (mirrors `Array.prototype.sort`).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* `routePrecedence` is a pure function of the pattern, so each pattern's score
|
|
8
|
+
* is computed exactly once up front (decorate-sort) instead of ~2·log n times
|
|
9
|
+
* by a comparator that re-parses on every comparison. The `localeCompare`
|
|
10
|
+
* tiebreaker already guarantees a total order, so the result is byte-identical
|
|
11
|
+
* to comparing precedence inline.
|
|
12
|
+
*
|
|
13
|
+
* Usage: sortRoutes(routes)
|
|
7
14
|
*/
|
|
8
|
-
declare function
|
|
15
|
+
declare function sortRoutes<T extends {
|
|
9
16
|
pattern: string;
|
|
10
|
-
}>(
|
|
17
|
+
}>(routes: T[]): T[];
|
|
18
|
+
/**
|
|
19
|
+
* Single source of truth for hybrid App/Pages route ownership.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors Next.js's DefaultRouteMatcherManager ordering: Pages providers
|
|
22
|
+
* are registered before App providers, then merged dynamic matchers sort
|
|
23
|
+
* together. Returns the router that should own a request/navigation to
|
|
24
|
+
* a URL that matched BOTH routers.
|
|
25
|
+
*
|
|
26
|
+
* Centralised so the server's request handling and the client's link /
|
|
27
|
+
* prefetch / programmatic-navigation paths all reach the same owner for
|
|
28
|
+
* the same (pages pattern, app pattern) pair. This intentionally implements
|
|
29
|
+
* Next.js's segment-tree ordering directly instead of vinext's broader
|
|
30
|
+
* `sortRoutes()` score heuristic. It only arbitrates two routes that already
|
|
31
|
+
* matched the same URL; each router's own trie ordering remains unchanged.
|
|
32
|
+
*
|
|
33
|
+
* Usage:
|
|
34
|
+
* compareHybridRoutePatterns("/:slug", true, "/:slug", true) // → "pages"
|
|
35
|
+
* compareHybridRoutePatterns("/_sites/:slug*", true, "/:slug*", true) // → "pages"
|
|
36
|
+
* compareHybridRoutePatterns("/:path+", true, "/dashboard", false) // → "app"
|
|
37
|
+
* compareHybridRoutePatterns("/", false, "/", false) // → "app"
|
|
38
|
+
*/
|
|
39
|
+
declare function compareHybridRoutePatterns(pagesPattern: string, pagesIsDynamic: boolean, appPattern: string, appIsDynamic: boolean): "app" | "pages";
|
|
11
40
|
/**
|
|
12
41
|
* Decode a filesystem or URL path segment while preserving encoded path delimiters.
|
|
13
42
|
* Mirrors Next.js segment-wise decoding so "%5F" becomes "_" but "%2F" stays "%2F".
|
|
@@ -85,4 +114,4 @@ declare function isOptionalCatchAllSegment(segment: string): boolean;
|
|
|
85
114
|
*/
|
|
86
115
|
declare function countConsumedPathnameSegments(visibleTreePathSegments: readonly string[], pathnameSegmentCount: number): number;
|
|
87
116
|
//#endregion
|
|
88
|
-
export { buildParams,
|
|
117
|
+
export { buildParams, compareHybridRoutePatterns, countConsumedPathnameSegments, decodeMatchedParams, decodeRouteSegment, isCatchAllSegment, isInvisibleSegment, isOptionalCatchAllSegment, normalizePathnameForRouteMatch, normalizePathnameForRouteMatchStrict, sortRoutes, splitPathSegments, splitPathnameForRouteMatch };
|
package/dist/routing/utils.js
CHANGED
|
@@ -44,14 +44,66 @@ function routePrecedence(pattern) {
|
|
|
44
44
|
return score;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Sort
|
|
48
|
-
*
|
|
47
|
+
* Sort routes by precedence — lower score sorts first (higher priority), with a
|
|
48
|
+
* lexicographic tiebreaker on the pattern for determinism. Sorts in place and
|
|
49
|
+
* returns the same array (mirrors `Array.prototype.sort`).
|
|
49
50
|
*
|
|
50
|
-
*
|
|
51
|
+
* `routePrecedence` is a pure function of the pattern, so each pattern's score
|
|
52
|
+
* is computed exactly once up front (decorate-sort) instead of ~2·log n times
|
|
53
|
+
* by a comparator that re-parses on every comparison. The `localeCompare`
|
|
54
|
+
* tiebreaker already guarantees a total order, so the result is byte-identical
|
|
55
|
+
* to comparing precedence inline.
|
|
56
|
+
*
|
|
57
|
+
* Usage: sortRoutes(routes)
|
|
58
|
+
*/
|
|
59
|
+
function sortRoutes(routes) {
|
|
60
|
+
const scores = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const route of routes) if (!scores.has(route.pattern)) scores.set(route.pattern, routePrecedence(route.pattern));
|
|
62
|
+
return routes.sort((a, b) => {
|
|
63
|
+
const diff = (scores.get(a.pattern) ?? 0) - (scores.get(b.pattern) ?? 0);
|
|
64
|
+
return diff !== 0 ? diff : a.pattern.localeCompare(b.pattern);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Single source of truth for hybrid App/Pages route ownership.
|
|
69
|
+
*
|
|
70
|
+
* Mirrors Next.js's DefaultRouteMatcherManager ordering: Pages providers
|
|
71
|
+
* are registered before App providers, then merged dynamic matchers sort
|
|
72
|
+
* together. Returns the router that should own a request/navigation to
|
|
73
|
+
* a URL that matched BOTH routers.
|
|
74
|
+
*
|
|
75
|
+
* Centralised so the server's request handling and the client's link /
|
|
76
|
+
* prefetch / programmatic-navigation paths all reach the same owner for
|
|
77
|
+
* the same (pages pattern, app pattern) pair. This intentionally implements
|
|
78
|
+
* Next.js's segment-tree ordering directly instead of vinext's broader
|
|
79
|
+
* `sortRoutes()` score heuristic. It only arbitrates two routes that already
|
|
80
|
+
* matched the same URL; each router's own trie ordering remains unchanged.
|
|
81
|
+
*
|
|
82
|
+
* Usage:
|
|
83
|
+
* compareHybridRoutePatterns("/:slug", true, "/:slug", true) // → "pages"
|
|
84
|
+
* compareHybridRoutePatterns("/_sites/:slug*", true, "/:slug*", true) // → "pages"
|
|
85
|
+
* compareHybridRoutePatterns("/:path+", true, "/dashboard", false) // → "app"
|
|
86
|
+
* compareHybridRoutePatterns("/", false, "/", false) // → "app"
|
|
51
87
|
*/
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
function compareHybridRoutePatterns(pagesPattern, pagesIsDynamic, appPattern, appIsDynamic) {
|
|
89
|
+
if (pagesPattern === appPattern) throw new Error(`Conflicting app and page routes found for "${pagesPattern}"`);
|
|
90
|
+
if (!pagesIsDynamic) return appIsDynamic ? "pages" : "app";
|
|
91
|
+
if (!appIsDynamic) return "app";
|
|
92
|
+
const pagesSegments = pagesPattern.split("/").filter(Boolean);
|
|
93
|
+
const appSegments = appPattern.split("/").filter(Boolean);
|
|
94
|
+
const segmentRank = (segment) => {
|
|
95
|
+
if (!segment.startsWith(":")) return 0;
|
|
96
|
+
if (segment.endsWith("*")) return 3;
|
|
97
|
+
if (segment.endsWith("+")) return 2;
|
|
98
|
+
return 1;
|
|
99
|
+
};
|
|
100
|
+
for (let index = 0; index < Math.min(pagesSegments.length, appSegments.length); index++) {
|
|
101
|
+
const pagesRank = segmentRank(pagesSegments[index]);
|
|
102
|
+
const appRank = segmentRank(appSegments[index]);
|
|
103
|
+
if (pagesRank !== appRank) return pagesRank < appRank ? "pages" : "app";
|
|
104
|
+
}
|
|
105
|
+
if (pagesSegments.length !== appSegments.length) return pagesSegments.length < appSegments.length ? "pages" : "app";
|
|
106
|
+
return "pages";
|
|
55
107
|
}
|
|
56
108
|
const PATH_DELIMITER_REGEX = /([/#?\\]|%(2f|23|3f|5c))/gi;
|
|
57
109
|
function encodePathDelimiters(segment) {
|
|
@@ -187,4 +239,4 @@ function countConsumedPathnameSegments(visibleTreePathSegments, pathnameSegmentC
|
|
|
187
239
|
return consumed;
|
|
188
240
|
}
|
|
189
241
|
//#endregion
|
|
190
|
-
export { buildParams,
|
|
242
|
+
export { buildParams, compareHybridRoutePatterns, countConsumedPathnameSegments, decodeMatchedParams, decodeRouteSegment, isCatchAllSegment, isInvisibleSegment, isOptionalCatchAllSegment, normalizePathnameForRouteMatch, normalizePathnameForRouteMatchStrict, sortRoutes, splitPathSegments, splitPathnameForRouteMatch };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NextI18nConfig } from "../config/next-config.js";
|
|
1
2
|
import { Route } from "../routing/pages-router.js";
|
|
2
3
|
import { ModuleImporter } from "./instrumentation.js";
|
|
3
4
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -7,6 +8,10 @@ import { IncomingMessage, ServerResponse } from "node:http";
|
|
|
7
8
|
* Handle an API route request.
|
|
8
9
|
* Returns true if the request was handled, false if no API route matched.
|
|
9
10
|
*/
|
|
10
|
-
declare function handleApiRoute(runner: ModuleImporter, req: IncomingMessage, res: ServerResponse, url: string, apiRoutes: Route[]
|
|
11
|
+
declare function handleApiRoute(runner: ModuleImporter, req: IncomingMessage, res: ServerResponse, url: string, apiRoutes: Route[], nextConfig?: {
|
|
12
|
+
basePath?: string;
|
|
13
|
+
i18n?: NextI18nConfig | null;
|
|
14
|
+
trailingSlash?: boolean;
|
|
15
|
+
}): Promise<boolean>;
|
|
11
16
|
//#endregion
|
|
12
17
|
export { handleApiRoute };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { hasBasePath } from "../utils/base-path.js";
|
|
1
2
|
import { matchRoute } from "../routing/pages-router.js";
|
|
2
3
|
import "./server-globals.js";
|
|
4
|
+
import { parseCookieHeader } from "../utils/parse-cookie.js";
|
|
3
5
|
import { NextRequest } from "../shims/server.js";
|
|
4
6
|
import { importModule, reportRequestError } from "./instrumentation.js";
|
|
5
|
-
import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
|
|
7
|
+
import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
|
|
6
8
|
import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
|
|
7
9
|
import { isEdgeApiRuntime } from "./edge-api-runtime.js";
|
|
8
10
|
import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pages-body-parser-config.js";
|
|
@@ -71,13 +73,7 @@ async function parseBody(req, sizeLimit = MAX_BODY_SIZE) {
|
|
|
71
73
|
* Parse cookies from the Cookie header.
|
|
72
74
|
*/
|
|
73
75
|
function parseCookies(req) {
|
|
74
|
-
|
|
75
|
-
const cookies = {};
|
|
76
|
-
for (const part of header.split(";")) {
|
|
77
|
-
const [key, ...rest] = part.split("=");
|
|
78
|
-
if (key) cookies[key.trim()] = rest.join("=").trim();
|
|
79
|
-
}
|
|
80
|
-
return cookies;
|
|
76
|
+
return parseCookieHeader(req.headers.cookie);
|
|
81
77
|
}
|
|
82
78
|
function isEdgeApiRouteModule(module) {
|
|
83
79
|
if (typeof module.default !== "function") return false;
|
|
@@ -98,13 +94,19 @@ function readEdgeRequestBody(req) {
|
|
|
98
94
|
req.on("error", (error) => controller.error(error));
|
|
99
95
|
} });
|
|
100
96
|
}
|
|
101
|
-
function createEdgeApiRequest(req, url) {
|
|
97
|
+
function createEdgeApiRequest(req, url, params, nextConfig) {
|
|
102
98
|
const headers = new Headers();
|
|
103
|
-
for (const [name, value] of Object.entries(req.headers))
|
|
104
|
-
|
|
99
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
100
|
+
if (name.startsWith(":")) continue;
|
|
101
|
+
if (Array.isArray(value)) for (const item of value) headers.append(name, item);
|
|
102
|
+
else if (value !== void 0) headers.set(name, value);
|
|
103
|
+
}
|
|
105
104
|
const proto = resolveRequestProtocol(req);
|
|
106
105
|
const host = resolveRequestHost(req, "localhost");
|
|
107
|
-
const requestUrl = new URL(url, `${proto}://${host}`);
|
|
106
|
+
const requestUrl = new URL(req.url ?? url, `${proto}://${host}`);
|
|
107
|
+
const basePath = nextConfig?.basePath;
|
|
108
|
+
if (basePath && !hasBasePath(requestUrl.pathname, basePath)) requestUrl.pathname = `${basePath}${requestUrl.pathname}`;
|
|
109
|
+
requestUrl.search = urlQueryToSearchParams(mergeRouteParamsIntoQuery(parseQueryString(url), params)).toString();
|
|
108
110
|
const body = readEdgeRequestBody(req);
|
|
109
111
|
const init = {
|
|
110
112
|
headers,
|
|
@@ -210,14 +212,18 @@ function enhanceApiObjects(req, res, query, body) {
|
|
|
210
212
|
* Handle an API route request.
|
|
211
213
|
* Returns true if the request was handled, false if no API route matched.
|
|
212
214
|
*/
|
|
213
|
-
async function handleApiRoute(runner, req, res, url, apiRoutes) {
|
|
215
|
+
async function handleApiRoute(runner, req, res, url, apiRoutes, nextConfig) {
|
|
214
216
|
const match = matchRoute(url, apiRoutes);
|
|
215
217
|
if (!match) return false;
|
|
216
218
|
const { route, params } = match;
|
|
217
219
|
try {
|
|
218
220
|
const apiModule = await importModule(runner, route.filePath);
|
|
219
221
|
if (isEdgeApiRouteModule(apiModule)) {
|
|
220
|
-
const nextRequest = new NextRequest(createEdgeApiRequest(req, url)
|
|
222
|
+
const nextRequest = new NextRequest(createEdgeApiRequest(req, url, params, nextConfig), nextConfig ? { nextConfig: {
|
|
223
|
+
basePath: nextConfig.basePath,
|
|
224
|
+
i18n: nextConfig.i18n ?? void 0,
|
|
225
|
+
trailingSlash: nextConfig.trailingSlash
|
|
226
|
+
} } : void 0);
|
|
221
227
|
const response = await apiModule.default(nextRequest);
|
|
222
228
|
if (!(response instanceof Response)) throw new Error("Edge API route did not return a Response");
|
|
223
229
|
res.statusCode = response.status;
|
|
@@ -253,7 +259,7 @@ async function handleApiRoute(runner, req, res, url, apiRoutes) {
|
|
|
253
259
|
reportRequestError(e instanceof Error ? e : new Error(String(e)), {
|
|
254
260
|
path: url,
|
|
255
261
|
method: req.method ?? "GET",
|
|
256
|
-
headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v ?? "")]))
|
|
262
|
+
headers: Object.fromEntries(Object.entries(req.headers).filter(([k]) => !k.startsWith(":")).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v ?? "")]))
|
|
257
263
|
}, {
|
|
258
264
|
routerKind: "Pages Router",
|
|
259
265
|
routePath: match.route.pattern,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ServerActionResultFacts } from "./navigation-planner.js";
|
|
2
|
+
|
|
1
3
|
//#region src/server/app-browser-action-result.d.ts
|
|
2
4
|
type AppBrowserServerActionResult<TRoot> = {
|
|
3
5
|
root?: TRoot;
|
|
@@ -24,12 +26,23 @@ declare function shouldClearClientNavigationCachesForServerActionResult<TRoot>(r
|
|
|
24
26
|
declare function parseServerActionRevalidationHeader(headers: Pick<Headers, "get">): ServerActionRevalidationKind;
|
|
25
27
|
declare function normalizeServerActionThrownValue(data: unknown, responseStatus: number): unknown;
|
|
26
28
|
declare function readInvalidServerActionResponseError(response: Pick<Response, "headers" | "status" | "text">, hasRedirectLocation: boolean): Promise<Error | null>;
|
|
27
|
-
|
|
28
|
-
declare function resolveServerActionRedirectCompatibilityHardNavigationTarget(options: {
|
|
29
|
+
type ServerActionResultResponseFactsInput = {
|
|
29
30
|
actionRedirectHref: string | null;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
actionRedirectType: string | null;
|
|
32
|
+
clientCompatibilityId: string | null;
|
|
33
|
+
contentTypeHeader: string | null;
|
|
34
|
+
compatibilityIdHeader: string | null;
|
|
35
|
+
currentHref: string;
|
|
36
|
+
origin: string;
|
|
37
|
+
responseUrl: string | null;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Converts raw browser response data into the narrow facts expected by the
|
|
41
|
+
* navigation planner. This is the single place where redirect-type
|
|
42
|
+
* normalisation and RSC content-type detection happen for server-action
|
|
43
|
+
* compatibility checks.
|
|
44
|
+
*/
|
|
45
|
+
declare function createServerActionResultFacts(input: ServerActionResultResponseFactsInput): ServerActionResultFacts;
|
|
33
46
|
declare function shouldScheduleRefreshForDiscardedServerAction(revalidation: ServerActionRevalidationKind): boolean;
|
|
34
47
|
declare function createServerActionInitiationSnapshot<TRouterState>(options: {
|
|
35
48
|
href: string;
|
|
@@ -48,4 +61,4 @@ type DiscardedServerActionRefreshSchedulerOptions = {
|
|
|
48
61
|
};
|
|
49
62
|
declare function createDiscardedServerActionRefreshScheduler(options: DiscardedServerActionRefreshSchedulerOptions): DiscardedServerActionRefreshScheduler;
|
|
50
63
|
//#endregion
|
|
51
|
-
export { AppBrowserServerActionResult, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError,
|
|
64
|
+
export { AppBrowserServerActionResult, ServerActionResultResponseFactsInput, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
|