Skip to content

Commit

Permalink
feat: pluggable matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
skirtles-code committed Mar 7, 2024
1 parent 8618943 commit be657db
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 41 deletions.
10 changes: 7 additions & 3 deletions packages/router/src/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
import { watch } from 'vue'
import { decode } from './encoding'
import { isSameRouteRecord } from './location'
import { RouterMatcher } from './matcher'
import { GenericRouterMatcher } from './matcher'
import { RouteRecordMatcher } from './matcher/pathMatcher'
import { PathParser } from './matcher/pathParserRanker'
import { Router } from './router'
import { GenericRouter } from './router'
import { UseLinkDevtoolsContext } from './RouterLink'
import { RouterViewDevtoolsContext } from './RouterView'
import { RouteLocationNormalized } from './types'
Expand Down Expand Up @@ -59,7 +59,11 @@ function formatDisplay(display: string) {
// to support multiple router instances
let routerId = 0

export function addDevtools(app: App, router: Router, matcher: RouterMatcher) {
export function addDevtools<RC>(
app: App,
router: GenericRouter<RC>,
matcher: GenericRouterMatcher<RC>
) {
// Take over router.beforeEach and afterEach

// make sure we are not registering the devtool twice
Expand Down
16 changes: 13 additions & 3 deletions packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { createWebHistory } from './history/html5'
export { createMemoryHistory } from './history/memory'
export { createWebHashHistory } from './history/hash'
export { createRouterMatcher } from './matcher'
export type { RouterMatcher } from './matcher'
export type { GenericRouterMatcher, RouterMatcher } from './matcher'

export { parseQuery, stringifyQuery } from './query'
export type {
Expand Down Expand Up @@ -67,8 +67,18 @@ export type {
NavigationHookAfter,
} from './types'

export { createRouter } from './router'
export type { Router, RouterOptions, RouterScrollBehavior } from './router'
export {
createRouter,
createRouterWithMatcher,
createDefaultMatcher,
} from './router'
export type {
Router,
RouterWithMatcher,
RouterOptions,
RouterWithMatcherOptions,
RouterScrollBehavior,
} from './router'

export { NavigationFailureType, isNavigationFailure } from './errors'
export type {
Expand Down
53 changes: 42 additions & 11 deletions packages/router/src/matcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import { warn } from '../warning'
import { assign, noop } from '../utils'

/**
* Internal RouterMatcher
* Internal GenericRouterMatcher
*
* @internal
*/
export interface RouterMatcher {
addRoute: (record: RouteRecordRaw, parent?: RouteRecordMatcher) => () => void
export interface GenericRouterMatcher<RC> {
addRoute: (record: RC, parent?: RouteRecordMatcher) => () => void
removeRoute: {
(matcher: RouteRecordMatcher): void
(name: RouteRecordName): void
Expand All @@ -47,6 +47,18 @@ export interface RouterMatcher {
) => MatcherLocation
}

/**
* Internal RouterMatcher
*
* @internal
*/
export interface RouterMatcher extends GenericRouterMatcher<RouteRecordRaw> {}

// TODO: Should this be considered internal?
export interface CloneableRouterMatcher extends RouterMatcher {
clone: () => CloneableRouterMatcher
}

/**
* Creates a Router Matcher.
*
Expand All @@ -57,15 +69,37 @@ export interface RouterMatcher {
export function createRouterMatcher(
routes: Readonly<RouteRecordRaw[]>,
globalOptions: PathParserOptions
): RouterMatcher {
// normalized ordered array of matchers
const matchers: RouteRecordMatcher[] = []
const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
): CloneableRouterMatcher {
globalOptions = mergeOptions(
{ strict: false, end: true, sensitive: false } as PathParserOptions,
globalOptions
)

const matcher = createRouterMatcherInternal(
globalOptions,
[],
new Map<RouteRecordName, RouteRecordMatcher>()
)

// add initial routes
routes.forEach(route => matcher.addRoute(route))

return matcher
}

function createRouterMatcherInternal(
globalOptions: PathParserOptions,
matchers: RouteRecordMatcher[],
matcherMap: Map<RouteRecordName, RouteRecordMatcher>
): CloneableRouterMatcher {
function clone() {
return createRouterMatcherInternal(
globalOptions,
[...matchers],
new Map(matcherMap)
)
}

function getRecordMatcher(name: RouteRecordName) {
return matcherMap.get(name)
}
Expand Down Expand Up @@ -350,10 +384,7 @@ export function createRouterMatcher(
}
}

// add initial routes
routes.forEach(route => addRoute(route))

return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher, clone }
}

function paramsFromLocation(
Expand Down
92 changes: 68 additions & 24 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import {
scrollToPosition,
_ScrollPositionNormalized,
} from './scrollBehavior'
import { createRouterMatcher, PathParserOptions } from './matcher'
import {
CloneableRouterMatcher,
createRouterMatcher,
GenericRouterMatcher,
PathParserOptions,
} from './matcher'
import {
createRouterError,
ErrorTypes,
Expand Down Expand Up @@ -97,10 +102,11 @@ export interface RouterScrollBehavior {
): Awaitable<ScrollPosition | false | void>
}

/**
* Options to initialize a {@link Router} instance.
*/
export interface RouterOptions extends PathParserOptions {
export interface DefaultMatcherOptions extends PathParserOptions {
routes: Readonly<RouteRecordRaw[]>
}

export interface SharedOptions {
/**
* History implementation used by the router. Most web applications should use
* `createWebHistory` but it requires the server to be properly configured.
Expand All @@ -117,10 +123,6 @@ export interface RouterOptions extends PathParserOptions {
* ```
*/
history: RouterHistory
/**
* Initial list of routes that should be added to the router.
*/
routes: Readonly<RouteRecordRaw[]>
/**
* Function to control scrolling when navigating between pages. Can return a
* Promise to delay scrolling. Check {@link ScrollBehavior}.
Expand Down Expand Up @@ -174,10 +176,24 @@ export interface RouterOptions extends PathParserOptions {
// linkInactiveClass?: string
}

/**
* Options to initialize a {@link Router} instance.
*/
export interface RouterOptions extends SharedOptions, PathParserOptions {
/**
* Initial list of routes that should be added to the router.
*/
routes: Readonly<RouteRecordRaw[]>
}

export interface RouterWithMatcherOptions<RC> extends SharedOptions {
matcher: GenericRouterMatcher<RC>
}

/**
* Router instance.
*/
export interface Router {
export interface GenericRouter<RC> {
/**
* @internal
*/
Expand All @@ -189,7 +205,7 @@ export interface Router {
/**
* Original options object passed to create the Router
*/
readonly options: RouterOptions
readonly options: SharedOptions

/**
* Allows turning off the listening of history events. This is a low level api for micro-frontends.
Expand All @@ -202,13 +218,13 @@ export interface Router {
* @param parentName - Parent Route Record where `route` should be appended at
* @param route - Route Record to add
*/
addRoute(parentName: RouteRecordName, route: RouteRecordRaw): () => void
addRoute(parentName: RouteRecordName, route: RC): () => void
/**
* Add a new {@link RouteRecordRaw | route record} to the router.
*
* @param route - Route Record to add
*/
addRoute(route: RouteRecordRaw): () => void
addRoute(route: RC): () => void
/**
* Remove an existing route by its name.
*
Expand Down Expand Up @@ -260,12 +276,12 @@ export interface Router {
* Go back in history if possible by calling `history.back()`. Equivalent to
* `router.go(-1)`.
*/
back(): ReturnType<Router['go']>
back(): ReturnType<GenericRouter<RC>['go']>
/**
* Go forward in history if possible by calling `history.forward()`.
* Equivalent to `router.go(1)`.
*/
forward(): ReturnType<Router['go']>
forward(): ReturnType<GenericRouter<RC>['go']>
/**
* Allows you to move forward or backward through the history. Calls
* `history.go()`.
Expand Down Expand Up @@ -352,13 +368,44 @@ export interface Router {
install(app: App): void
}

export interface Router extends GenericRouter<RouteRecordRaw> {
readonly options: RouterOptions
}

export interface RouterWithMatcher<RC> extends GenericRouter<RC> {
readonly options: RouterWithMatcherOptions<RC>
}

export function createDefaultMatcher(
options: DefaultMatcherOptions
): CloneableRouterMatcher {
return createRouterMatcher(options.routes, options)
}

/**
* Creates a Router instance that can be used by a Vue app.
*
* @param options - {@link RouterOptions}
*/
export function createRouter(options: RouterOptions): Router {
const matcher = createRouterMatcher(options.routes, options)
const matcher = createDefaultMatcher(options)

const router = createRouterWithMatcher({
...options,
matcher,
})

// Set the original options
;(router as any).options = options

// Casting needed due to the 'options' property
return router as GenericRouter<RouteRecordRaw> as Router
}

export function createRouterWithMatcher<RC>(
options: RouterWithMatcherOptions<RC>
): RouterWithMatcher<RC> {
const matcher = options.matcher
const parseQuery = options.parseQuery || originalParseQuery
const stringifyQuery = options.stringifyQuery || originalStringifyQuery
const routerHistory = options.history
Expand Down Expand Up @@ -390,12 +437,9 @@ export function createRouter(options: RouterOptions): Router {
// @ts-expect-error: intentionally avoid the type check
applyToParams.bind(null, decode)

function addRoute(
parentOrRoute: RouteRecordName | RouteRecordRaw,
route?: RouteRecordRaw
) {
function addRoute(parentOrRoute: RouteRecordName | RC, route?: RC) {
let parent: Parameters<(typeof matcher)['addRoute']>[1] | undefined
let record: RouteRecordRaw
let record: RC
if (isRouteName(parentOrRoute)) {
parent = matcher.getRecordMatcher(parentOrRoute)
if (__DEV__ && !parent) {
Expand Down Expand Up @@ -1201,7 +1245,7 @@ export function createRouter(options: RouterOptions): Router {
let started: boolean | undefined
const installedApps = new Set<App>()

const router: Router = {
const router: RouterWithMatcher<RC> = {
currentRoute,
listening: true,

Expand Down Expand Up @@ -1230,7 +1274,7 @@ export function createRouter(options: RouterOptions): Router {
app.component('RouterLink', RouterLink)
app.component('RouterView', RouterView)

app.config.globalProperties.$router = router
app.config.globalProperties.$router = router as any
Object.defineProperty(app.config.globalProperties, '$route', {
enumerable: true,
get: () => unref(currentRoute),
Expand Down Expand Up @@ -1261,7 +1305,7 @@ export function createRouter(options: RouterOptions): Router {
})
}

app.provide(routerKey, router)
app.provide(routerKey, router as any)
app.provide(routeLocationKey, shallowReactive(reactiveRoute))
app.provide(routerViewLocationKey, currentRoute)

Expand Down

0 comments on commit be657db

Please sign in to comment.