From: Eduardo San Martin Morote Date: Wed, 29 Apr 2020 16:09:19 +0000 (+0200) Subject: refactor: spread matcher options in route records X-Git-Tag: v4.0.0-alpha.8~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8eb2dd29c97a5bdace002171c1d416845533a97d;p=thirdparty%2Fvuejs%2Frouter.git refactor: spread matcher options in route records --- diff --git a/playground/router.ts b/playground/router.ts index 8c520fd1..b2a89f4a 100644 --- a/playground/router.ts +++ b/playground/router.ts @@ -19,7 +19,7 @@ let removeRoute: (() => void) | undefined export const routerHistory = createWebHistory() export const router = createRouter({ history: routerHistory, - pathOptions: { strict: true }, + strict: true, routes: [ { path: '/home', redirect: '/' }, { @@ -132,7 +132,8 @@ export const router = createRouter({ path: '/dynamic', name: 'dynamic', component: Nested, - options: { end: false, strict: true }, + end: false, + strict: true, beforeEnter(to, from, next) { if (!removeRoute) { removeRoute = router.addRoute('dynamic', { diff --git a/src/matcher/index.ts b/src/matcher/index.ts index fdd5198f..86acf567 100644 --- a/src/matcher/index.ts +++ b/src/matcher/index.ts @@ -51,7 +51,7 @@ export function createRouterMatcher( let mainNormalizedRecord = normalizeRouteRecord(record) // we might be the child of an alias mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record - const options: PathParserOptions = { ...globalOptions, ...record.options } + const options: PathParserOptions = mergeOptions(globalOptions, record) // generate an array of records to correctly handle aliases const normalizedRecords: typeof mainNormalizedRecord[] = [ mainNormalizedRecord, @@ -347,4 +347,14 @@ function mergeMetaFields(matched: MatcherLocation['matched']) { ) } -export { PathParserOptions } +function mergeOptions(defaults: T, partialOptions: Partial): T { + let options = {} as T + for (let key in defaults) { + options[key] = + key in partialOptions ? partialOptions[key] : (defaults[key] as any) + } + + return options +} + +export { PathParserOptionsPublic as PathParserOptions } from './pathParserRanker' diff --git a/src/matcher/pathParserRanker.ts b/src/matcher/pathParserRanker.ts index 33fa691a..9811387d 100644 --- a/src/matcher/pathParserRanker.ts +++ b/src/matcher/pathParserRanker.ts @@ -62,6 +62,11 @@ export interface PathParserOptions { end?: boolean } +export type PathParserOptionsPublic = Pick< + PathParserOptions, + 'end' | 'sensitive' | 'strict' +> + // default pattern for a param: non greedy everything but / const BASE_PARAM_PATTERN = '[^/]+?' diff --git a/src/router.ts b/src/router.ts index bb37b5dd..f6a15915 100644 --- a/src/router.ts +++ b/src/router.ts @@ -62,7 +62,7 @@ export interface ScrollBehavior { ): Awaitable } -export interface RouterOptions { +export interface RouterOptions extends PathParserOptions { /** * History implementation used by the router. Most web applications should use * `createWebHistory` but it requires the server to be properly configured. @@ -109,11 +109,6 @@ export interface RouterOptions { * {@link RouterOptions.parseQuery | `parseQuery`} counterpart to handle query parsing. */ stringifyQuery?: typeof originalStringifyQuery - - /** - * Global matcher rules applied to every route record. - */ - pathOptions?: PathParserOptions } export interface Router { @@ -151,15 +146,12 @@ export interface Router { * * @param options - {@link RouterOptions} */ -export function createRouter({ - history, - routes, - scrollBehavior, - parseQuery = originalParseQuery, - stringifyQuery = originalStringifyQuery, - pathOptions = {}, -}: RouterOptions): Router { - const matcher = createRouterMatcher(routes, pathOptions) +export function createRouter(options: RouterOptions): Router { + const matcher = createRouterMatcher(options.routes, options) + let parseQuery = options.parseQuery || originalParseQuery + let stringifyQuery = options.stringifyQuery || originalStringifyQuery + let { scrollBehavior } = options + let routerHistory = options.history const beforeGuards = useCallbacks>() const beforeResolveGuards = useCallbacks>() @@ -169,8 +161,8 @@ export function createRouter({ ) let pendingLocation: RouteLocation = START_LOCATION_NORMALIZED - if (isBrowser && 'scrollRestoration' in window.history) { - window.history.scrollRestoration = 'manual' + if (isBrowser && 'scrollRestoration' in history) { + history.scrollRestoration = 'manual' } const encodeParams = applyToParams.bind(null, encodeParam) @@ -210,15 +202,15 @@ export function createRouter({ } function resolve( - location: Readonly, + rawLocation: Readonly, currentLocation?: Readonly ): RouteLocation & { href: string } { - // const objectLocation = routerLocationAsObject(location) + // const objectLocation = routerLocationAsObject(rawLocation) currentLocation = currentLocation || currentRoute.value - if (typeof location === 'string') { + if (typeof rawLocation === 'string') { let locationNormalized = parseURL( parseQuery, - location, + rawLocation, currentLocation.path ) let matchedRoute = matcher.resolve( @@ -238,40 +230,40 @@ export function createRouter({ // matched: matchedRoute.matched, params: decodeParams(matchedRoute.params), redirectedFrom: undefined, - href: history.base + locationNormalized.fullPath, + href: routerHistory.base + locationNormalized.fullPath, } } // TODO: dev warning if params and path at the same time // path could be relative in object as well - if ('path' in location) { - location = { - ...location, - path: parseURL(parseQuery, location.path, currentLocation.path).path, + if ('path' in rawLocation) { + rawLocation = { + ...rawLocation, + path: parseURL(parseQuery, rawLocation.path, currentLocation.path).path, } } let matchedRoute: MatcherLocation = // relative or named location, path is ignored - // for same reason TS thinks location.params can be undefined + // for same reason TS thinks rawLocation.params can be undefined matcher.resolve( - 'params' in location - ? { ...location, params: encodeParams(location.params) } - : location, + 'params' in rawLocation + ? { ...rawLocation, params: encodeParams(rawLocation.params) } + : rawLocation, currentLocation ) - const hash = encodeHash(location.hash || '') + const hash = encodeHash(rawLocation.hash || '') // put back the unencoded params as given by the user (avoid the cost of decoding them) // TODO: normalize params if we accept numbers as raw values matchedRoute.params = - 'params' in location - ? location.params! + 'params' in rawLocation + ? rawLocation.params! : decodeParams(matchedRoute.params) const fullPath = stringifyURL(stringifyQuery, { - ...location, + ...rawLocation, hash, path: matchedRoute.path, }) @@ -281,10 +273,10 @@ export function createRouter({ // keep the hash encoded so fullPath is effectively path + encodedQuery + // hash hash, - query: normalizeQuery(location.query), + query: normalizeQuery(rawLocation.query), ...matchedRoute, redirectedFrom: undefined, - href: history.base + fullPath, + href: routerHistory.base + fullPath, } } @@ -544,7 +536,7 @@ export function createRouter({ // only consider as push if it's not the first navigation const isFirstNavigation = from === START_LOCATION_NORMALIZED - const state = !isBrowser ? {} : window.history.state + const state = !isBrowser ? {} : history.state // change URL only if the user did a push/replace and if it's not the initial navigation because // it's just reflecting the url @@ -552,11 +544,11 @@ export function createRouter({ // on the initial navigation, we want to reuse the scroll position from // history state if it exists if (replace || isFirstNavigation) - history.replace(toLocation, { + routerHistory.replace(toLocation, { scroll: isFirstNavigation && state && state.scroll, ...data, }) - else history.push(toLocation, data) + else routerHistory.push(toLocation, data) } // accept current navigation @@ -581,7 +573,7 @@ export function createRouter({ } // attach listener to history to trigger navigations - history.listen((to, _from, info) => { + routerHistory.listen((to, _from, info) => { // TODO: in dev try catch to correctly log the matcher error // cannot be a redirect route because it was in history const toLocation = resolve(to.fullPath) as RouteLocationNormalized @@ -612,7 +604,7 @@ export function createRouter({ return error as NavigationFailure } if (error.type === ErrorTypes.NAVIGATION_GUARD_REDIRECT) { - history.go(-info.delta, false) + routerHistory.go(-info.delta, false) // the error is already handled by router.push we just want to avoid // logging the error pushWithRedirect( @@ -625,7 +617,7 @@ export function createRouter({ return Promise.reject() } // TODO: test on different browsers ensure consistent behavior - history.go(-info.delta, false) + routerHistory.go(-info.delta, false) // unrecognized error, transfer to the global handler return triggerError(error) }) @@ -640,7 +632,7 @@ export function createRouter({ ) // revert the navigation - if (failure) history.go(-info.delta, false) + if (failure) routerHistory.go(-info.delta, false) triggerAfterEach( toLocation as RouteLocationNormalizedLoaded, @@ -710,7 +702,7 @@ export function createRouter({ if (!scrollBehavior) return Promise.resolve() return nextTick() - .then(() => scrollBehavior(to, from, scrollPosition || null)) + .then(() => scrollBehavior!(to, from, scrollPosition || null)) .then(position => position && scrollToPosition(position)) } @@ -725,9 +717,9 @@ export function createRouter({ push, replace, - go: history.go, - back: () => history.go(-1), - forward: () => history.go(1), + go: routerHistory.go, + back: () => routerHistory.go(-1), + forward: () => routerHistory.go(1), beforeEach: beforeGuards.add, beforeResolve: beforeResolveGuards.add, @@ -736,7 +728,7 @@ export function createRouter({ onError: errorHandlers.add, isReady, - history, + history: routerHistory, install(app: App) { applyRouterPlugin(app, this) }, diff --git a/src/types/index.ts b/src/types/index.ts index ad30df7a..64d1be2a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ import { LocationQuery, LocationQueryRaw } from '../query' -import { PathParserOptions } from '../matcher/pathParserRanker' +import { PathParserOptions } from '../matcher' import { Ref, ComputedRef, ComponentOptions } from 'vue' import { RouteRecord, RouteRecordNormalized } from '../matcher/types' import { HistoryState } from '../history/common' @@ -137,7 +137,7 @@ export type RouteRecordName = string | symbol /** * Common properties among all kind of {@link RouteRecordRaw} */ -export interface _RouteRecordBase { +export interface _RouteRecordBase extends PathParserOptions { /** * Path of the record. Should start with `/` unless the record is the child of * another record. @@ -171,9 +171,6 @@ export interface _RouteRecordBase { * Arbitrary data attached to the record. */ meta?: Record - // TODO: only allow a subset? - // TODO: RFC: remove this and only allow global options - options?: PathParserOptions } export type RouteRecordRedirectOption =