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 */ 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 + } + }" + `) + }) }) 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