From a53ecf9e6a88219a8f200c443503b66b9142ca35 Mon Sep 17 00:00:00 2001 From: Anoesj Date: Thu, 7 Aug 2025 12:07:14 +0200 Subject: [PATCH 1/3] fix: remove componentless routes from `routes` in `_RouteFileInfoMap` --- src/codegen/generateRouteFileInfoMap.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/codegen/generateRouteFileInfoMap.ts b/src/codegen/generateRouteFileInfoMap.ts index bb9de4204..60235470c 100644 --- a/src/codegen/generateRouteFileInfoMap.ts +++ b/src/codegen/generateRouteFileInfoMap.ts @@ -64,19 +64,28 @@ function generateRouteFileInfoLines( routeNames: string[] childrenNamedViews: string[] | null }> { - const children = node.children.size > 0 ? node.getChildrenDeepSorted() : null + const deepChildren = + node.children.size > 0 ? node.getChildrenDeepSorted() : null - const childrenNamedViews = children + const deepChildrenNamedViews = deepChildren ? Array.from( new Set( - children.flatMap((child) => Array.from(child.value.components.keys())) + deepChildren.flatMap((child) => + Array.from(child.value.components.keys()) + ) ) ) : null - const routeNames = [node, ...node.getChildrenDeepSorted()] - // an unnamed route cannot be accessed in types - .filter((node): node is TreeNode & { name: string } => !!node.name) + const routeNames: Array> = [ + node, + ...(deepChildren ?? []), + ] + // unnamed routes and routes that don't correspond to certain components cannot be accessed in types + .filter( + (node): node is TreeNode & { name: string } => + node.value.components.size > 0 && !!node.name + ) .map((node) => node.name) // Most of the time we only have one view, but with named views we can have multiple. @@ -86,7 +95,7 @@ function generateRouteFileInfoLines( : Array.from(node.value.components.values()).map((file) => ({ key: relative(rootDir, file).replaceAll('\\', '/'), routeNames, - childrenNamedViews, + childrenNamedViews: deepChildrenNamedViews, })) const childrenRouteInfo = node From 9335f743d3ba2d354d86f744fcec1424fbf5445c Mon Sep 17 00:00:00 2001 From: Anoesj Date: Thu, 7 Aug 2025 16:07:53 +0200 Subject: [PATCH 2/3] test: check `routes` in `_RouteFileInfoMap` does not contain componentless routes --- src/codegen/generateRouteFileInfoMap.spec.ts | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/codegen/generateRouteFileInfoMap.spec.ts b/src/codegen/generateRouteFileInfoMap.spec.ts index 7e49ae055..12f535944 100644 --- a/src/codegen/generateRouteFileInfoMap.spec.ts +++ b/src/codegen/generateRouteFileInfoMap.spec.ts @@ -108,4 +108,65 @@ describe('generateRouteFileInfoMap', () => { }" `) }) + + it('does not contain routes without components', () => { + const tree = new PrefixTree(DEFAULT_OPTIONS) + tree.insert('optional/[[id]]', 'optional/[[id]].vue') + tree.insert( + 'optional-repeatable/[[id]]+', + 'optional-repeatable/[[id]]+.vue' + ) + tree.insert('repeatable/[id]+', 'repeatable/[id]+.vue') + + expect(formatExports(generateRouteFileInfoMap(tree, { root: '' }))) + .toMatchInlineSnapshot(` + "export interface _RouteFileInfoMap { + 'optional/[[id]].vue': { + routes: '/optional/[[id]]' + views: never + } + 'optional-repeatable/[[id]]+.vue': { + routes: '/optional-repeatable/[[id]]+' + views: never + } + 'repeatable/[id]+.vue': { + routes: '/repeatable/[id]+' + views: never + } + }" + `) + }) + + it('does not contain nested routes without components', () => { + const tree = new PrefixTree(DEFAULT_OPTIONS) + tree.insert('parent', 'parent.vue') + tree.insert('parent/optional/[[id]]', 'parent/optional/[[id]].vue') + tree.insert( + 'parent/optional-repeatable/[[id]]+', + 'parent/optional-repeatable/[[id]]+.vue' + ) + tree.insert('parent/repeatable/[id]+', 'parent/repeatable/[id]+.vue') + + expect(formatExports(generateRouteFileInfoMap(tree, { root: '' }))) + .toMatchInlineSnapshot(` + "export interface _RouteFileInfoMap { + 'parent.vue': { + routes: '/parent' | '/parent/optional/[[id]]' | '/parent/optional-repeatable/[[id]]+' | '/parent/repeatable/[id]+' + views: 'default' + } + 'parent/optional/[[id]].vue': { + routes: '/parent/optional/[[id]]' + views: never + } + 'parent/optional-repeatable/[[id]]+.vue': { + routes: '/parent/optional-repeatable/[[id]]+' + views: never + } + 'parent/repeatable/[id]+.vue': { + routes: '/parent/repeatable/[id]+' + views: never + } + }" + `) + }) }) From 0c20a465a80a2a6caf164b7afacf90a33d9cb2c5 Mon Sep 17 00:00:00 2001 From: Anoesj Date: Thu, 7 Aug 2025 16:08:31 +0200 Subject: [PATCH 3/3] chore: improve jsdoc comment --- src/codegen/generateDTS.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/generateDTS.ts b/src/codegen/generateDTS.ts index 09a6e2b8c..b9fd1eb7b 100644 --- a/src/codegen/generateDTS.ts +++ b/src/codegen/generateDTS.ts @@ -49,7 +49,7 @@ ${normalizeLines(routeNamedMap)} /** * Route file to route info map by unplugin-vue-router. - * Used by the volar plugin to automatically type useRoute() + * Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`. * * Each key is a file path relative to the project root with 2 properties: * - routes: union of route names of the possible routes when in this page (passed to useRoute<...>()) @@ -61,7 +61,7 @@ ${normalizeLines(routeFileInfoMap)} /** * Get a union of possible route names in a certain route component file. - * Used by the volar plugin to automatically type useRoute() + * Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`. * * @internal */