Skip to content

Commit 41c00e7

Browse files
committed
fix: avoid importing unused param parsers
1 parent bf0fc9b commit 41c00e7

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

packages/router/src/unplugin/codegen/generateParamParsers.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it, test } from 'vitest'
22
import {
33
warnMissingParamParsers,
44
collectMissingParamParsers,
5+
collectUsedParamParserNames,
56
generateParamParsersTypesDeclarations,
67
generateParamsTypes,
78
generateParamParserOptions,
@@ -158,6 +159,49 @@ describe('collectMissingParamParsers', () => {
158159
})
159160
})
160161

162+
describe('collectUsedParamParserNames', () => {
163+
it('returns an empty set when no route references a parser', () => {
164+
const tree = new PrefixTree(DEFAULT_OPTIONS)
165+
tree.insert('users', 'users.vue')
166+
tree.insert('posts/[id]', 'posts/[id].vue')
167+
168+
expect(collectUsedParamParserNames(tree).size).toBe(0)
169+
})
170+
171+
it('collects parser names from path params', () => {
172+
const tree = new PrefixTree(DEFAULT_OPTIONS)
173+
tree.insert('users/[id=uuid]', 'users/[id=uuid].vue')
174+
tree.insert('posts/[slug=slug]', 'posts/[slug=slug].vue')
175+
176+
expect(collectUsedParamParserNames(tree)).toEqual(new Set(['uuid', 'slug']))
177+
})
178+
179+
it('includes native parser names', () => {
180+
const tree = new PrefixTree(DEFAULT_OPTIONS)
181+
tree.insert('users/[id=int]', 'users/[id=int].vue')
182+
183+
expect(collectUsedParamParserNames(tree)).toEqual(new Set(['int']))
184+
})
185+
186+
it('collects parser names from query params', () => {
187+
const tree = new PrefixTree(DEFAULT_OPTIONS)
188+
const node = tree.insert('search', 'search.vue')
189+
node.setCustomRouteBlock('search.vue', {
190+
params: { query: { id: 'uuid' } },
191+
})
192+
193+
expect(collectUsedParamParserNames(tree)).toEqual(new Set(['uuid']))
194+
})
195+
196+
it('deduplicates parsers referenced multiple times', () => {
197+
const tree = new PrefixTree(DEFAULT_OPTIONS)
198+
tree.insert('users/[id=uuid]', 'users/[id=uuid].vue')
199+
tree.insert('teams/[id=uuid]', 'teams/[id=uuid].vue')
200+
201+
expect(collectUsedParamParserNames(tree)).toEqual(new Set(['uuid']))
202+
})
203+
})
204+
161205
describe('generateParamParsersTypesDeclarations', () => {
162206
it('returns empty string for empty param parsers map', () => {
163207
const paramParsers: ParamParsersMap = new Map()

packages/router/src/unplugin/codegen/generateParamParsers.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ export interface MissingParamParser {
6666
filePaths: string[]
6767
}
6868

69+
/**
70+
* Walks the route tree and returns the set of parser names referenced by any
71+
* path or query param. Native parser names (`int`, `bool`) and references to
72+
* parsers not present on disk are included as-is — callers decide what to do
73+
* with them.
74+
*/
75+
export function collectUsedParamParserNames(tree: PrefixTree): Set<string> {
76+
const used = new Set<string>()
77+
for (const node of tree.getChildrenDeepSorted()) {
78+
for (const param of node.params) {
79+
if (param.parser) {
80+
used.add(param.parser)
81+
}
82+
}
83+
}
84+
return used
85+
}
86+
6987
export function collectMissingParamParsers(
7088
tree: PrefixTree,
7189
paramParsers: ParamParsersMap

packages/router/src/unplugin/codegen/generateRouteResolver.spec.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,4 +1404,97 @@ describe('generateRouteResolver', () => {
14041404
expect(warnings).not.toContain('"/members"')
14051405
})
14061406
})
1407+
1408+
describe('param parser filtering', () => {
1409+
type ParamParserEntry = NonNullable<ReturnType<ParamParsersMap['get']>>
1410+
const uuidEntry = {
1411+
name: 'uuid',
1412+
typeName: 'Param_uuid',
1413+
relativePath: 'parsers/uuid',
1414+
absolutePath: '/abs/parsers/uuid',
1415+
} satisfies ParamParserEntry
1416+
const slugEntry = {
1417+
name: 'slug',
1418+
typeName: 'Param_slug',
1419+
relativePath: 'parsers/slug',
1420+
absolutePath: '/abs/parsers/slug',
1421+
} satisfies ParamParserEntry
1422+
1423+
it('omits imports and normalized declarations for unused parsers', () => {
1424+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1425+
tree.insert('users/[id=uuid]', 'users/[id=uuid].vue')
1426+
1427+
const importsMap = new ImportsMap()
1428+
const paramParsersMap: ParamParsersMap = new Map([
1429+
['uuid', uuidEntry],
1430+
['slug', slugEntry],
1431+
])
1432+
1433+
const resolver = generateRouteResolver(
1434+
tree,
1435+
DEFAULT_OPTIONS,
1436+
importsMap,
1437+
paramParsersMap
1438+
)
1439+
1440+
expect(resolver).toContain('_normalized_PARAM_PARSER__uuid')
1441+
expect(resolver).not.toContain('PARAM_PARSER__slug')
1442+
expect(resolver).not.toContain('_normalized_PARAM_PARSER__slug')
1443+
1444+
const imports = importsMap.toString()
1445+
expect(imports).toContain("from '/abs/parsers/uuid'")
1446+
expect(imports).not.toContain("from '/abs/parsers/slug'")
1447+
})
1448+
1449+
it('omits all parser imports when no route references any parser', () => {
1450+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1451+
tree.insert('users/[id]', 'users/[id].vue')
1452+
1453+
const importsMap = new ImportsMap()
1454+
const paramParsersMap: ParamParsersMap = new Map([
1455+
['uuid', uuidEntry],
1456+
['slug', slugEntry],
1457+
])
1458+
1459+
const resolver = generateRouteResolver(
1460+
tree,
1461+
DEFAULT_OPTIONS,
1462+
importsMap,
1463+
paramParsersMap
1464+
)
1465+
1466+
expect(resolver).not.toContain('_normalizeParamParser')
1467+
expect(resolver).not.toContain('PARAM_PARSER__uuid')
1468+
expect(resolver).not.toContain('PARAM_PARSER__slug')
1469+
1470+
const imports = importsMap.toString()
1471+
expect(imports).not.toContain('_normalizeParamParser')
1472+
expect(imports).not.toContain("from '/abs/parsers/uuid'")
1473+
expect(imports).not.toContain("from '/abs/parsers/slug'")
1474+
})
1475+
1476+
it('detects parsers referenced from query params', () => {
1477+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1478+
const node = tree.insert('search', 'search.vue')
1479+
node.setCustomRouteBlock('search.vue', {
1480+
params: { query: { id: 'uuid' } },
1481+
})
1482+
1483+
const importsMap = new ImportsMap()
1484+
const paramParsersMap: ParamParsersMap = new Map([
1485+
['uuid', uuidEntry],
1486+
['slug', slugEntry],
1487+
])
1488+
1489+
const resolver = generateRouteResolver(
1490+
tree,
1491+
DEFAULT_OPTIONS,
1492+
importsMap,
1493+
paramParsersMap
1494+
)
1495+
1496+
expect(resolver).toContain('_normalized_PARAM_PARSER__uuid')
1497+
expect(resolver).not.toContain('_normalized_PARAM_PARSER__slug')
1498+
})
1499+
})
14071500
})

packages/router/src/unplugin/codegen/generateRouteResolver.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
generatePathParamsOptions,
99
generateParamParserOptions,
1010
generateNormalizedParamParsersDeclarations,
11+
collectUsedParamParserNames,
1112
} from './generateParamParsers'
1213
import { generatePageImport, formatMeta } from './generateRouteRecords'
1314

@@ -78,6 +79,13 @@ export function generateRouteResolver(
7879
importsMap: ImportsMap,
7980
paramParsersMap: ParamParsersMap
8081
): string {
82+
// restrict imports + normalized declarations to parsers actually referenced
83+
// by a route, so unused parser files don't get pulled into the bundle
84+
const usedParserNames = collectUsedParamParserNames(tree)
85+
const usedParamParsersMap: ParamParsersMap = new Map(
86+
Array.from(paramParsersMap).filter(([key]) => usedParserNames.has(key))
87+
)
88+
8189
const state: GenerateRouteResolverState = { id: 0, matchableRecords: [] }
8290
const records = tree.getChildrenSorted().map(node =>
8391
generateRouteRecord({
@@ -87,7 +95,7 @@ export function generateRouteResolver(
8795
state,
8896
options,
8997
importsMap,
90-
paramParsersMap,
98+
paramParsersMap: usedParamParsersMap,
9199
})
92100
)
93101

@@ -97,7 +105,7 @@ export function generateRouteResolver(
97105
importsMap.add('vue-router/experimental', 'normalizeRouteRecord')
98106

99107
const normalizedDeclarations = generateNormalizedParamParsersDeclarations(
100-
paramParsersMap,
108+
usedParamParsersMap,
101109
importsMap
102110
)
103111

0 commit comments

Comments
 (0)