vinext 0.1.7 → 0.2.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 +51 -30
- package/dist/build/inject-pregenerated-paths.d.ts +4 -0
- package/dist/build/inject-pregenerated-paths.js +18 -0
- package/dist/build/layout-classification.js +1 -1
- package/dist/build/nitro-route-rules.js +1 -1
- package/dist/build/pages-client-assets-module.d.ts +11 -0
- package/dist/build/pages-client-assets-module.js +27 -0
- package/dist/build/prerender.d.ts +2 -1
- package/dist/build/prerender.js +13 -6
- package/dist/build/report.d.ts +13 -2
- package/dist/build/report.js +99 -2
- package/dist/build/run-prerender.d.ts +7 -0
- package/dist/build/run-prerender.js +10 -1
- package/dist/build/standalone.js +2 -0
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/check.d.ts +18 -0
- package/dist/check.js +77 -19
- package/dist/cli-args.d.ts +1 -2
- package/dist/cli-args.js +1 -1
- package/dist/cli-dev-config.d.ts +12 -0
- package/dist/cli-dev-config.js +23 -0
- package/dist/cli.js +113 -97
- package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay-store.js +1 -1
- package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay.js +2 -2
- package/dist/client/navigation-runtime.d.ts +2 -0
- package/dist/client/navigation-runtime.js +3 -1
- package/dist/client/window-next.d.ts +2 -1
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.js +2 -2
- package/dist/config/config-matchers.js +14 -10
- package/dist/config/next-config.d.ts +6 -3
- package/dist/config/next-config.js +47 -1
- package/dist/config/prerender.d.ts +34 -0
- package/dist/config/prerender.js +55 -0
- package/dist/config/server-external-packages.d.ts +4 -0
- package/dist/config/server-external-packages.js +91 -0
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +89 -15
- package/dist/entries/app-rsc-manifest.js +10 -0
- package/dist/entries/pages-client-entry.d.ts +3 -0
- package/dist/entries/pages-client-entry.js +16 -1
- package/dist/entries/pages-server-entry.js +6 -2
- package/dist/image/image-adapters-virtual.d.ts +59 -0
- package/dist/image/image-adapters-virtual.js +59 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +211 -118
- package/dist/init-cloudflare.d.ts +43 -0
- package/dist/init-cloudflare.js +839 -0
- package/dist/init-platform.d.ts +40 -0
- package/dist/init-platform.js +195 -0
- package/dist/init.d.ts +16 -38
- package/dist/init.js +208 -97
- package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
- package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
- package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
- package/dist/packages/cloudflare/src/deploy-config.js +150 -0
- package/dist/packages/cloudflare/src/deploy-help.js +55 -0
- package/dist/packages/cloudflare/src/deploy.js +276 -0
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
- package/dist/plugins/ast-scope.d.ts +16 -0
- package/dist/plugins/ast-scope.js +62 -0
- package/dist/plugins/ast-utils.d.ts +27 -1
- package/dist/plugins/ast-utils.js +32 -1
- package/dist/plugins/css-module-imports.d.ts +14 -0
- package/dist/plugins/css-module-imports.js +59 -0
- package/dist/plugins/extensionless-dynamic-import.js +2 -2
- package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
- package/dist/plugins/ignore-dynamic-requests.js +530 -0
- package/dist/plugins/middleware-server-only.d.ts +8 -6
- package/dist/plugins/middleware-server-only.js +8 -7
- package/dist/plugins/optimize-imports.js +1 -1
- package/dist/plugins/typeof-window.d.ts +1 -1
- package/dist/plugins/typeof-window.js +28 -56
- package/dist/routing/app-route-graph.d.ts +29 -4
- package/dist/routing/app-route-graph.js +182 -40
- package/dist/routing/app-router.d.ts +5 -0
- package/dist/routing/app-router.js +5 -0
- package/dist/routing/file-matcher.d.ts +8 -0
- package/dist/routing/file-matcher.js +10 -1
- package/dist/routing/pages-router.js +2 -2
- package/dist/server/api-handler.js +9 -14
- package/dist/server/app-browser-action-result.d.ts +4 -1
- package/dist/server/app-browser-action-result.js +8 -1
- package/dist/server/app-browser-entry.js +194 -190
- package/dist/server/app-browser-history-controller.d.ts +2 -1
- package/dist/server/app-browser-history-controller.js +6 -2
- package/dist/server/app-browser-interception-context.d.ts +1 -0
- package/dist/server/app-browser-interception-context.js +4 -2
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +60 -31
- package/dist/server/app-browser-server-action-client.d.ts +32 -0
- package/dist/server/app-browser-server-action-client.js +127 -0
- package/dist/server/app-browser-state.d.ts +11 -1
- package/dist/server/app-browser-state.js +25 -8
- package/dist/server/app-browser-visible-commit.d.ts +6 -1
- package/dist/server/app-browser-visible-commit.js +43 -25
- package/dist/server/app-elements-wire.d.ts +9 -2
- package/dist/server/app-elements-wire.js +8 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +4 -3
- package/dist/server/app-fallback-renderer.d.ts +5 -2
- package/dist/server/app-fallback-renderer.js +15 -7
- package/dist/server/app-hydration-cache-publication.d.ts +11 -0
- package/dist/server/app-hydration-cache-publication.js +43 -0
- package/dist/server/app-middleware.d.ts +4 -3
- package/dist/server/app-middleware.js +8 -3
- package/dist/server/app-optimistic-routing.js +11 -4
- package/dist/server/app-page-boundary-render.d.ts +3 -0
- package/dist/server/app-page-boundary-render.js +14 -3
- package/dist/server/app-page-cache-finalizer.d.ts +1 -0
- package/dist/server/app-page-cache-finalizer.js +10 -3
- package/dist/server/app-page-cache-render.d.ts +1 -0
- package/dist/server/app-page-cache-render.js +8 -4
- package/dist/server/app-page-cache.d.ts +1 -0
- package/dist/server/app-page-cache.js +5 -2
- package/dist/server/app-page-dispatch.d.ts +33 -4
- package/dist/server/app-page-dispatch.js +63 -90
- package/dist/server/app-page-element-builder.d.ts +9 -2
- package/dist/server/app-page-element-builder.js +114 -23
- package/dist/server/app-page-head.d.ts +16 -1
- package/dist/server/app-page-head.js +44 -22
- package/dist/server/app-page-params.d.ts +2 -1
- package/dist/server/app-page-params.js +8 -1
- package/dist/server/app-page-ppr-runtime.d.ts +7 -0
- package/dist/server/app-page-ppr-runtime.js +70 -0
- package/dist/server/app-page-probe.d.ts +1 -0
- package/dist/server/app-page-probe.js +6 -1
- package/dist/server/app-page-render-identity.d.ts +1 -0
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render.d.ts +4 -1
- package/dist/server/app-page-render.js +26 -3
- package/dist/server/app-page-request.d.ts +22 -1
- package/dist/server/app-page-request.js +92 -15
- package/dist/server/app-page-route-wiring.d.ts +14 -2
- package/dist/server/app-page-route-wiring.js +61 -25
- package/dist/server/app-page-search-params-observation.d.ts +4 -2
- package/dist/server/app-page-search-params-observation.js +11 -7
- package/dist/server/app-page-segment-state.d.ts +2 -1
- package/dist/server/app-page-segment-state.js +3 -1
- package/dist/server/app-page-stream.d.ts +5 -0
- package/dist/server/app-page-stream.js +3 -1
- package/dist/server/app-ppr-fallback-shell-render.d.ts +4 -3
- package/dist/server/app-route-handler-dispatch.js +1 -0
- package/dist/server/app-route-handler-execution.js +7 -2
- package/dist/server/app-route-handler-response.js +1 -0
- package/dist/server/app-route-handler-runtime.js +1 -1
- package/dist/server/app-route-module-loader.d.ts +2 -0
- package/dist/server/app-route-module-loader.js +1 -0
- package/dist/server/app-router-entry.js +16 -8
- package/dist/server/app-rsc-errors.js +7 -1
- package/dist/server/app-rsc-handler.d.ts +3 -1
- package/dist/server/app-rsc-handler.js +67 -39
- package/dist/server/app-rsc-request-normalization.d.ts +3 -2
- package/dist/server/app-rsc-request-normalization.js +6 -3
- package/dist/server/app-rsc-route-matching.d.ts +7 -0
- package/dist/server/app-rsc-route-matching.js +36 -3
- package/dist/server/app-segment-config.d.ts +12 -0
- package/dist/server/app-segment-config.js +91 -5
- package/dist/server/app-server-action-execution.d.ts +5 -0
- package/dist/server/app-server-action-execution.js +94 -33
- package/dist/server/app-ssr-entry.d.ts +3 -0
- package/dist/server/app-ssr-entry.js +19 -8
- package/dist/server/app-ssr-stream.d.ts +7 -3
- package/dist/server/app-ssr-stream.js +10 -6
- package/dist/server/app-static-generation.d.ts +1 -0
- package/dist/server/app-static-generation.js +1 -0
- package/dist/server/app-visited-response-cache.d.ts +6 -0
- package/dist/server/app-visited-response-cache.js +3 -1
- package/dist/server/client-trace-metadata.js +26 -0
- package/dist/server/default-global-not-found-module.d.ts +14 -0
- package/dist/server/default-global-not-found-module.js +14 -0
- package/dist/server/dev-route-files.js +4 -0
- package/dist/server/dev-server.js +19 -18
- package/dist/server/dev-stack-sourcemap.d.ts +1 -1
- package/dist/server/dev-stack-sourcemap.js +1 -1
- package/dist/server/fetch-handler.d.ts +2 -0
- package/dist/server/fetch-handler.js +18 -0
- package/dist/server/headers.d.ts +5 -15
- package/dist/server/headers.js +4 -15
- package/dist/server/image-optimization.d.ts +51 -1
- package/dist/server/image-optimization.js +52 -2
- package/dist/server/implicit-tags.js +1 -1
- package/dist/server/isr-cache.d.ts +1 -1
- package/dist/server/isr-cache.js +2 -2
- package/dist/server/metadata-routes.d.ts +3 -1
- package/dist/server/metadata-routes.js +12 -2
- package/dist/server/middleware-runtime.js +6 -1
- package/dist/server/navigation-planner.d.ts +1 -0
- package/dist/server/navigation-planner.js +14 -3
- package/dist/server/pages-api-route.js +2 -2
- package/dist/server/pages-asset-tags.d.ts +4 -6
- package/dist/server/pages-asset-tags.js +12 -12
- package/dist/server/pages-client-assets.d.ts +12 -0
- package/dist/server/pages-client-assets.js +10 -0
- package/dist/server/pages-node-compat.d.ts +8 -1
- package/dist/server/pages-node-compat.js +32 -4
- package/dist/server/pages-page-data.d.ts +24 -1
- package/dist/server/pages-page-data.js +45 -24
- package/dist/server/pages-page-handler.d.ts +2 -1
- package/dist/server/pages-page-handler.js +19 -12
- package/dist/server/pages-page-response.d.ts +7 -0
- package/dist/server/pages-page-response.js +4 -1
- package/dist/server/pages-request-pipeline.d.ts +2 -0
- package/dist/server/pages-request-pipeline.js +25 -1
- package/dist/server/pages-router-entry.d.ts +30 -0
- package/dist/server/pages-router-entry.js +108 -0
- package/dist/server/prerender-manifest.d.ts +3 -1
- package/dist/server/prerender-route-params.js +1 -1
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +48 -26
- package/dist/server/request-pipeline.js +1 -0
- package/dist/server/seed-cache.js +4 -4
- package/dist/server/worker-utils.d.ts +2 -1
- package/dist/server/worker-utils.js +7 -1
- package/dist/shims/app-router-scroll-state.d.ts +1 -0
- package/dist/shims/app-router-scroll-state.js +1 -0
- package/dist/shims/app-router-scroll.js +2 -1
- package/dist/shims/cache.js +19 -15
- package/dist/shims/cdn-cache.js +1 -1
- package/dist/shims/dynamic-preload-chunks.js +2 -1
- package/dist/shims/error-boundary.d.ts +19 -1
- package/dist/shims/error-boundary.js +11 -1
- package/dist/shims/form.d.ts +3 -1
- package/dist/shims/form.js +37 -43
- package/dist/shims/headers.d.ts +9 -1
- package/dist/shims/headers.js +31 -6
- package/dist/shims/image.js +75 -14
- package/dist/shims/internal/app-route-detection.d.ts +2 -17
- package/dist/shims/internal/app-route-detection.js +4 -17
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -15
- package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
- package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
- package/dist/shims/internal/pages-data-target.d.ts +10 -9
- package/dist/shims/internal/pages-data-target.js +91 -12
- package/dist/shims/internal/pages-router-components.d.ts +7 -0
- package/dist/shims/internal/pages-router-components.js +13 -0
- package/dist/shims/layout-segment-context.d.ts +4 -1
- package/dist/shims/layout-segment-context.js +18 -4
- package/dist/shims/link.js +70 -37
- package/dist/shims/metadata.d.ts +3 -2
- package/dist/shims/metadata.js +8 -4
- package/dist/shims/navigation.d.ts +5 -1
- package/dist/shims/navigation.js +45 -10
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/root-params.d.ts +15 -1
- package/dist/shims/root-params.js +21 -1
- package/dist/shims/router.d.ts +5 -7
- package/dist/shims/router.js +374 -94
- package/dist/shims/server.js +3 -2
- package/dist/shims/slot.js +3 -6
- package/dist/typegen.js +6 -5
- package/dist/utils/client-runtime-metadata.d.ts +2 -18
- package/dist/utils/client-runtime-metadata.js +31 -22
- package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
- package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
- package/dist/utils/domain-locale.d.ts +6 -3
- package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
- package/dist/{server → utils}/middleware-request-headers.js +2 -2
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +1 -1
- package/dist/utils/project.d.ts +52 -2
- package/dist/utils/project.js +230 -5
- package/dist/utils/protocol-headers.d.ts +17 -0
- package/dist/utils/protocol-headers.js +17 -0
- package/dist/utils/react-version.d.ts +4 -0
- package/dist/utils/react-version.js +44 -0
- package/package.json +53 -4
- package/dist/deploy.d.ts +0 -190
- package/dist/deploy.js +0 -1033
- package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
- /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
|
@@ -1,55 +1,17 @@
|
|
|
1
1
|
import { collectBindingNames, forEachAstChild, hasRange, isAstRecord, isIdentifierNamed, nodeArray } from "./ast-utils.js";
|
|
2
|
+
import { collectDirectScopeBindings, collectLoopScopeBindings, collectSwitchScopeBindings, collectVarScopeBindings, createAstScope, hasAstBinding, isFunctionNode } from "./ast-scope.js";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { parseAst } from "vite";
|
|
3
5
|
import MagicString from "magic-string";
|
|
4
6
|
//#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
7
|
function createChildScope(node, parent) {
|
|
45
|
-
if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "
|
|
46
|
-
const scope =
|
|
8
|
+
if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "TSModuleBlock" && node.type !== "CatchClause" && node.type !== "ForStatement" && node.type !== "ForInStatement" && node.type !== "ForOfStatement" && node.type !== "ClassDeclaration" && node.type !== "ClassExpression") return null;
|
|
9
|
+
const scope = createAstScope(parent);
|
|
47
10
|
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") collectBindingNames(node.id, scope.bindings);
|
|
48
11
|
else if (node.type === "CatchClause") collectBindingNames(node.param, scope.bindings);
|
|
49
|
-
|
|
50
|
-
if (node.type === "
|
|
51
|
-
|
|
52
|
-
}
|
|
12
|
+
collectDirectScopeBindings(node, scope);
|
|
13
|
+
if (node.type === "StaticBlock" || node.type === "TSModuleBlock") collectVarScopeBindings(node, scope);
|
|
14
|
+
if (node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement") collectLoopScopeBindings(node, scope);
|
|
53
15
|
return scope;
|
|
54
16
|
}
|
|
55
17
|
function getTypeofWindowReplacement(environment) {
|
|
@@ -70,42 +32,52 @@ function evaluateTypeofWindowComparison(node, replacement, scope) {
|
|
|
70
32
|
].includes(String(node.operator))) return null;
|
|
71
33
|
const left = isAstRecord(node.left) ? node.left : null;
|
|
72
34
|
const right = isAstRecord(node.right) ? node.right : null;
|
|
73
|
-
const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !
|
|
74
|
-
const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !
|
|
35
|
+
const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !hasAstBinding(scope, "window");
|
|
36
|
+
const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !hasAstBinding(scope, "window");
|
|
75
37
|
const comparedValue = leftIsTypeofWindow ? stringLiteralValue(right) : rightIsTypeofWindow ? stringLiteralValue(left) : null;
|
|
76
38
|
if (comparedValue === null) return null;
|
|
77
39
|
const equal = replacement === comparedValue;
|
|
78
40
|
return node.operator === "==" || node.operator === "===" ? equal : !equal;
|
|
79
41
|
}
|
|
80
|
-
function replaceTypeofWindow(code, replacement) {
|
|
42
|
+
function replaceTypeofWindow(code, replacement, id = "file.js") {
|
|
81
43
|
if (!/typeof\s+window/.test(code)) return null;
|
|
44
|
+
const extension = path.extname(id.split("?", 1)[0]);
|
|
45
|
+
const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : "js";
|
|
82
46
|
let ast;
|
|
83
47
|
try {
|
|
84
|
-
ast = parseAst(code);
|
|
48
|
+
ast = parseAst(code, { lang });
|
|
85
49
|
} catch {
|
|
86
50
|
return null;
|
|
87
51
|
}
|
|
88
52
|
const output = new MagicString(code);
|
|
89
53
|
let changed = false;
|
|
90
54
|
if (!isAstRecord(ast)) return null;
|
|
91
|
-
const rootScope =
|
|
92
|
-
|
|
93
|
-
|
|
55
|
+
const rootScope = createAstScope(null);
|
|
56
|
+
collectDirectScopeBindings(ast, rootScope);
|
|
57
|
+
collectVarScopeBindings(ast, rootScope);
|
|
94
58
|
function visit(node, parentScope) {
|
|
95
59
|
if (isFunctionNode(node)) {
|
|
96
|
-
const parameterScope =
|
|
60
|
+
const parameterScope = createAstScope(parentScope);
|
|
97
61
|
collectBindingNames(node.id, parameterScope.bindings);
|
|
98
62
|
for (const parameter of nodeArray(node.params)) {
|
|
99
63
|
collectBindingNames(parameter, parameterScope.bindings);
|
|
100
64
|
if (isAstRecord(parameter)) visit(parameter, parameterScope);
|
|
101
65
|
}
|
|
102
66
|
if (isAstRecord(node.body)) if (node.body.type === "BlockStatement") {
|
|
103
|
-
const bodyScope =
|
|
104
|
-
|
|
67
|
+
const bodyScope = createAstScope(parameterScope);
|
|
68
|
+
collectDirectScopeBindings(node.body, bodyScope);
|
|
69
|
+
collectVarScopeBindings(node.body, bodyScope);
|
|
105
70
|
visit(node.body, bodyScope);
|
|
106
71
|
} else visit(node.body, parameterScope);
|
|
107
72
|
return;
|
|
108
73
|
}
|
|
74
|
+
if (node.type === "SwitchStatement") {
|
|
75
|
+
if (isAstRecord(node.discriminant)) visit(node.discriminant, parentScope);
|
|
76
|
+
const switchScope = createAstScope(parentScope);
|
|
77
|
+
collectSwitchScopeBindings(node, switchScope);
|
|
78
|
+
for (const switchCase of nodeArray(node.cases)) if (isAstRecord(switchCase)) visit(switchCase, switchScope);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
109
81
|
const scope = createChildScope(node, parentScope) ?? parentScope;
|
|
110
82
|
if (node.type === "IfStatement" && hasRange(node)) {
|
|
111
83
|
const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
|
|
@@ -132,7 +104,7 @@ function replaceTypeofWindow(code, replacement) {
|
|
|
132
104
|
return;
|
|
133
105
|
}
|
|
134
106
|
}
|
|
135
|
-
if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !
|
|
107
|
+
if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !hasAstBinding(scope, "window") && hasRange(node)) {
|
|
136
108
|
output.overwrite(node.start, node.end, JSON.stringify(replacement));
|
|
137
109
|
changed = true;
|
|
138
110
|
return;
|
|
@@ -23,7 +23,9 @@ type InterceptingRoute = {
|
|
|
23
23
|
sourceMatchPattern: string; /** Absolute path to the intercepting page component */
|
|
24
24
|
pagePath: string; /** Filesystem segments from app/ root to the intercepting page directory. */
|
|
25
25
|
sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
|
|
26
|
-
layoutPaths: string[]; /**
|
|
26
|
+
layoutPaths: string[]; /** Normalized branch segments accumulated at each intercept layout. */
|
|
27
|
+
layoutSegments?: string[][]; /** Full normalized interception branch segments through the page. */
|
|
28
|
+
branchSegments?: string[]; /** Parameter names for dynamic segments */
|
|
27
29
|
params: string[];
|
|
28
30
|
/**
|
|
29
31
|
* Synthetic page-carrier slot id for sibling (slot-less) interception.
|
|
@@ -40,7 +42,9 @@ type ParallelSlot = {
|
|
|
40
42
|
hasPage: boolean; /** Absolute path to the slot's page component */
|
|
41
43
|
pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
|
|
42
44
|
defaultPath: string | null; /** Absolute path to the slot's layout component (wraps slot content) */
|
|
43
|
-
layoutPath: string | null; /**
|
|
45
|
+
layoutPath: string | null; /** Nested active-branch layouts whose exports contribute route config. */
|
|
46
|
+
configLayoutPaths?: string[]; /** Tree positions of configLayoutPaths relative to the slot root. */
|
|
47
|
+
configLayoutTreePositions?: number[]; /** Absolute path to the slot's loading component */
|
|
44
48
|
loadingPath: string | null; /** Absolute path to the slot's error component */
|
|
45
49
|
errorPath: string | null; /** Intercepting routes within this slot */
|
|
46
50
|
interceptingRoutes: InterceptingRoute[];
|
|
@@ -79,7 +83,12 @@ type AppRoute = {
|
|
|
79
83
|
routePath: string | null; /** Ordered list of layout files from root to leaf */
|
|
80
84
|
layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
|
|
81
85
|
templates: string[]; /** Parallel route slots (from @slot directories at the route's directory level) */
|
|
82
|
-
parallelSlots: ParallelSlot[];
|
|
86
|
+
parallelSlots: ParallelSlot[]; /** Stable implicit children-slot identity for parallel-slot sub-route families. */
|
|
87
|
+
childrenSlot?: {
|
|
88
|
+
id: string;
|
|
89
|
+
ownerTreePath: string;
|
|
90
|
+
state: "active" | "default" | "unmatched";
|
|
91
|
+
};
|
|
83
92
|
/**
|
|
84
93
|
* Interception markers not wrapped in an `@slot` directory.
|
|
85
94
|
* On soft-nav, the intercepting page replaces the entire page response.
|
|
@@ -118,7 +127,16 @@ type AppRoute = {
|
|
|
118
127
|
* Includes route groups and dynamic segments (as template strings like "[id]").
|
|
119
128
|
* Used at render time to compute the child segments for useSelectedLayoutSegments().
|
|
120
129
|
*/
|
|
121
|
-
routeSegments: string[];
|
|
130
|
+
routeSegments: string[];
|
|
131
|
+
/**
|
|
132
|
+
* Active filesystem segments for the default `children` slot.
|
|
133
|
+
*
|
|
134
|
+
* Synthetic routes materialized only from named parallel-slot pages still
|
|
135
|
+
* use the full `routeSegments` for URL matching, params, and route identity,
|
|
136
|
+
* but their children slot renders the parent's default.tsx. In that case the
|
|
137
|
+
* active children segments remain at the parent route.
|
|
138
|
+
*/
|
|
139
|
+
childrenRouteSegments?: string[]; /** Tree position (directory depth from app/ root) for each template. */
|
|
122
140
|
templateTreePositions?: number[];
|
|
123
141
|
/**
|
|
124
142
|
* Tree position (directory depth from app/ root) for each layout.
|
|
@@ -272,6 +290,13 @@ type RouteManifest = {
|
|
|
272
290
|
graphVersion: GraphVersion;
|
|
273
291
|
segmentGraph: StaticSegmentGraph;
|
|
274
292
|
};
|
|
293
|
+
/**
|
|
294
|
+
* Build the App Router route graph by scanning `appDir`.
|
|
295
|
+
*
|
|
296
|
+
* `appDir` must be forward-slash. Every path in the graph is derived from it
|
|
297
|
+
* with `path.posix.*` and `findFile`, so a native appDir would produce mixed
|
|
298
|
+
* separators on Windows. Production callers normalize it at their entry.
|
|
299
|
+
*/
|
|
275
300
|
declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
|
|
276
301
|
routes: AppRouteGraphRoute[];
|
|
277
302
|
routeManifest: RouteManifest;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { normalizePathSeparators } from "../utils/path.js";
|
|
2
1
|
import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
|
|
2
|
+
import { normalizePathSeparators } from "../utils/path.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";
|
|
@@ -83,6 +83,19 @@ function createStaticSegmentGraph(routes) {
|
|
|
83
83
|
templateIds: [...route.ids.templates],
|
|
84
84
|
slotIds: route.parallelSlots.map((slot) => slot.id).sort(compareStableStrings)
|
|
85
85
|
});
|
|
86
|
+
if (route.childrenSlot) {
|
|
87
|
+
const ownerLayoutId = findRouteManifestOwnerLayoutIdByTreePath(route, route.childrenSlot.ownerTreePath);
|
|
88
|
+
routeEntries.get(route.ids.route).slotIds = [...routeEntries.get(route.ids.route).slotIds, route.childrenSlot.id].sort(compareStableStrings);
|
|
89
|
+
slotBindings.set(`${route.ids.route}::${route.childrenSlot.id}`, {
|
|
90
|
+
id: `${route.ids.route}::${route.childrenSlot.id}`,
|
|
91
|
+
routeId: route.ids.route,
|
|
92
|
+
slotId: route.childrenSlot.id,
|
|
93
|
+
ownerLayoutId,
|
|
94
|
+
state: route.childrenSlot.state,
|
|
95
|
+
defaultId: null,
|
|
96
|
+
routeSegments: null
|
|
97
|
+
});
|
|
98
|
+
}
|
|
86
99
|
if (route.ids.page) pages.set(route.ids.page, {
|
|
87
100
|
id: route.ids.page,
|
|
88
101
|
routeId: route.ids.route,
|
|
@@ -225,6 +238,10 @@ function findRouteManifestOwnerLayoutId(route, treePosition) {
|
|
|
225
238
|
const layoutIndex = route.layoutTreePositions.indexOf(treePosition);
|
|
226
239
|
return route.ids.layouts[layoutIndex] ?? null;
|
|
227
240
|
}
|
|
241
|
+
function findRouteManifestOwnerLayoutIdByTreePath(route, treePath) {
|
|
242
|
+
const layoutIndex = route.layoutTreePositions.findIndex((treePosition) => createAppRouteGraphTreePath(route.routeSegments, treePosition) === treePath);
|
|
243
|
+
return route.ids.layouts[layoutIndex] ?? null;
|
|
244
|
+
}
|
|
228
245
|
function findSlotOwnerLayoutId(route, slot) {
|
|
229
246
|
if (slot.layoutIndex < 0) return null;
|
|
230
247
|
return route.ids.layouts[slot.layoutIndex] ?? null;
|
|
@@ -344,6 +361,13 @@ function createRouteManifestGraphVersion(segmentGraph) {
|
|
|
344
361
|
};
|
|
345
362
|
return `graph:${createHash("sha256").update(JSON.stringify(stableShape)).digest("hex")}`;
|
|
346
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Build the App Router route graph by scanning `appDir`.
|
|
366
|
+
*
|
|
367
|
+
* `appDir` must be forward-slash. Every path in the graph is derived from it
|
|
368
|
+
* with `path.posix.*` and `findFile`, so a native appDir would produce mixed
|
|
369
|
+
* separators on Windows. Production callers normalize it at their entry.
|
|
370
|
+
*/
|
|
347
371
|
async function buildAppRouteGraph(appDir, matcher) {
|
|
348
372
|
const routes = [];
|
|
349
373
|
const scanMatcher = { ...matcher };
|
|
@@ -360,12 +384,16 @@ async function buildAppRouteGraph(appDir, matcher) {
|
|
|
360
384
|
const routePatterns = new Set(routes.map((route) => route.pattern));
|
|
361
385
|
const ghostParentRoutes = [];
|
|
362
386
|
for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
|
|
363
|
-
const dir = path.dirname(file);
|
|
364
|
-
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
387
|
+
const dir = path.posix.dirname(file);
|
|
388
|
+
const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
|
|
365
389
|
if (!hasParallelSlotDirectory(routeDir)) continue;
|
|
366
|
-
if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
|
|
367
|
-
const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
|
|
390
|
+
if (discoverParallelSlots(routeDir, appDir, scanMatcher, true).length === 0) continue;
|
|
391
|
+
const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null, true);
|
|
368
392
|
if (!route) continue;
|
|
393
|
+
if (routes.some((candidate) => candidate.patternParts.length === route.patternParts.length + 1 && candidate.patternParts.at(-1)?.endsWith("*") && patternsStructurallyEquivalent(candidate.patternParts.slice(0, -1), route.patternParts))) {
|
|
394
|
+
ghostParentRoutes.push(route);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
369
397
|
if (routePatterns.has(route.pattern)) {
|
|
370
398
|
ghostParentRoutes.push(route);
|
|
371
399
|
continue;
|
|
@@ -434,11 +462,16 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
434
462
|
const applySlotSubPages = (route, slotPages, rawSegments) => {
|
|
435
463
|
route.parallelSlots = route.parallelSlots.map((slot) => {
|
|
436
464
|
const subPage = slotPages.get(slot.key);
|
|
437
|
-
if (subPage !== void 0)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
465
|
+
if (subPage !== void 0) {
|
|
466
|
+
const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
|
|
467
|
+
return {
|
|
468
|
+
...slot,
|
|
469
|
+
pagePath: subPage,
|
|
470
|
+
configLayoutPaths,
|
|
471
|
+
configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
|
|
472
|
+
routeSegments: rawSegments
|
|
473
|
+
};
|
|
474
|
+
}
|
|
442
475
|
return slot;
|
|
443
476
|
});
|
|
444
477
|
};
|
|
@@ -477,8 +510,29 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
477
510
|
}
|
|
478
511
|
}
|
|
479
512
|
if (subPathMap.size === 0) continue;
|
|
513
|
+
const childrenOwnerTreePath = parentRoute.parallelSlots.find((slot) => path.dirname(slot.ownerDir) === parentPageDir)?.ownerTreePath;
|
|
514
|
+
if (!childrenOwnerTreePath) throw new Error(`[vinext] App route graph invariant violated: missing children slot owner for ${parentRoute.pattern}`);
|
|
480
515
|
const childrenDefault = findFile(parentPageDir, "default", matcher);
|
|
481
516
|
if (parentRoute.pagePath && !childrenDefault) continue;
|
|
517
|
+
const childrenSlotId = createAppRouteGraphSlotId("children", childrenOwnerTreePath);
|
|
518
|
+
if (parentRoute.pagePath) parentRoute.childrenSlot = {
|
|
519
|
+
id: childrenSlotId,
|
|
520
|
+
ownerTreePath: childrenOwnerTreePath,
|
|
521
|
+
state: "active"
|
|
522
|
+
};
|
|
523
|
+
for (const route of routes) {
|
|
524
|
+
if (!route.pagePath || route === parentRoute) continue;
|
|
525
|
+
const relativePageDir = path.relative(parentPageDir, path.dirname(route.pagePath));
|
|
526
|
+
if (relativePageDir === "" || relativePageDir === ".." || relativePageDir.startsWith(`..${path.sep}`) || path.isAbsolute(relativePageDir)) continue;
|
|
527
|
+
const existingOwnerDepth = route.childrenSlot?.ownerTreePath.split("/").filter(Boolean).length;
|
|
528
|
+
const candidateOwnerDepth = childrenOwnerTreePath.split("/").filter(Boolean).length;
|
|
529
|
+
if (existingOwnerDepth !== void 0 && existingOwnerDepth >= candidateOwnerDepth) continue;
|
|
530
|
+
route.childrenSlot = {
|
|
531
|
+
id: childrenSlotId,
|
|
532
|
+
ownerTreePath: childrenOwnerTreePath,
|
|
533
|
+
state: "active"
|
|
534
|
+
};
|
|
535
|
+
}
|
|
482
536
|
const childrenCatchAll = childrenDefault ? null : findCatchAllPage(parentPageDir, matcher);
|
|
483
537
|
const childrenFallback = childrenDefault ?? childrenCatchAll;
|
|
484
538
|
for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
|
|
@@ -495,9 +549,12 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
495
549
|
if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
|
|
496
550
|
const subSlots = parentRoute.parallelSlots.map((slot) => {
|
|
497
551
|
const subPage = slotPages.get(slot.key);
|
|
552
|
+
const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
|
|
498
553
|
return {
|
|
499
554
|
...slot,
|
|
500
555
|
pagePath: subPage || null,
|
|
556
|
+
configLayoutPaths,
|
|
557
|
+
configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
|
|
501
558
|
routeSegments: subPage ? rawSegments : null
|
|
502
559
|
};
|
|
503
560
|
});
|
|
@@ -517,6 +574,11 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
517
574
|
layouts: parentRoute.layouts,
|
|
518
575
|
templates: parentRoute.templates,
|
|
519
576
|
parallelSlots: subSlots,
|
|
577
|
+
childrenSlot: {
|
|
578
|
+
id: childrenSlotId,
|
|
579
|
+
ownerTreePath: childrenOwnerTreePath,
|
|
580
|
+
state: childrenDefault ? "default" : childrenCatchAll ? "active" : "unmatched"
|
|
581
|
+
},
|
|
520
582
|
loadingPath: parentRoute.loadingPath,
|
|
521
583
|
errorPath: parentRoute.errorPath,
|
|
522
584
|
layoutErrorPaths: parentRoute.layoutErrorPaths,
|
|
@@ -527,6 +589,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
527
589
|
unauthorizedPath: parentRoute.unauthorizedPath,
|
|
528
590
|
unauthorizedPaths: parentRoute.unauthorizedPaths,
|
|
529
591
|
routeSegments: [...parentRoute.routeSegments, ...rawSegments],
|
|
592
|
+
childrenRouteSegments: childrenDefault ? parentRoute.routeSegments : void 0,
|
|
530
593
|
templateTreePositions: parentRoute.templateTreePositions,
|
|
531
594
|
layoutTreePositions: parentRoute.layoutTreePositions,
|
|
532
595
|
isDynamic: parentRoute.isDynamic || subIsDynamic,
|
|
@@ -541,6 +604,26 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
|
|
|
541
604
|
}
|
|
542
605
|
return syntheticRoutes;
|
|
543
606
|
}
|
|
607
|
+
const dirEntriesCache = /* @__PURE__ */ new WeakMap();
|
|
608
|
+
function readDirEntriesCached(dir, matcher) {
|
|
609
|
+
let perMatcher = dirEntriesCache.get(matcher);
|
|
610
|
+
if (!perMatcher) {
|
|
611
|
+
perMatcher = /* @__PURE__ */ new Map();
|
|
612
|
+
dirEntriesCache.set(matcher, perMatcher);
|
|
613
|
+
}
|
|
614
|
+
let entries = perMatcher.get(dir);
|
|
615
|
+
if (entries === void 0) {
|
|
616
|
+
try {
|
|
617
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
618
|
+
} catch (error) {
|
|
619
|
+
const code = error.code;
|
|
620
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
621
|
+
entries = [];
|
|
622
|
+
}
|
|
623
|
+
perMatcher.set(dir, entries);
|
|
624
|
+
}
|
|
625
|
+
return entries;
|
|
626
|
+
}
|
|
544
627
|
const findSlotSubPagesCache = /* @__PURE__ */ new WeakMap();
|
|
545
628
|
function findSlotSubPages(slotDir, matcher) {
|
|
546
629
|
let perMatcher = findSlotSubPagesCache.get(matcher);
|
|
@@ -574,6 +657,23 @@ function findSlotSubPages(slotDir, matcher) {
|
|
|
574
657
|
perMatcher.set(slotDir, results);
|
|
575
658
|
return results;
|
|
576
659
|
}
|
|
660
|
+
function findSlotConfigLayoutPaths(slotDir, pagePath, matcher) {
|
|
661
|
+
if (!pagePath) return [];
|
|
662
|
+
const layouts = [];
|
|
663
|
+
let dir = path.dirname(pagePath);
|
|
664
|
+
while (dir !== slotDir && dir.startsWith(`${slotDir}${path.sep}`)) {
|
|
665
|
+
const layoutPath = findFile(dir, "layout", matcher);
|
|
666
|
+
if (layoutPath) layouts.unshift(layoutPath);
|
|
667
|
+
dir = path.dirname(dir);
|
|
668
|
+
}
|
|
669
|
+
return layouts;
|
|
670
|
+
}
|
|
671
|
+
function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
|
|
672
|
+
return layoutPaths.map((layoutPath) => {
|
|
673
|
+
const relativeDir = path.relative(slotDir, path.dirname(layoutPath));
|
|
674
|
+
return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
577
677
|
/**
|
|
578
678
|
* Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
|
|
579
679
|
* `[[...slug]]` directory that contains a `page` file. Returns the absolute
|
|
@@ -604,17 +704,29 @@ function findCatchAllPage(dir, matcher) {
|
|
|
604
704
|
}
|
|
605
705
|
/**
|
|
606
706
|
* Convert a file path relative to app/ into an AppRoute.
|
|
707
|
+
*
|
|
708
|
+
* `file` and `appDir` must be forward-slash. `file` comes from
|
|
709
|
+
* `scanWithExtensions` (already forward-slash) and is joined onto `appDir` with
|
|
710
|
+
* `path.posix.join` to form the page/route path, so a native input would
|
|
711
|
+
* produce a mixed separator on Windows.
|
|
607
712
|
*/
|
|
608
713
|
function fileToAppRoute(file, appDir, type, matcher) {
|
|
609
|
-
let dir = path.dirname(file);
|
|
610
|
-
if (type === "page" && dir !== "." && path.basename(dir) === "@children") {
|
|
611
|
-
const parent = path.dirname(dir);
|
|
714
|
+
let dir = path.posix.dirname(file);
|
|
715
|
+
if (type === "page" && dir !== "." && path.posix.basename(dir) === "@children") {
|
|
716
|
+
const parent = path.posix.dirname(dir);
|
|
612
717
|
dir = parent === "" || parent === "." ? "." : parent;
|
|
613
718
|
}
|
|
614
|
-
return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
|
|
719
|
+
return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.posix.join(appDir, file) : null, type === "route" ? path.posix.join(appDir, file) : null);
|
|
615
720
|
}
|
|
616
|
-
|
|
617
|
-
|
|
721
|
+
/**
|
|
722
|
+
* `dir`, `appDir`, `pagePath`, and `routePath` must all be forward-slash. `dir`
|
|
723
|
+
* is split on `path.posix.sep` and joined onto `appDir` with `path.posix.join`.
|
|
724
|
+
* `appDir` is threaded to the layout/slot/boundary discovery below, which builds
|
|
725
|
+
* paths the same way. `pagePath` and `routePath` are stored on the route node as
|
|
726
|
+
* canonical ids that get compared and re-joined downstream.
|
|
727
|
+
*/
|
|
728
|
+
function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeNestedOnlySlots = false) {
|
|
729
|
+
const segments = dir === "." ? [] : dir.split("/");
|
|
618
730
|
const params = [];
|
|
619
731
|
let isDynamic = false;
|
|
620
732
|
const convertedRoute = convertSegmentsToRouteParts(segments);
|
|
@@ -631,7 +743,8 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
|
|
|
631
743
|
const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
|
|
632
744
|
const errorPaths = errorEntries.map((entry) => entry.path);
|
|
633
745
|
const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
|
|
634
|
-
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
746
|
+
const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
|
|
747
|
+
const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
|
|
635
748
|
const loadingPath = findFile(routeDir, "loading", matcher);
|
|
636
749
|
const errorPath = findFile(routeDir, "error", matcher);
|
|
637
750
|
const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
|
|
@@ -640,11 +753,11 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
|
|
|
640
753
|
const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
|
|
641
754
|
const forbiddenPaths = discoverBoundaryFilePerLayout(layouts, "forbidden", matcher);
|
|
642
755
|
const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
|
|
643
|
-
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
|
|
756
|
+
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots);
|
|
644
757
|
return {
|
|
645
758
|
ids: createAppRouteSemanticIds({
|
|
646
759
|
pattern: pattern === "/" ? "/" : pattern,
|
|
647
|
-
pagePath,
|
|
760
|
+
pagePath: effectivePagePath,
|
|
648
761
|
routePath,
|
|
649
762
|
routeSegments: segments,
|
|
650
763
|
layoutTreePositions,
|
|
@@ -652,7 +765,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
|
|
|
652
765
|
slots: parallelSlots
|
|
653
766
|
}),
|
|
654
767
|
pattern: pattern === "/" ? "/" : pattern,
|
|
655
|
-
pagePath,
|
|
768
|
+
pagePath: effectivePagePath,
|
|
656
769
|
routePath,
|
|
657
770
|
layouts,
|
|
658
771
|
templates,
|
|
@@ -863,8 +976,12 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
|
|
|
863
976
|
* Walk from appDir through each segment to the route's directory. At each level
|
|
864
977
|
* that has @slot dirs, collect them. Slots at the route's own directory level
|
|
865
978
|
* use page.tsx; slots at ancestor levels use default.tsx only.
|
|
979
|
+
*
|
|
980
|
+
* `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
|
|
981
|
+
* `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
|
|
982
|
+
* below only matches when both share the canonical separator.
|
|
866
983
|
*/
|
|
867
|
-
function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
984
|
+
function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots = false) {
|
|
868
985
|
const slotMap = /* @__PURE__ */ new Map();
|
|
869
986
|
let currentDir = appDir;
|
|
870
987
|
const dirsToCheck = [];
|
|
@@ -875,7 +992,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
875
992
|
segmentIndex: 0
|
|
876
993
|
});
|
|
877
994
|
for (let i = 0; i < segments.length; i++) {
|
|
878
|
-
currentDir = path.join(currentDir, segments[i]);
|
|
995
|
+
currentDir = path.posix.join(currentDir, segments[i]);
|
|
879
996
|
if (findFile(currentDir, "layout", matcher)) layoutIdx++;
|
|
880
997
|
dirsToCheck.push({
|
|
881
998
|
dir: currentDir,
|
|
@@ -887,9 +1004,9 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
887
1004
|
for (const { dir, layoutIdx: lvlLayoutIdx, segmentIndex } of dirsToCheck) {
|
|
888
1005
|
if (lvlLayoutIdx < 0 && routeHasLayout) continue;
|
|
889
1006
|
const slotLayoutIdx = Math.max(lvlLayoutIdx, 0);
|
|
890
|
-
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
|
|
891
1007
|
const segmentsBelow = segments.slice(segmentIndex);
|
|
892
1008
|
const isActiveUrlLevel = dir === routeDir || segmentsBelow.every(isInvisibleSegment);
|
|
1009
|
+
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots && isActiveUrlLevel);
|
|
893
1010
|
for (const slot of slotsAtLevel) if (isActiveUrlLevel) {
|
|
894
1011
|
slot.layoutIndex = slotLayoutIdx;
|
|
895
1012
|
slotMap.set(slot.key, slot);
|
|
@@ -902,9 +1019,12 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
902
1019
|
slotPatternParts = [...ownerUrl?.urlSegments ?? [], ...mirror.slotUrlSegments];
|
|
903
1020
|
slotParamNames = [...ownerUrl?.params ?? [], ...mirror.slotParamNames];
|
|
904
1021
|
}
|
|
1022
|
+
const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, mirror?.pagePath ?? null, matcher);
|
|
905
1023
|
const inheritedSlot = {
|
|
906
1024
|
...slot,
|
|
907
1025
|
pagePath: mirror?.pagePath ?? null,
|
|
1026
|
+
configLayoutPaths,
|
|
1027
|
+
configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
|
|
908
1028
|
layoutIndex: slotLayoutIdx,
|
|
909
1029
|
routeSegments: mirror?.segments ?? null,
|
|
910
1030
|
slotPatternParts,
|
|
@@ -1035,6 +1155,9 @@ function patternsStructurallyEquivalent(a, b) {
|
|
|
1035
1155
|
*
|
|
1036
1156
|
* Returns the absolute page path, or null if no root-level page is found.
|
|
1037
1157
|
*
|
|
1158
|
+
* `slotDir` must be forward-slash: the `path.posix.join` descent stays a
|
|
1159
|
+
* canonical id only when the base already is.
|
|
1160
|
+
*
|
|
1038
1161
|
* Only descends into route-group directories (those whose name starts with `(`
|
|
1039
1162
|
* and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
|
|
1040
1163
|
* are not transparent and are therefore not searched.
|
|
@@ -1042,42 +1165,44 @@ function patternsStructurallyEquivalent(a, b) {
|
|
|
1042
1165
|
function findSlotRootPage(slotDir, matcher) {
|
|
1043
1166
|
const directPage = findFile(slotDir, "page", matcher);
|
|
1044
1167
|
if (directPage) return directPage;
|
|
1045
|
-
|
|
1046
|
-
try {
|
|
1047
|
-
entries = fs.readdirSync(slotDir, { withFileTypes: true });
|
|
1048
|
-
} catch {
|
|
1049
|
-
return null;
|
|
1050
|
-
}
|
|
1168
|
+
const entries = readDirEntriesCached(slotDir, matcher);
|
|
1051
1169
|
for (const entry of entries) {
|
|
1052
1170
|
if (!entry.isDirectory()) continue;
|
|
1053
1171
|
if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
|
|
1054
|
-
const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
|
|
1172
|
+
const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
|
|
1055
1173
|
if (found) return found;
|
|
1056
1174
|
}
|
|
1057
1175
|
return null;
|
|
1058
1176
|
}
|
|
1059
1177
|
/**
|
|
1060
1178
|
* Discover parallel route slots (@team, @analytics, etc.) in a directory.
|
|
1061
|
-
* Returns a ParallelSlot for each @-prefixed subdirectory that has a page
|
|
1179
|
+
* Returns a ParallelSlot for each @-prefixed subdirectory that has a page,
|
|
1180
|
+
* default component, intercepting route, or nested page-backed sub-route.
|
|
1181
|
+
*
|
|
1182
|
+
* `dir` and `appDir` must be forward-slash. The slot directory is built from
|
|
1183
|
+
* `dir` with `path.posix.join`, and the owner segments and slot key come from
|
|
1184
|
+
* `path.posix.relative(appDir, …)`, which only yields a forward-slash relative
|
|
1185
|
+
* path when both operands already are.
|
|
1062
1186
|
*/
|
|
1063
|
-
function discoverParallelSlots(dir, appDir, matcher) {
|
|
1064
|
-
|
|
1065
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1187
|
+
function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
|
|
1188
|
+
const entries = readDirEntriesCached(dir, matcher);
|
|
1066
1189
|
const slots = [];
|
|
1067
1190
|
for (const entry of entries) {
|
|
1068
1191
|
if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
|
|
1069
1192
|
if (entry.name === "@children") continue;
|
|
1070
1193
|
const slotName = entry.name.slice(1);
|
|
1071
|
-
const slotDir = path.join(dir, entry.name);
|
|
1194
|
+
const slotDir = path.posix.join(dir, entry.name);
|
|
1072
1195
|
const pagePath = findSlotRootPage(slotDir, matcher);
|
|
1073
1196
|
const defaultPath = findFile(slotDir, "default", matcher);
|
|
1074
1197
|
const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
|
|
1075
|
-
|
|
1076
|
-
|
|
1198
|
+
const hasNestedPages = includeNestedOnlySlots && findSlotSubPages(slotDir, matcher).length > 0;
|
|
1199
|
+
if (!pagePath && !defaultPath && interceptingRoutes.length === 0 && !hasNestedPages) continue;
|
|
1200
|
+
const ownerSegments = path.posix.relative(appDir, dir).split("/").filter((segment) => segment.length > 0);
|
|
1077
1201
|
const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
|
|
1202
|
+
const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
|
|
1078
1203
|
slots.push({
|
|
1079
1204
|
id: createAppRouteGraphSlotId(slotName, ownerTreePath),
|
|
1080
|
-
key: `${slotName}@${path.relative(appDir, slotDir)
|
|
1205
|
+
key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
|
|
1081
1206
|
name: slotName,
|
|
1082
1207
|
ownerDir: slotDir,
|
|
1083
1208
|
ownerTreePath,
|
|
@@ -1085,6 +1210,8 @@ function discoverParallelSlots(dir, appDir, matcher) {
|
|
|
1085
1210
|
pagePath,
|
|
1086
1211
|
defaultPath,
|
|
1087
1212
|
layoutPath: findFile(slotDir, "layout", matcher),
|
|
1213
|
+
configLayoutPaths,
|
|
1214
|
+
configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
|
|
1088
1215
|
loadingPath: findFile(slotDir, "loading", matcher),
|
|
1089
1216
|
errorPath: findFile(slotDir, "error", matcher),
|
|
1090
1217
|
interceptingRoutes,
|
|
@@ -1134,6 +1261,10 @@ function isInterceptionMarkerDir(name) {
|
|
|
1134
1261
|
* Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.
|
|
1135
1262
|
* They intercept navigation to another route and render within the slot instead.
|
|
1136
1263
|
*
|
|
1264
|
+
* `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
|
|
1265
|
+
* down to `path.posix.join` and `path.posix.relative` when building the
|
|
1266
|
+
* intercept page paths and target patterns.
|
|
1267
|
+
*
|
|
1137
1268
|
* @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
|
|
1138
1269
|
* @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
|
|
1139
1270
|
* @param appDir - The root app directory
|
|
@@ -1237,6 +1368,10 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
|
|
|
1237
1368
|
/**
|
|
1238
1369
|
* Recursively scan a directory tree for page.tsx files that are inside
|
|
1239
1370
|
* intercepting route directories.
|
|
1371
|
+
*
|
|
1372
|
+
* `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
|
|
1373
|
+
* descends with `path.posix.join` and all three reach the `path.posix.join` /
|
|
1374
|
+
* `path.posix.relative` calls that build the intercept page paths and patterns.
|
|
1240
1375
|
*/
|
|
1241
1376
|
function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
|
|
1242
1377
|
if (!fs.existsSync(currentDir)) return;
|
|
@@ -1245,11 +1380,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
|
|
|
1245
1380
|
if (!entry.isDirectory()) continue;
|
|
1246
1381
|
if (entry.name.startsWith("_")) continue;
|
|
1247
1382
|
const interceptMatch = matchInterceptConvention(entry.name);
|
|
1383
|
+
const interceptDir = path.posix.join(currentDir, entry.name);
|
|
1248
1384
|
if (interceptMatch) {
|
|
1249
1385
|
const restOfName = entry.name.slice(interceptMatch.prefix.length);
|
|
1250
|
-
const interceptDir = path.join(currentDir, entry.name);
|
|
1251
1386
|
collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
|
|
1252
|
-
} else scanForInterceptingPages(
|
|
1387
|
+
} else scanForInterceptingPages(interceptDir, routeDir, appDir, results, matcher);
|
|
1253
1388
|
}
|
|
1254
1389
|
}
|
|
1255
1390
|
/**
|
|
@@ -1272,8 +1407,12 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
|
|
|
1272
1407
|
if (targetPattern) {
|
|
1273
1408
|
const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
|
|
1274
1409
|
results.push({
|
|
1410
|
+
branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
|
|
1275
1411
|
convention,
|
|
1276
1412
|
layoutPaths: [...layoutPaths],
|
|
1413
|
+
layoutSegments: layoutPaths.map((layoutPath) => {
|
|
1414
|
+
return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
|
|
1415
|
+
}),
|
|
1277
1416
|
targetPattern: targetPattern.pattern,
|
|
1278
1417
|
sourceMatchPattern,
|
|
1279
1418
|
pagePath: page,
|
|
@@ -1407,6 +1546,9 @@ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
|
|
|
1407
1546
|
* registered per-scan cache; otherwise falls back to a direct probe (identical
|
|
1408
1547
|
* result). The `null` "not found" outcome is cached too, so repeated misses on
|
|
1409
1548
|
* shared ancestors cost a single set of `existsSync` calls per scan.
|
|
1549
|
+
*
|
|
1550
|
+
* `dir` must be forward-slash. The returned path comes from `findFileWithExts`,
|
|
1551
|
+
* so it is forward-slash too.
|
|
1410
1552
|
*/
|
|
1411
1553
|
function findFile(dir, name, matcher) {
|
|
1412
1554
|
const cache = findFileProbeCache.get(matcher);
|