Skip to content

Commit be657db

Browse files
committed
feat: pluggable matcher
1 parent 8618943 commit be657db

File tree

4 files changed

+130
-41
lines changed

4 files changed

+130
-41
lines changed

packages/router/src/devtools.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import {
1010
import { watch } from 'vue'
1111
import { decode } from './encoding'
1212
import { isSameRouteRecord } from './location'
13-
import { RouterMatcher } from './matcher'
13+
import { GenericRouterMatcher } from './matcher'
1414
import { RouteRecordMatcher } from './matcher/pathMatcher'
1515
import { PathParser } from './matcher/pathParserRanker'
16-
import { Router } from './router'
16+
import { GenericRouter } from './router'
1717
import { UseLinkDevtoolsContext } from './RouterLink'
1818
import { RouterViewDevtoolsContext } from './RouterView'
1919
import { RouteLocationNormalized } from './types'
@@ -59,7 +59,11 @@ function formatDisplay(display: string) {
5959
// to support multiple router instances
6060
let routerId = 0
6161

62-
export function addDevtools(app: App, router: Router, matcher: RouterMatcher) {
62+
export function addDevtools<RC>(
63+
app: App,
64+
router: GenericRouter<RC>,
65+
matcher: GenericRouterMatcher<RC>
66+
) {
6367
// Take over router.beforeEach and afterEach
6468

6569
// make sure we are not registering the devtool twice

packages/router/src/index.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export { createWebHistory } from './history/html5'
22
export { createMemoryHistory } from './history/memory'
33
export { createWebHashHistory } from './history/hash'
44
export { createRouterMatcher } from './matcher'
5-
export type { RouterMatcher } from './matcher'
5+
export type { GenericRouterMatcher, RouterMatcher } from './matcher'
66

77
export { parseQuery, stringifyQuery } from './query'
88
export type {
@@ -67,8 +67,18 @@ export type {
6767
NavigationHookAfter,
6868
} from './types'
6969

70-
export { createRouter } from './router'
71-
export type { Router, RouterOptions, RouterScrollBehavior } from './router'
70+
export {
71+
createRouter,
72+
createRouterWithMatcher,
73+
createDefaultMatcher,
74+
} from './router'
75+
export type {
76+
Router,
77+
RouterWithMatcher,
78+
RouterOptions,
79+
RouterWithMatcherOptions,
80+
RouterScrollBehavior,
81+
} from './router'
7282

7383
export { NavigationFailureType, isNavigationFailure } from './errors'
7484
export type {

packages/router/src/matcher/index.ts

+42-11
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import { warn } from '../warning'
2222
import { assign, noop } from '../utils'
2323

2424
/**
25-
* Internal RouterMatcher
25+
* Internal GenericRouterMatcher
2626
*
2727
* @internal
2828
*/
29-
export interface RouterMatcher {
30-
addRoute: (record: RouteRecordRaw, parent?: RouteRecordMatcher) => () => void
29+
export interface GenericRouterMatcher<RC> {
30+
addRoute: (record: RC, parent?: RouteRecordMatcher) => () => void
3131
removeRoute: {
3232
(matcher: RouteRecordMatcher): void
3333
(name: RouteRecordName): void
@@ -47,6 +47,18 @@ export interface RouterMatcher {
4747
) => MatcherLocation
4848
}
4949

50+
/**
51+
* Internal RouterMatcher
52+
*
53+
* @internal
54+
*/
55+
export interface RouterMatcher extends GenericRouterMatcher<RouteRecordRaw> {}
56+
57+
// TODO: Should this be considered internal?
58+
export interface CloneableRouterMatcher extends RouterMatcher {
59+
clone: () => CloneableRouterMatcher
60+
}
61+
5062
/**
5163
* Creates a Router Matcher.
5264
*
@@ -57,15 +69,37 @@ export interface RouterMatcher {
5769
export function createRouterMatcher(
5870
routes: Readonly<RouteRecordRaw[]>,
5971
globalOptions: PathParserOptions
60-
): RouterMatcher {
61-
// normalized ordered array of matchers
62-
const matchers: RouteRecordMatcher[] = []
63-
const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
72+
): CloneableRouterMatcher {
6473
globalOptions = mergeOptions(
6574
{ strict: false, end: true, sensitive: false } as PathParserOptions,
6675
globalOptions
6776
)
6877

78+
const matcher = createRouterMatcherInternal(
79+
globalOptions,
80+
[],
81+
new Map<RouteRecordName, RouteRecordMatcher>()
82+
)
83+
84+
// add initial routes
85+
routes.forEach(route => matcher.addRoute(route))
86+
87+
return matcher
88+
}
89+
90+
function createRouterMatcherInternal(
91+
globalOptions: PathParserOptions,
92+
matchers: RouteRecordMatcher[],
93+
matcherMap: Map<RouteRecordName, RouteRecordMatcher>
94+
): CloneableRouterMatcher {
95+
function clone() {
96+
return createRouterMatcherInternal(
97+
globalOptions,
98+
[...matchers],
99+
new Map(matcherMap)
100+
)
101+
}
102+
69103
function getRecordMatcher(name: RouteRecordName) {
70104
return matcherMap.get(name)
71105
}
@@ -350,10 +384,7 @@ export function createRouterMatcher(
350384
}
351385
}
352386

353-
// add initial routes
354-
routes.forEach(route => addRoute(route))
355-
356-
return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
387+
return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher, clone }
357388
}
358389

359390
function paramsFromLocation(

packages/router/src/router.ts

+68-24
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ import {
2525
scrollToPosition,
2626
_ScrollPositionNormalized,
2727
} from './scrollBehavior'
28-
import { createRouterMatcher, PathParserOptions } from './matcher'
28+
import {
29+
CloneableRouterMatcher,
30+
createRouterMatcher,
31+
GenericRouterMatcher,
32+
PathParserOptions,
33+
} from './matcher'
2934
import {
3035
createRouterError,
3136
ErrorTypes,
@@ -97,10 +102,11 @@ export interface RouterScrollBehavior {
97102
): Awaitable<ScrollPosition | false | void>
98103
}
99104

100-
/**
101-
* Options to initialize a {@link Router} instance.
102-
*/
103-
export interface RouterOptions extends PathParserOptions {
105+
export interface DefaultMatcherOptions extends PathParserOptions {
106+
routes: Readonly<RouteRecordRaw[]>
107+
}
108+
109+
export interface SharedOptions {
104110
/**
105111
* History implementation used by the router. Most web applications should use
106112
* `createWebHistory` but it requires the server to be properly configured.
@@ -117,10 +123,6 @@ export interface RouterOptions extends PathParserOptions {
117123
* ```
118124
*/
119125
history: RouterHistory
120-
/**
121-
* Initial list of routes that should be added to the router.
122-
*/
123-
routes: Readonly<RouteRecordRaw[]>
124126
/**
125127
* Function to control scrolling when navigating between pages. Can return a
126128
* Promise to delay scrolling. Check {@link ScrollBehavior}.
@@ -174,10 +176,24 @@ export interface RouterOptions extends PathParserOptions {
174176
// linkInactiveClass?: string
175177
}
176178

179+
/**
180+
* Options to initialize a {@link Router} instance.
181+
*/
182+
export interface RouterOptions extends SharedOptions, PathParserOptions {
183+
/**
184+
* Initial list of routes that should be added to the router.
185+
*/
186+
routes: Readonly<RouteRecordRaw[]>
187+
}
188+
189+
export interface RouterWithMatcherOptions<RC> extends SharedOptions {
190+
matcher: GenericRouterMatcher<RC>
191+
}
192+
177193
/**
178194
* Router instance.
179195
*/
180-
export interface Router {
196+
export interface GenericRouter<RC> {
181197
/**
182198
* @internal
183199
*/
@@ -189,7 +205,7 @@ export interface Router {
189205
/**
190206
* Original options object passed to create the Router
191207
*/
192-
readonly options: RouterOptions
208+
readonly options: SharedOptions
193209

194210
/**
195211
* Allows turning off the listening of history events. This is a low level api for micro-frontends.
@@ -202,13 +218,13 @@ export interface Router {
202218
* @param parentName - Parent Route Record where `route` should be appended at
203219
* @param route - Route Record to add
204220
*/
205-
addRoute(parentName: RouteRecordName, route: RouteRecordRaw): () => void
221+
addRoute(parentName: RouteRecordName, route: RC): () => void
206222
/**
207223
* Add a new {@link RouteRecordRaw | route record} to the router.
208224
*
209225
* @param route - Route Record to add
210226
*/
211-
addRoute(route: RouteRecordRaw): () => void
227+
addRoute(route: RC): () => void
212228
/**
213229
* Remove an existing route by its name.
214230
*
@@ -260,12 +276,12 @@ export interface Router {
260276
* Go back in history if possible by calling `history.back()`. Equivalent to
261277
* `router.go(-1)`.
262278
*/
263-
back(): ReturnType<Router['go']>
279+
back(): ReturnType<GenericRouter<RC>['go']>
264280
/**
265281
* Go forward in history if possible by calling `history.forward()`.
266282
* Equivalent to `router.go(1)`.
267283
*/
268-
forward(): ReturnType<Router['go']>
284+
forward(): ReturnType<GenericRouter<RC>['go']>
269285
/**
270286
* Allows you to move forward or backward through the history. Calls
271287
* `history.go()`.
@@ -352,13 +368,44 @@ export interface Router {
352368
install(app: App): void
353369
}
354370

371+
export interface Router extends GenericRouter<RouteRecordRaw> {
372+
readonly options: RouterOptions
373+
}
374+
375+
export interface RouterWithMatcher<RC> extends GenericRouter<RC> {
376+
readonly options: RouterWithMatcherOptions<RC>
377+
}
378+
379+
export function createDefaultMatcher(
380+
options: DefaultMatcherOptions
381+
): CloneableRouterMatcher {
382+
return createRouterMatcher(options.routes, options)
383+
}
384+
355385
/**
356386
* Creates a Router instance that can be used by a Vue app.
357387
*
358388
* @param options - {@link RouterOptions}
359389
*/
360390
export function createRouter(options: RouterOptions): Router {
361-
const matcher = createRouterMatcher(options.routes, options)
391+
const matcher = createDefaultMatcher(options)
392+
393+
const router = createRouterWithMatcher({
394+
...options,
395+
matcher,
396+
})
397+
398+
// Set the original options
399+
;(router as any).options = options
400+
401+
// Casting needed due to the 'options' property
402+
return router as GenericRouter<RouteRecordRaw> as Router
403+
}
404+
405+
export function createRouterWithMatcher<RC>(
406+
options: RouterWithMatcherOptions<RC>
407+
): RouterWithMatcher<RC> {
408+
const matcher = options.matcher
362409
const parseQuery = options.parseQuery || originalParseQuery
363410
const stringifyQuery = options.stringifyQuery || originalStringifyQuery
364411
const routerHistory = options.history
@@ -390,12 +437,9 @@ export function createRouter(options: RouterOptions): Router {
390437
// @ts-expect-error: intentionally avoid the type check
391438
applyToParams.bind(null, decode)
392439

393-
function addRoute(
394-
parentOrRoute: RouteRecordName | RouteRecordRaw,
395-
route?: RouteRecordRaw
396-
) {
440+
function addRoute(parentOrRoute: RouteRecordName | RC, route?: RC) {
397441
let parent: Parameters<(typeof matcher)['addRoute']>[1] | undefined
398-
let record: RouteRecordRaw
442+
let record: RC
399443
if (isRouteName(parentOrRoute)) {
400444
parent = matcher.getRecordMatcher(parentOrRoute)
401445
if (__DEV__ && !parent) {
@@ -1201,7 +1245,7 @@ export function createRouter(options: RouterOptions): Router {
12011245
let started: boolean | undefined
12021246
const installedApps = new Set<App>()
12031247

1204-
const router: Router = {
1248+
const router: RouterWithMatcher<RC> = {
12051249
currentRoute,
12061250
listening: true,
12071251

@@ -1230,7 +1274,7 @@ export function createRouter(options: RouterOptions): Router {
12301274
app.component('RouterLink', RouterLink)
12311275
app.component('RouterView', RouterView)
12321276

1233-
app.config.globalProperties.$router = router
1277+
app.config.globalProperties.$router = router as any
12341278
Object.defineProperty(app.config.globalProperties, '$route', {
12351279
enumerable: true,
12361280
get: () => unref(currentRoute),
@@ -1261,7 +1305,7 @@ export function createRouter(options: RouterOptions): Router {
12611305
})
12621306
}
12631307

1264-
app.provide(routerKey, router)
1308+
app.provide(routerKey, router as any)
12651309
app.provide(routeLocationKey, shallowReactive(reactiveRoute))
12661310
app.provide(routerViewLocationKey, currentRoute)
12671311

0 commit comments

Comments
 (0)