Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add route grouping #549

Merged
merged 19 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ function _test() {
<li>
<RouterLink to="/">Home</RouterLink>
</li>
<li>
<RouterLink to="/group">Group (thing.vue)</RouterLink>
</li>
<li>
<RouterLink to="/users/2" v-slot="{ href }">{{ href }}</RouterLink>
</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>test group child (resolves to root, treated as static)</h1>
</template>
3 changes: 3 additions & 0 deletions playground/src/pages/(test-group)/test-group-child.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>Test group child (resolves to root)</h1>
</template>
3 changes: 3 additions & 0 deletions playground/src/pages/file(ignored-parentheses).vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>file(ignored-brackets)</h1>
</template>
3 changes: 3 additions & 0 deletions playground/src/pages/group/(thing).vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>(thing).vue - Parentheses are ignored and this file becomes the index</h1>
</template>
3 changes: 3 additions & 0 deletions playground/src/pages/nested-group/(group).vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>(group).vue</h1>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>Nested group deep child (resolves to nested-group)</h1>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>Nested group first level child (resolves to nested group)</h1>
</template>
40 changes: 40 additions & 0 deletions src/codegen/generateRouteMap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,42 @@ describe('generateRouteNamedMap', () => {
}"
`)
})

it('ignores folder names in parentheses', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)

tree.insert('(group)/a', 'a.vue')

expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/(group)/a': RouteRecordInfo<'/(group)/a', '/a', Record<never, never>, Record<never, never>>,
}"
`)
})

it('ignores nested folder names in parentheses', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)

tree.insert('(group)/(subgroup)/c', 'c.vue')

expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/(group)/(subgroup)/c': RouteRecordInfo<'/(group)/(subgroup)/c', '/c', Record<never, never>, Record<never, never>>,
}"
`)
})

it('treats files named with parentheses as index inside static folder', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)

tree.insert('folder/(group)', 'folder/(group).vue')

expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/folder/(group)': RouteRecordInfo<'/folder/(group)', '/folder', Record<never, never>, Record<never, never>>,
}"
`)
})
})

/**
Expand All @@ -193,4 +229,8 @@ describe('generateRouteNamedMap', () => {
* /static/...[param].vue -> /static/:param+
* /static/...[[param]].vue -> /static/:param*
* /static/...[[...param]].vue -> /static/:param(.*)*
* /(group)/a.vue -> /a
* /(group)/(subgroup)/c.vue -> /c
* /folder/(group).vue -> /folder
* /(home).vue -> /
*/
22 changes: 22 additions & 0 deletions src/core/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,28 @@ describe('Tree', () => {
expect(child.fullPath).toBe('/a')
})

it('should strip groups from file paths', () => {
posva marked this conversation as resolved.
Show resolved Hide resolved
posva marked this conversation as resolved.
Show resolved Hide resolved
const tree = new PrefixTree(RESOLVED_OPTIONS)
tree.insert('(home)', '(home).vue')
let child = tree.children.get('(home)')!
expect(child).toBeDefined()
expect(child.path).toBe('/')
expect(child.fullPath).toBe('/')
})

it('strips groups in folders', () => {
const tree = new PrefixTree(RESOLVED_OPTIONS)
tree.insert('nested-group/(nested-group)', 'nested-group/(nested-group)')

const rootNode = tree.children.get('nested-group')!
expect(rootNode).toBeDefined()
expect(rootNode.value.path).toBe('/nested-group')

const nestedGroupNode = rootNode.children.get('(nested-group)')!
expect(nestedGroupNode).toBeDefined()
expect(nestedGroupNode.value.path).toBe('/nested-group')
})

describe('dot nesting', () => {
it('transforms dots into nested routes by default', () => {
const tree = new PrefixTree(RESOLVED_OPTIONS)
Expand Down
53 changes: 51 additions & 2 deletions src/core/treeNodeValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { joinPath, mergeRouteRecordOverride } from './utils'

export const enum TreeNodeType {
static,
group,
param,
}

Expand Down Expand Up @@ -90,6 +91,10 @@ class _TreeNodeValueBase {
return this._type === TreeNodeType.static
}

isGroup(): this is TreeNodeValueGroup {
return this._type === TreeNodeType.group
}

get overrides() {
return [...this._overrides.entries()]
.sort(([nameA], [nameB]) =>
Expand Down Expand Up @@ -177,6 +182,21 @@ export class TreeNodeValueStatic extends _TreeNodeValueBase {
}
}

export class TreeNodeValueGroup extends _TreeNodeValueBase {
override _type: TreeNodeType.group = TreeNodeType.group
groupName: string

constructor(
rawSegment: string,
parent: TreeNodeValue | undefined,
pathSegment: string,
groupName: string
) {
super(rawSegment, parent, pathSegment)
this.groupName = groupName
}
}

export interface TreeRouteParam {
paramName: string
modifier: string
Expand All @@ -201,7 +221,10 @@ export class TreeNodeValueParam extends _TreeNodeValueBase {
}
}

export type TreeNodeValue = TreeNodeValueStatic | TreeNodeValueParam
export type TreeNodeValue =
| TreeNodeValueStatic
| TreeNodeValueParam
| TreeNodeValueGroup

export interface TreeNodeValueOptions extends ParseSegmentOptions {
/**
Expand Down Expand Up @@ -231,7 +254,7 @@ function resolveTreeNodeValueOptions(
}

/**
* Creates a new TreeNodeValue based on the segment. The result can be a static segment or a param segment.
* Creates a new TreeNodeValue based on the segment. The result can be a static segment, group segment or a param segment.
*
* @param segment - path segment
* @param parent - parent node
Expand All @@ -249,6 +272,32 @@ export function createTreeNodeValue(
// ensure default options
const options = resolveTreeNodeValueOptions(opts)

// extract the group between parentheses
const openingPar = segment.indexOf('(')

if (options.format === 'file' && openingPar >= 0) {
let groupName: string

const closingPar = segment.lastIndexOf(')')
if (closingPar < 0 || closingPar < openingPar) {
posva marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`Invalid segment: "${segment}"`)
}

groupName = segment.slice(openingPar + 1, closingPar)
const before = segment.slice(0, openingPar)
const after = segment.slice(closingPar + 1)

if (!before && !after) {
// pure group: no contribution to the path
return new TreeNodeValueGroup(segment, parent, '', groupName)
}
console.warn(
`Warning: Invalid group syntax detected in segment "${segment}". Treated as a static path.`
)

return new TreeNodeValueStatic(segment, parent, segment)
}

const [pathSegment, params, subSegments] =
options.format === 'path'
? parseRawPathSegment(segment)
Expand Down