Skip to content

Commit 332a89c

Browse files
committed
fix(experimental): reuse parent path for index routes
1 parent 1bbc50d commit 332a89c

File tree

4 files changed

+115
-7
lines changed

4 files changed

+115
-7
lines changed

src/codegen/generateRouteResolver.spec.ts

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,101 @@ describe('generateRouteResolver', () => {
320320
`)
321321
})
322322

323+
it('reuses parent path matcher when possible', () => {
324+
const tree = new PrefixTree(DEFAULT_OPTIONS)
325+
const importsMap = new ImportsMap()
326+
tree.insert('a', 'a.vue')
327+
tree.insert('a/index', 'a/index.vue')
328+
tree.insert('a/about', 'a/about.vue')
329+
330+
// with a param
331+
tree.insert('p/[id]', 'p/[id].vue')
332+
tree.insert('p/[id]/index', 'p/[id]/index.vue')
333+
tree.insert('p/[id]/details', 'p/[id]/details.vue')
334+
335+
const resolver = generateRouteResolver(
336+
tree,
337+
DEFAULT_OPTIONS,
338+
importsMap,
339+
new Map()
340+
)
341+
342+
expect(resolver).toMatchInlineSnapshot(`
343+
"
344+
const __route_0 = normalizeRouteRecord({
345+
name: '/a',
346+
path: new MatcherPatternPathStatic('/a'),
347+
components: {
348+
'default': () => import('a.vue')
349+
},
350+
})
351+
const __route_1 = normalizeRouteRecord({
352+
name: '/a/',
353+
path: __route_0.path,
354+
components: {
355+
'default': () => import('a/index.vue')
356+
},
357+
parent: __route_0,
358+
})
359+
const __route_2 = normalizeRouteRecord({
360+
name: '/a/about',
361+
path: new MatcherPatternPathStatic('/a/about'),
362+
components: {
363+
'default': () => import('a/about.vue')
364+
},
365+
parent: __route_0,
366+
})
367+
368+
const __route_3 = normalizeRouteRecord({
369+
name: '/p/[id]',
370+
path: new MatcherPatternPathDynamic(
371+
/^\\/p\\/([^/]+?)$/i,
372+
{
373+
id: [/* no parser */],
374+
},
375+
["p",1],
376+
/* trailingSlash */
377+
),
378+
components: {
379+
'default': () => import('p/[id].vue')
380+
},
381+
})
382+
const __route_4 = normalizeRouteRecord({
383+
name: '/p/[id]/',
384+
path: __route_3.path,
385+
components: {
386+
'default': () => import('p/[id]/index.vue')
387+
},
388+
parent: __route_3,
389+
})
390+
const __route_5 = normalizeRouteRecord({
391+
name: '/p/[id]/details',
392+
path: new MatcherPatternPathDynamic(
393+
/^\\/p\\/([^/]+?)\\/details$/i,
394+
{
395+
id: [/* no parser */],
396+
},
397+
["p",1,"details"],
398+
/* trailingSlash */
399+
),
400+
components: {
401+
'default': () => import('p/[id]/details.vue')
402+
},
403+
parent: __route_3,
404+
})
405+
406+
export const resolver = createFixedResolver([
407+
__route_1, // /a
408+
__route_2, // /a/about
409+
__route_4, // /p/:id
410+
__route_5, // /p/:id/details
411+
__route_3, // /p/:id
412+
__route_0, // /a
413+
])
414+
"
415+
`)
416+
})
417+
323418
it('generates correct nested layouts', () => {
324419
const tree = new PrefixTree(DEFAULT_OPTIONS)
325420
const importsMap = new ImportsMap()
@@ -354,7 +449,7 @@ describe('generateRouteResolver', () => {
354449
})
355450
const __route_1 = normalizeRouteRecord({
356451
name: '/a/(a-home)',
357-
path: new MatcherPatternPathStatic('/a'),
452+
path: __route_0.path,
358453
components: {
359454
'default': () => import('a/(a-home).vue')
360455
},
@@ -370,7 +465,7 @@ describe('generateRouteResolver', () => {
370465
})
371466
const __route_3 = normalizeRouteRecord({
372467
name: '/a/b/(b-home)',
373-
path: new MatcherPatternPathStatic('/a/b'),
468+
path: __route_2.path,
374469
components: {
375470
'default': () => import('a/b/(b-home).vue')
376471
},

src/codegen/generateRouteResolver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export function generateRouteRecord({
177177
})
178178
const routeRecordObject = `{
179179
${recordName}
180-
${generateRouteRecordPath({ node, importsMap, paramParsersMap })}${
180+
${generateRouteRecordPath({ node, importsMap, paramParsersMap, parentVar })}${
181181
queryProperty ? `\n ${queryProperty}` : ''
182182
}${formatMeta(node, ' ')}
183183
${recordComponents}${parentVar ? `\n parent: ${parentVar},` : ''}
@@ -248,14 +248,25 @@ export function generateRouteRecordPath({
248248
node,
249249
importsMap,
250250
paramParsersMap,
251+
parentVar,
251252
}: {
252253
node: TreeNode
253254
importsMap: ImportsMap
254255
paramParsersMap: ParamParsersMap
256+
parentVar?: string | null | undefined
255257
}) {
256258
if (!node.isMatchable()) {
257259
return ''
258260
}
261+
262+
// reuse the parent path matcher if it's exactly the same
263+
// this allows defining index pages and letting the router
264+
// recognize them by just checking the recorde.path === record.parent.path
265+
// it's used for active route matching
266+
if (parentVar && node.regexp === node.parent?.regexp) {
267+
return `path: ${parentVar}.path,`
268+
}
269+
259270
const params = node.pathParams
260271
if (params.length > 0) {
261272
return `path: new MatcherPatternPathDynamic(

src/core/tree.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import type { TreeNodeValue } from './treeNodeValue'
1010
import { CustomRouteBlock } from './customBlock'
1111
import { RouteMeta } from 'vue-router'
12+
import { ESCAPED_TRAILING_SLASH_RE } from './utils'
1213

1314
export interface TreeNodeOptions extends ResolvedOptions {
1415
treeNodeOptions?: TreeNodeValueOptions
@@ -365,13 +366,13 @@ export class TreeNode {
365366
}
366367
}
367368

368-
// TODO: trailingSlash
369369
return (
370370
'/^' +
371371
// Avoid adding a leading slash if the first segment
372372
// is an optional segment that already includes it
373373
(re.startsWith('(?:\\/') ? '' : '\\/') +
374-
re +
374+
// TODO: trailingSlash
375+
re.replace(ESCAPED_TRAILING_SLASH_RE, '') +
375376
'$/i'
376377
)
377378
}

src/core/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ export function throttle(fn: () => void, wait: number, initialWait: number) {
106106
}
107107
}
108108

109-
const LEADING_SLASH_RE = /^\//
110-
const TRAILING_SLASH_RE = /\/$/
109+
export const LEADING_SLASH_RE = /^\//
110+
export const TRAILING_SLASH_RE = /\/$/
111+
export const ESCAPED_TRAILING_SLASH_RE = /\\\/$/
111112
export function joinPath(...paths: string[]): string {
112113
let result = ''
113114
for (const path of paths) {

0 commit comments

Comments
 (0)