From 879fae63055e44307c89d20320ac8936414445e8 Mon Sep 17 00:00:00 2001
From: Eduardo San Martin Morote
Date: Thu, 31 Jul 2025 12:01:59 +0200
Subject: [PATCH] feat: allow multiple query matchers per route
---
packages/experiments-playground/src/App.vue | 23 +++++++++++++--
.../src/router/index.ts | 29 +++++--------------
.../route-resolver/resolver-static.spec.ts | 22 +++++++-------
.../route-resolver/resolver-static.ts | 14 ++++++---
4 files changed, 51 insertions(+), 37 deletions(-)
diff --git a/packages/experiments-playground/src/App.vue b/packages/experiments-playground/src/App.vue
index 99a0b093..cbd335ec 100644
--- a/packages/experiments-playground/src/App.vue
+++ b/packages/experiments-playground/src/App.vue
@@ -1,10 +1,25 @@
@@ -20,7 +35,7 @@ const url = ref('')
|
Profiles list
-
diff --git a/packages/experiments-playground/src/router/index.ts b/packages/experiments-playground/src/router/index.ts
index f1db8824..0ecadb85 100644
--- a/packages/experiments-playground/src/router/index.ts
+++ b/packages/experiments-playground/src/router/index.ts
@@ -11,6 +11,7 @@ import type {
MatcherPatternQuery,
} from 'vue-router/experimental'
import PageHome from '../pages/(home).vue'
+import type { EmptyParams } from 'vue-router/experimental'
// type ExtractMatcherQueryParams =
// T extends MatcherPatternQuery ? P : never
@@ -34,14 +35,16 @@ const PAGE_QUERY_PATTERN_MATCHER: MatcherPatternQuery<{ page: number }> = {
build: params => ({ page: String(params.page) }),
}
-const QUERY_PATTERN_MATCHER: MatcherPatternQuery<{ q: string }> = {
+const QUERY_PATTERN_MATCHER: MatcherPatternQuery<{ q?: string }> = {
match: query => {
return {
q: typeof query.q === 'string' ? query.q : '',
}
},
- build: params => {
- return { q: params.q || '' }
+ // NOTE: we need either to cast or to add an explicit return type annotation
+ // because of the special meaning of {} in TypeScript.
+ build: (params): { q?: string } => {
+ return params.q ? { q: params.q } : ({} as EmptyParams)
},
}
@@ -72,22 +75,6 @@ const QUERY_PATTERN_MATCHER: MatcherPatternQuery<{ q: string }> = {
// QUERY_PATTERN_MATCHER
// )
-const QUERY_MATCHER_COMBINED: MatcherPatternQuery<{
- page: number
- q: string
-}> = {
- match: query => {
- return {
- ...PAGE_QUERY_PATTERN_MATCHER.match(query),
- ...QUERY_PATTERN_MATCHER.match(query),
- }
- },
- build: params => ({
- ...PAGE_QUERY_PATTERN_MATCHER.build(params),
- ...QUERY_PATTERN_MATCHER.build(params),
- }),
-}
-
const ANY_HASH_PATTERN_MATCHER: MatcherPatternHash/ hash could be named anything, in this case it creates a param named hash
{ hash: string | null }> = {
match: hash => ({ hash: hash ? hash.slice(1) : null }),
@@ -104,7 +91,7 @@ const r_group = normalizeRouteRecord({
const r_home = normalizeRouteRecord({
name: 'home',
path: new MatcherPatternPathStatic('/'),
- query: QUERY_MATCHER_COMBINED,
+ query: [PAGE_QUERY_PATTERN_MATCHER, QUERY_PATTERN_MATCHER],
parent: r_group,
components: { default: PageHome },
})
@@ -124,7 +111,7 @@ const r_profiles_layout = normalizeRouteRecord({
meta: {
layout: 'profile',
},
- query: PAGE_QUERY_PATTERN_MATCHER,
+ query: [PAGE_QUERY_PATTERN_MATCHER],
})
const r_profiles_list = normalizeRouteRecord({
diff --git a/packages/router/src/experimental/route-resolver/resolver-static.spec.ts b/packages/router/src/experimental/route-resolver/resolver-static.spec.ts
index 2ca07dfe..5f7db6b3 100644
--- a/packages/router/src/experimental/route-resolver/resolver-static.spec.ts
+++ b/packages/router/src/experimental/route-resolver/resolver-static.spec.ts
@@ -117,7 +117,7 @@ describe('StaticResolver', () => {
{
name: 'any-path',
path: ANY_PATH_PATTERN_MATCHER,
- query: PAGE_QUERY_PATTERN_MATCHER_LOCAL,
+ query: [PAGE_QUERY_PATTERN_MATCHER_LOCAL],
},
])
@@ -156,7 +156,7 @@ describe('StaticResolver', () => {
{
name: 'user-detail',
path: USER_ID_PATH_PATTERN_MATCHER,
- query: PAGE_QUERY_PATTERN_MATCHER_LOCAL,
+ query: [PAGE_QUERY_PATTERN_MATCHER_LOCAL],
hash: ANY_HASH_PATTERN_MATCHER,
},
])
@@ -317,14 +317,16 @@ describe('StaticResolver', () => {
{
name: 'query',
path: EMPTY_PATH_PATTERN_MATCHER,
- query: {
- match(q) {
- return { q }
- },
- build({ q }) {
- return { ...q }
- },
- } satisfies MatcherPatternQuery<{ q: MatcherQueryParams }>,
+ query: [
+ {
+ match(q) {
+ return { q }
+ },
+ build({ q }) {
+ return { ...q }
+ },
+ } satisfies MatcherPatternQuery<{ q: MatcherQueryParams }>,
+ ],
},
])
expect(resolver.resolve('/?%23%2F%3F=%23%2F%3F')).toMatchObject({
diff --git a/packages/router/src/experimental/route-resolver/resolver-static.ts b/packages/router/src/experimental/route-resolver/resolver-static.ts
index acc10697..4b7068be 100644
--- a/packages/router/src/experimental/route-resolver/resolver-static.ts
+++ b/packages/router/src/experimental/route-resolver/resolver-static.ts
@@ -41,7 +41,7 @@ export interface EXPERIMENTAL_ResolverRecord_Base {
/**
* {@link MatcherPattern} for the query section of the URI.
*/
- query?: MatcherPatternQuery
+ query?: MatcherPatternQuery[]
/**
* {@link MatcherPattern} for the hash section of the URI.
@@ -63,7 +63,9 @@ export interface EXPERIMENTAL_ResolverRecord_Group
extends EXPERIMENTAL_ResolverRecord_Base {
name?: undefined
path?: undefined
- query?: undefined
+ // Query is the only kind of matcher that is non-exclusive
+ // all matched records get their queries merged
+ // query?: undefined
hash?: undefined
}
@@ -190,7 +192,9 @@ export function createStaticResolver<
...currentLocation?.query,
...normalizeQuery(to.query),
},
- ...matched.map(record => record.query?.build(params))
+ ...matched.flatMap(record =>
+ record.query?.map(query => query.build(params))
+ )
)
return {
@@ -233,7 +237,9 @@ export function createStaticResolver<
matched = buildMatched(record)
const queryParams: MatcherQueryParams = Object.assign(
{},
- ...matched.map(record => record.query?.match(url.query))
+ ...matched.flatMap(record =>
+ record.query?.map(query => query.match(url.query))
+ )
)
// TODO: test performance
// for (const record of matched) {
--
2.47.2