Skip to content

Commit 8fe453c

Browse files
committed
feat(experimental): handle aliasOf in resolvers
1 parent 49b925d commit 8fe453c

File tree

3 files changed

+287
-1
lines changed

3 files changed

+287
-1
lines changed

packages/router/src/experimental/route-resolver/resolver-fixed.spec.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,5 +1218,176 @@ describe('fixed resolver', () => {
12181218
})
12191219
})
12201220
})
1221+
1222+
describe('aliases', () => {
1223+
// A matcher for /people/:id to use as an alias path
1224+
const PEOPLE_ID_PATH_PATTERN_MATCHER: MatcherPatternPath<{
1225+
id: number
1226+
}> = {
1227+
match(value) {
1228+
const match = value.match(/^\/people\/(\d+)$/)
1229+
if (!match?.[1]) miss()
1230+
const id = Number(match[1])
1231+
if (Number.isNaN(id)) miss()
1232+
return { id }
1233+
},
1234+
build({ id }) {
1235+
return `/people/${id}`
1236+
},
1237+
}
1238+
1239+
it('excludes aliasOf records from name map', () => {
1240+
const original = {
1241+
name: 'users',
1242+
path: new MatcherPatternPathStatic('/users'),
1243+
}
1244+
const alias = {
1245+
name: 'users',
1246+
path: new MatcherPatternPathStatic('/people'),
1247+
aliasOf: original,
1248+
}
1249+
const resolver = createFixedResolver([original, alias])
1250+
1251+
expect(resolver.getRoute('users')).toBe(original)
1252+
})
1253+
1254+
it('resolves alias path to the original route name', () => {
1255+
const original = {
1256+
name: 'users',
1257+
path: new MatcherPatternPathStatic('/users'),
1258+
}
1259+
const alias = {
1260+
name: 'users',
1261+
path: new MatcherPatternPathStatic('/people'),
1262+
aliasOf: original,
1263+
}
1264+
const resolver = createFixedResolver([original, alias])
1265+
1266+
expect(resolver.resolve({ path: '/people' })).toMatchObject({
1267+
name: 'users',
1268+
path: '/people',
1269+
})
1270+
})
1271+
1272+
it('resolves by name using original path, not alias', () => {
1273+
const original = {
1274+
name: 'users',
1275+
path: new MatcherPatternPathStatic('/users'),
1276+
}
1277+
const alias = {
1278+
name: 'users',
1279+
path: new MatcherPatternPathStatic('/people'),
1280+
aliasOf: original,
1281+
}
1282+
const resolver = createFixedResolver([original, alias])
1283+
1284+
expect(resolver.resolve({ name: 'users', params: {} })).toMatchObject({
1285+
name: 'users',
1286+
path: '/users',
1287+
})
1288+
})
1289+
1290+
it('original path still resolves normally', () => {
1291+
const original = {
1292+
name: 'users',
1293+
path: new MatcherPatternPathStatic('/users'),
1294+
}
1295+
const alias = {
1296+
name: 'users',
1297+
path: new MatcherPatternPathStatic('/people'),
1298+
aliasOf: original,
1299+
}
1300+
const resolver = createFixedResolver([original, alias])
1301+
1302+
const result = resolver.resolve({ path: '/users' })
1303+
expect(result.name).toBe('users')
1304+
expect(result.matched[0].aliasOf).toBeUndefined()
1305+
})
1306+
1307+
it('matched array entry has aliasOf pointing to original', () => {
1308+
const original = {
1309+
name: 'users',
1310+
path: new MatcherPatternPathStatic('/users'),
1311+
}
1312+
const alias = {
1313+
name: 'users',
1314+
path: new MatcherPatternPathStatic('/people'),
1315+
aliasOf: original,
1316+
}
1317+
const resolver = createFixedResolver([original, alias])
1318+
1319+
const result = resolver.resolve({ path: '/people' })
1320+
expect(result.matched).toHaveLength(1)
1321+
expect(result.matched[0].aliasOf).toBe(original)
1322+
})
1323+
1324+
it('alias with dynamic params resolves correctly', () => {
1325+
const original = {
1326+
name: 'user-detail',
1327+
path: USER_ID_PATH_PATTERN_MATCHER,
1328+
}
1329+
const alias = {
1330+
name: 'user-detail',
1331+
path: PEOPLE_ID_PATH_PATTERN_MATCHER,
1332+
aliasOf: original,
1333+
}
1334+
const resolver = createFixedResolver([original, alias])
1335+
1336+
expect(resolver.resolve({ path: '/people/42' })).toMatchObject({
1337+
name: 'user-detail',
1338+
params: { id: 42 },
1339+
path: '/people/42',
1340+
})
1341+
})
1342+
1343+
it('supports multiple aliases', () => {
1344+
const original = {
1345+
name: 'users',
1346+
path: new MatcherPatternPathStatic('/users'),
1347+
}
1348+
const alias1 = {
1349+
name: 'users',
1350+
path: new MatcherPatternPathStatic('/people'),
1351+
aliasOf: original,
1352+
}
1353+
const alias2 = {
1354+
name: 'users',
1355+
path: new MatcherPatternPathStatic('/members'),
1356+
aliasOf: original,
1357+
}
1358+
const resolver = createFixedResolver([original, alias1, alias2])
1359+
1360+
expect(resolver.resolve({ path: '/people' })).toMatchObject({
1361+
name: 'users',
1362+
})
1363+
expect(resolver.resolve({ path: '/members' })).toMatchObject({
1364+
name: 'users',
1365+
})
1366+
})
1367+
1368+
it('child alias resolves with parent in matched', () => {
1369+
const parent = {
1370+
name: 'parent',
1371+
path: new MatcherPatternPathStatic('/parent'),
1372+
}
1373+
const child = {
1374+
name: 'child',
1375+
path: new MatcherPatternPathStatic('/child'),
1376+
parent,
1377+
}
1378+
const childAlias = {
1379+
name: 'child',
1380+
path: new MatcherPatternPathStatic('/child-alias'),
1381+
parent,
1382+
aliasOf: child,
1383+
}
1384+
const resolver = createFixedResolver([parent, child, childAlias])
1385+
1386+
const result = resolver.resolve({ path: '/child-alias' })
1387+
expect(result.matched).toHaveLength(2)
1388+
expect(result.matched[0]).toBe(parent)
1389+
expect(result.matched[1].aliasOf).toBe(child)
1390+
})
1391+
})
12211392
})
12221393
})

packages/router/src/experimental/route-resolver/resolver-fixed.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ export interface EXPERIMENTAL_ResolverRecord_Base {
5555
* It will be included in the `matched` array of a resolved location.
5656
*/
5757
parent?: EXPERIMENTAL_ResolverRecord | null // the parent can be matchable or not
58+
59+
/**
60+
* If this record is an alias of another record, this points to the original
61+
* record. Alias records are not added to the name map and resolve to the
62+
* original record's name.
63+
*/
64+
aliasOf?: EXPERIMENTAL_ResolverRecord | null
5865
}
5966

6067
/**
@@ -132,9 +139,12 @@ export function createFixedResolver<
132139
TRecord extends EXPERIMENTAL_ResolverRecord_Matchable,
133140
>(records: TRecord[]): EXPERIMENTAL_ResolverFixed<TRecord> {
134141
// allows fast access to a matcher by name
142+
// alias records are excluded so name lookups always return the original
135143
const recordMap = new Map<RecordName, TRecord>()
136144
for (const record of records) {
137-
recordMap.set(record.name, record)
145+
if (!record.aliasOf) {
146+
recordMap.set(record.name, record)
147+
}
138148
}
139149

140150
// NOTE: because of the overloads for `resolve`, we need to manually type the arguments

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,4 +1240,109 @@ describe('generateRouteResolver', () => {
12401240

12411241
expect(resolver).toMatchSnapshot()
12421242
})
1243+
1244+
describe('aliases', () => {
1245+
it('generates alias records for static alias paths', () => {
1246+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1247+
const importsMap = new ImportsMap()
1248+
const node = tree.insert('users', 'users.vue')
1249+
node.setCustomRouteBlock('users.vue', { alias: ['/people'] })
1250+
1251+
const resolver = generateRouteResolver(
1252+
tree,
1253+
DEFAULT_OPTIONS,
1254+
importsMap,
1255+
new Map()
1256+
)
1257+
1258+
expect(resolver).toMatchInlineSnapshot(`
1259+
"
1260+
const __route_0 = normalizeRouteRecord({
1261+
name: '/users',
1262+
path: new MatcherPatternPathStatic('/users'),
1263+
components: {
1264+
'default': () => import('users.vue')
1265+
},
1266+
})
1267+
const __route_1 = normalizeRouteRecord({
1268+
...__route_0,
1269+
path: new MatcherPatternPathStatic('/people'),
1270+
aliasOf: __route_0,
1271+
})
1272+
1273+
export const resolver = createFixedResolver([
1274+
__route_0, // /users
1275+
__route_1, // /people
1276+
])
1277+
"
1278+
`)
1279+
})
1280+
1281+
it('generates alias records for dynamic alias paths', () => {
1282+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1283+
const importsMap = new ImportsMap()
1284+
const node = tree.insert('users/[id]', 'users/[id].vue')
1285+
node.setCustomRouteBlock('users/[id].vue', {
1286+
alias: ['/people/:id'],
1287+
})
1288+
1289+
const resolver = generateRouteResolver(
1290+
tree,
1291+
DEFAULT_OPTIONS,
1292+
importsMap,
1293+
new Map()
1294+
)
1295+
1296+
expect(resolver).toContain('aliasOf: __route_0')
1297+
expect(resolver).toContain('MatcherPatternPathDynamic')
1298+
expect(resolver).toContain('/people/')
1299+
})
1300+
1301+
it('generates multiple alias records', () => {
1302+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1303+
const importsMap = new ImportsMap()
1304+
const node = tree.insert('users', 'users.vue')
1305+
node.setCustomRouteBlock('users.vue', {
1306+
alias: ['/people', '/members'],
1307+
})
1308+
1309+
const resolver = generateRouteResolver(
1310+
tree,
1311+
DEFAULT_OPTIONS,
1312+
importsMap,
1313+
new Map()
1314+
)
1315+
1316+
expect(resolver).toContain('aliasOf: __route_0')
1317+
expect(resolver).toContain(
1318+
"path: new MatcherPatternPathStatic('/people')"
1319+
)
1320+
expect(resolver).toContain(
1321+
"path: new MatcherPatternPathStatic('/members')"
1322+
)
1323+
// original + 2 aliases = 3 route entries in the resolver
1324+
const routeEntries = resolver
1325+
.split('\n')
1326+
.filter(l => l.trim().startsWith('__route_'))
1327+
expect(routeEntries).toHaveLength(3)
1328+
})
1329+
1330+
it('alias records are included in the resolver array', () => {
1331+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1332+
const importsMap = new ImportsMap()
1333+
const node = tree.insert('users', 'users.vue')
1334+
node.setCustomRouteBlock('users.vue', { alias: ['/people'] })
1335+
1336+
const resolver = generateRouteResolver(
1337+
tree,
1338+
DEFAULT_OPTIONS,
1339+
importsMap,
1340+
new Map()
1341+
)
1342+
1343+
// Both records should be in the createFixedResolver array
1344+
expect(resolver).toMatch(/createFixedResolver\(\[[\s\S]*__route_0/)
1345+
expect(resolver).toMatch(/createFixedResolver\(\[[\s\S]*__route_1/)
1346+
})
1347+
})
12431348
})

0 commit comments

Comments
 (0)