From: Eduardo San Martin Morote Date: Wed, 8 Jan 2025 09:48:35 +0000 (+0100) Subject: refactor: rename matcher to resolver X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=794fdd57fba7d3f0458e519d417d3376a95c16db;p=thirdparty%2Fvuejs%2Frouter.git refactor: rename matcher to resolver --- diff --git a/packages/router/src/experimental/router.ts b/packages/router/src/experimental/router.ts index 95762e89..9cf885cc 100644 --- a/packages/router/src/experimental/router.ts +++ b/packages/router/src/experimental/router.ts @@ -27,8 +27,8 @@ import type { NEW_LocationResolved, NEW_MatcherRecord, NEW_MatcherRecordRaw, - NEW_RouterMatcher, -} from '../new-route-resolver/matcher' + NEW_RouterResolver, +} from '../new-route-resolver/resolver' import { parseQuery as originalParseQuery, stringifyQuery as originalStringifyQuery, @@ -51,7 +51,6 @@ import type { RouteLocationAsRelative, RouteLocationAsRelativeTyped, RouteLocationAsString, - RouteLocationGeneric, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationRaw, @@ -191,7 +190,7 @@ export interface EXPERIMENTAL_RouterOptions< * Matcher to use to resolve routes. * @experimental */ - matcher: NEW_RouterMatcher + matcher: NEW_RouterResolver } /** @@ -407,6 +406,9 @@ export interface EXPERIMENTAL_RouteRecordRaw extends NEW_MatcherRecordRaw { // TODO: is it worth to have 2 types for the undefined values? export interface EXPERIMENTAL_RouteRecordNormalized extends NEW_MatcherRecord { + /** + * Arbitrary data attached to the record. + */ meta: RouteMeta } @@ -468,7 +470,7 @@ export function experimental_createRouter( | EXPERIMENTAL_RouteRecordRaw, route?: EXPERIMENTAL_RouteRecordRaw ) { - let parent: Parameters<(typeof matcher)['addRoute']>[1] | undefined + let parent: Parameters<(typeof matcher)['addMatcher']>[1] | undefined let rawRecord: EXPERIMENTAL_RouteRecordRaw if (isRouteName(parentOrRoute)) { @@ -486,20 +488,20 @@ export function experimental_createRouter( rawRecord = parentOrRoute } - const addedRecord = matcher.addRoute( + const addedRecord = matcher.addMatcher( normalizeRouteRecord(rawRecord), parent ) return () => { - matcher.removeRoute(addedRecord) + matcher.removeMatcher(addedRecord) } } function removeRoute(name: NonNullable) { const recordMatcher = matcher.getMatcher(name) if (recordMatcher) { - matcher.removeRoute(recordMatcher) + matcher.removeMatcher(recordMatcher) } else if (__DEV__) { warn(`Cannot remove non-existent route "${String(name)}"`) } @@ -1219,7 +1221,7 @@ export function experimental_createRouter( addRoute, removeRoute, - clearRoutes: matcher.clearRoutes, + clearRoutes: matcher.clearMatchers, hasRoute, getRoutes, resolve, diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index fe951f7a..1a2541d7 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -14,10 +14,13 @@ import type { _PathParserOptions, } from './pathParserRanker' -import { comparePathParserScore } from './pathParserRanker' +import { + comparePathParserScore, + PATH_PARSER_OPTIONS_DEFAULTS, +} from './pathParserRanker' import { warn } from '../warning' -import { assign, noop } from '../utils' +import { assign, mergeOptions, noop } from '../utils' import type { RouteRecordNameGeneric, _RouteRecordProps } from '../typed-routes' /** @@ -64,8 +67,8 @@ export function createRouterMatcher( NonNullable, RouteRecordMatcher >() - globalOptions = mergeOptions( - { strict: false, end: true, sensitive: false } as PathParserOptions, + globalOptions = mergeOptions( + PATH_PARSER_OPTIONS_DEFAULTS, globalOptions ) @@ -429,7 +432,7 @@ export function normalizeRouteRecord( * components. Also accept a boolean for components. * @param record */ -function normalizeRecordProps( +export function normalizeRecordProps( record: RouteRecordRaw ): Record { const propsObject = {} as Record @@ -472,18 +475,6 @@ function mergeMetaFields(matched: MatcherLocation['matched']) { ) } -function mergeOptions( - defaults: T, - partialOptions: Partial -): T { - const options = {} as T - for (const key in defaults) { - options[key] = key in partialOptions ? partialOptions[key]! : defaults[key] - } - - return options -} - type ParamKey = RouteRecordMatcher['keys'][number] function isSameParam(a: ParamKey, b: ParamKey): boolean { @@ -521,7 +512,7 @@ function checkSameParams(a: RouteRecordMatcher, b: RouteRecordMatcher) { * @param mainNormalizedRecord - RouteRecordNormalized * @param parent - RouteRecordMatcher */ -function checkChildMissingNameWithEmptyPath( +export function checkChildMissingNameWithEmptyPath( mainNormalizedRecord: RouteRecordNormalized, parent?: RouteRecordMatcher ) { diff --git a/packages/router/src/matcher/pathParserRanker.ts b/packages/router/src/matcher/pathParserRanker.ts index 81b07764..b2c0b40a 100644 --- a/packages/router/src/matcher/pathParserRanker.ts +++ b/packages/router/src/matcher/pathParserRanker.ts @@ -367,3 +367,8 @@ function isLastScoreNegative(score: PathParser['score']): boolean { const last = score[score.length - 1] return score.length > 0 && last[last.length - 1] < 0 } +export const PATH_PARSER_OPTIONS_DEFAULTS: PathParserOptions = { + strict: false, + end: true, + sensitive: false, +} diff --git a/packages/router/src/new-route-resolver/index.ts b/packages/router/src/new-route-resolver/index.ts index 17910f62..4c07b32c 100644 --- a/packages/router/src/new-route-resolver/index.ts +++ b/packages/router/src/new-route-resolver/index.ts @@ -1 +1 @@ -export { createCompiledMatcher } from './matcher' +export { createCompiledMatcher } from './resolver' diff --git a/packages/router/src/new-route-resolver/matcher-location.ts b/packages/router/src/new-route-resolver/matcher-location.ts index b9ca1ab0..f597df07 100644 --- a/packages/router/src/new-route-resolver/matcher-location.ts +++ b/packages/router/src/new-route-resolver/matcher-location.ts @@ -1,5 +1,5 @@ import type { LocationQueryRaw } from '../query' -import type { MatcherName } from './matcher' +import type { MatcherName } from './resolver' /** * Generic object of params that can be passed to a matcher. diff --git a/packages/router/src/new-route-resolver/matcher-pattern.ts b/packages/router/src/new-route-resolver/matcher-pattern.ts index c627c3bf..0f7d8c19 100644 --- a/packages/router/src/new-route-resolver/matcher-pattern.ts +++ b/packages/router/src/new-route-resolver/matcher-pattern.ts @@ -1,4 +1,4 @@ -import { decode, MatcherQueryParams } from './matcher' +import { decode, MatcherQueryParams } from './resolver' import { EmptyParams, MatcherParamsFormatted } from './matcher-location' import { miss } from './matchers/errors' diff --git a/packages/router/src/new-route-resolver/matcher-resolve.spec.ts b/packages/router/src/new-route-resolver/matcher-resolve.spec.ts index 91fb8fb2..6f9914af 100644 --- a/packages/router/src/new-route-resolver/matcher-resolve.spec.ts +++ b/packages/router/src/new-route-resolver/matcher-resolve.spec.ts @@ -1,6 +1,5 @@ import { createRouterMatcher, normalizeRouteRecord } from '../matcher' import { RouteComponent, RouteRecordRaw, MatcherLocation } from '../types' -import { MatcherLocationNormalizedLoose } from '../../__tests__/utils' import { defineComponent } from 'vue' import { START_LOCATION_NORMALIZED } from '../location' import { describe, expect, it } from 'vitest' @@ -10,7 +9,8 @@ import { MatcherLocationRaw, NEW_MatcherRecordRaw, NEW_LocationResolved, -} from './matcher' + NEW_MatcherRecord, +} from './resolver' import { PathParams, tokensToParser } from '../matcher/pathParserRanker' import { tokenizePath } from '../matcher/pathTokenizer' import { miss } from './matchers/errors' @@ -63,22 +63,23 @@ function compileRouteRecord( describe('RouterMatcher.resolve', () => { mockWarn() - type Matcher = ReturnType + type Matcher = ReturnType type MatcherResolvedLocation = ReturnType - const START_LOCATION: NEW_LocationResolved = { + const START_LOCATION: MatcherResolvedLocation = { name: Symbol('START'), - fullPath: '/', - path: '/', params: {}, + path: '/', + fullPath: '/', query: {}, hash: '', matched: [], + // meta: {}, } function isMatcherLocationResolved( location: unknown - ): location is NEW_LocationResolved { + ): location is NEW_LocationResolved { return !!( location && typeof location === 'object' && @@ -95,16 +96,16 @@ describe('RouterMatcher.resolve', () => { toLocation: MatcherLocationRaw, expectedLocation: Partial, fromLocation: - | NEW_LocationResolved + | NEW_LocationResolved | Exclude | `/${string}` = START_LOCATION ) { const records = (Array.isArray(record) ? record : [record]).map( (record): NEW_MatcherRecordRaw => compileRouteRecord(record) ) - const matcher = createCompiledMatcher() + const matcher = createCompiledMatcher() for (const record of records) { - matcher.addRoute(record) + matcher.addMatcher(record) } const resolved: MatcherResolvedLocation = { @@ -137,60 +138,6 @@ describe('RouterMatcher.resolve', () => { }) } - function _assertRecordMatch( - record: RouteRecordRaw | RouteRecordRaw[], - location: MatcherLocationRaw, - resolved: Partial, - start: MatcherLocation = START_LOCATION_NORMALIZED - ) { - record = Array.isArray(record) ? record : [record] - const matcher = createRouterMatcher(record, {}) - - if (!('meta' in resolved)) { - resolved.meta = record[0].meta || {} - } - - if (!('name' in resolved)) { - resolved.name = undefined - } - - // add location if provided as it should be the same value - if ('path' in location && !('path' in resolved)) { - resolved.path = location.path - } - - if ('redirect' in record) { - throw new Error('not handled') - } else { - // use one single record - if (!resolved.matched) resolved.matched = record.map(normalizeRouteRecord) - // allow passing an expect.any(Array) - else if (Array.isArray(resolved.matched)) - resolved.matched = resolved.matched.map(m => ({ - ...normalizeRouteRecord(m as any), - aliasOf: m.aliasOf, - })) - } - - // allows not passing params - resolved.params = - resolved.params || ('params' in location ? location.params : {}) - - const startCopy: MatcherLocation = { - ...start, - matched: start.matched.map(m => ({ - ...normalizeRouteRecord(m), - aliasOf: m.aliasOf, - })) as MatcherLocation['matched'], - } - - // make matched non enumerable - Object.defineProperty(startCopy, 'matched', { enumerable: false }) - - const result = matcher.resolve(location, startCopy) - expect(result).toEqual(resolved) - } - /** * * @param record - Record or records we are testing the matcher against diff --git a/packages/router/src/new-route-resolver/matcher.spec.ts b/packages/router/src/new-route-resolver/matcher.spec.ts index a0c59e6d..335ddb83 100644 --- a/packages/router/src/new-route-resolver/matcher.spec.ts +++ b/packages/router/src/new-route-resolver/matcher.spec.ts @@ -3,7 +3,7 @@ import { createCompiledMatcher, NO_MATCH_LOCATION, pathEncoded, -} from './matcher' +} from './resolver' import { MatcherPatternParams_Base, MatcherPatternPath, @@ -11,7 +11,7 @@ import { MatcherPatternPathStatic, MatcherPatternPathDynamic, } from './matcher-pattern' -import { NEW_MatcherRecord } from './matcher' +import { NEW_MatcherRecord } from './resolver' import { miss } from './matchers/errors' import { EmptyParams } from './matcher-location' @@ -133,25 +133,25 @@ describe('RouterMatcher', () => { describe('adding and removing', () => { it('add static path', () => { const matcher = createCompiledMatcher() - matcher.addRoute(EMPTY_PATH_ROUTE) + matcher.addMatcher(EMPTY_PATH_ROUTE) }) it('adds dynamic path', () => { const matcher = createCompiledMatcher() - matcher.addRoute(USER_ID_ROUTE) + matcher.addMatcher(USER_ID_ROUTE) }) it('removes static path', () => { const matcher = createCompiledMatcher() - matcher.addRoute(EMPTY_PATH_ROUTE) - matcher.removeRoute(EMPTY_PATH_ROUTE) + matcher.addMatcher(EMPTY_PATH_ROUTE) + matcher.removeMatcher(EMPTY_PATH_ROUTE) // Add assertions to verify the route was removed }) it('removes dynamic path', () => { const matcher = createCompiledMatcher() - matcher.addRoute(USER_ID_ROUTE) - matcher.removeRoute(USER_ID_ROUTE) + matcher.addMatcher(USER_ID_ROUTE) + matcher.removeMatcher(USER_ID_ROUTE) // Add assertions to verify the route was removed }) }) diff --git a/packages/router/src/new-route-resolver/matcher.test-d.ts b/packages/router/src/new-route-resolver/matcher.test-d.ts index 8ea5b771..26060c3a 100644 --- a/packages/router/src/new-route-resolver/matcher.test-d.ts +++ b/packages/router/src/new-route-resolver/matcher.test-d.ts @@ -2,15 +2,15 @@ import { describe, expectTypeOf, it } from 'vitest' import { NEW_LocationResolved, NEW_MatcherRecordRaw, - NEW_RouterMatcher, -} from './matcher' + NEW_RouterResolver, +} from './resolver' import { EXPERIMENTAL_RouteRecordNormalized } from '../experimental/router' describe('Matcher', () => { type TMatcherRecordRaw = NEW_MatcherRecordRaw type TMatcherRecord = EXPERIMENTAL_RouteRecordNormalized - const matcher: NEW_RouterMatcher = + const matcher: NEW_RouterResolver = {} as any describe('matcher.resolve()', () => { diff --git a/packages/router/src/new-route-resolver/matchers/test-utils.ts b/packages/router/src/new-route-resolver/matchers/test-utils.ts index e922e721..250efafd 100644 --- a/packages/router/src/new-route-resolver/matchers/test-utils.ts +++ b/packages/router/src/new-route-resolver/matchers/test-utils.ts @@ -4,7 +4,7 @@ import { MatcherPatternQuery, MatcherPatternParams_Base, } from '../matcher-pattern' -import { NEW_MatcherRecord } from '../matcher' +import { NEW_MatcherRecord } from '../resolver' import { miss } from './errors' export const ANY_PATH_PATTERN_MATCHER: MatcherPatternPath<{ diff --git a/packages/router/src/new-route-resolver/matcher.ts b/packages/router/src/new-route-resolver/resolver.ts similarity index 85% rename from packages/router/src/new-route-resolver/matcher.ts rename to packages/router/src/new-route-resolver/resolver.ts index c0ba504c..17ceecf0 100644 --- a/packages/router/src/new-route-resolver/matcher.ts +++ b/packages/router/src/new-route-resolver/resolver.ts @@ -27,11 +27,13 @@ import { _RouteRecordProps } from '../typed-routes' export type MatcherName = string | symbol /** - * Manage and resolve routes. Also handles the encoding, decoding, parsing and serialization of params, query, and hash. - * `TMatcherRecordRaw` represents the raw record type passed to {@link addRoute}. - * `TMatcherRecord` represents the normalized record type. + * Manage and resolve routes. Also handles the encoding, decoding, parsing and + * serialization of params, query, and hash. + * + * - `TMatcherRecordRaw` represents the raw record type passed to {@link addMatcher}. + * - `TMatcherRecord` represents the normalized record type returned by {@link getMatchers}. */ -export interface NEW_RouterMatcher { +export interface NEW_RouterResolver { /** * Resolves an absolute location (like `/path/to/somewhere`). */ @@ -80,9 +82,26 @@ export interface NEW_RouterMatcher { currentLocation: NEW_LocationResolved ): NEW_LocationResolved - addRoute(matcher: TMatcherRecordRaw, parent?: TMatcherRecord): TMatcherRecord - removeRoute(matcher: TMatcherRecord): void - clearRoutes(): void + /** + * Add a matcher record. Previously named `addRoute()`. + * @param matcher - The matcher record to add. + * @param parent - The parent matcher record if this is a child. + */ + addMatcher( + matcher: TMatcherRecordRaw, + parent?: TMatcherRecord + ): TMatcherRecord + + /** + * Remove a matcher by its name. Previously named `removeRoute()`. + * @param matcher - The matcher (returned by {@link addMatcher}) to remove. + */ + removeMatcher(matcher: TMatcherRecord): void + + /** + * Remove all matcher records. Prevoisly named `clearRoutes()`. + */ + clearMatchers(): void /** * Get a list of all matchers. @@ -98,7 +117,7 @@ export interface NEW_RouterMatcher { } /** - * Allowed location objects to be passed to {@link NEW_RouterMatcher['resolve']} + * Allowed location objects to be passed to {@link NEW_RouterResolver['resolve']} */ export type MatcherLocationRaw = | `/${string}` @@ -108,20 +127,6 @@ export type MatcherLocationRaw = | MatcherLocationAsPathRelative | MatcherLocationAsRelative -/** - * Matcher capable of adding and removing routes at runtime. - */ -export interface NEW_Matcher_Dynamic { - addRoute(record: TODO, parent?: TODO): () => void - - removeRoute(record: TODO): void - removeRoute(name: MatcherName): void - - clearRoutes(): void -} - -type TODO = any - export interface NEW_LocationResolved { // FIXME: remove `undefined` name: MatcherName | undefined @@ -234,7 +239,7 @@ export const NO_MATCH_LOCATION = { // FIXME: later on, the MatcherRecord should be compatible with RouteRecordRaw (which can miss a path, have children, etc) /** - * Experiment new matcher record base type. + * Experimental new matcher record base type. * * @experimental */ @@ -267,10 +272,7 @@ export interface NEW_MatcherRecordRaw { children?: NEW_MatcherRecordRaw[] } -/** - * Normalized version of a {@link NEW_MatcherRecordRaw} record. - */ -export interface NEW_MatcherRecord { +export interface NEW_MatcherRecordBase { /** * Name of the matcher. Unique across all matchers. */ @@ -280,9 +282,15 @@ export interface NEW_MatcherRecord { query?: MatcherPatternQuery hash?: MatcherPatternHash - parent?: NEW_MatcherRecord + parent?: T } +/** + * Normalized version of a {@link NEW_MatcherRecordRaw} record. + */ +export interface NEW_MatcherRecord + extends NEW_MatcherRecordBase {} + /** * Tagged template helper to encode params into a path. Doesn't work with null */ @@ -310,9 +318,9 @@ export function pathEncoded( /** * Build the `matched` array of a record that includes all parent records from the root to the current one. */ -function buildMatched(record: NEW_MatcherRecord): NEW_MatcherRecord[] { - const matched: NEW_MatcherRecord[] = [] - let node: NEW_MatcherRecord | undefined = record +function buildMatched>(record: T): T[] { + const matched: T[] = [] + let node: T | undefined = record while (node) { matched.unshift(node) node = node.parent @@ -320,11 +328,13 @@ function buildMatched(record: NEW_MatcherRecord): NEW_MatcherRecord[] { return matched } -export function createCompiledMatcher( +export function createCompiledMatcher< + TMatcherRecord extends NEW_MatcherRecordBase +>( records: NEW_MatcherRecordRaw[] = [] -): NEW_RouterMatcher { +): NEW_RouterResolver { // TODO: we also need an array that has the correct order - const matchers = new Map() + const matchers = new Map() // TODO: allow custom encode/decode functions // const encodeParams = applyToParams.bind(null, encodeParam) @@ -340,26 +350,26 @@ export function createCompiledMatcher( type MatcherResolveArgs = | [ absoluteLocation: `/${string}`, - currentLocation?: undefined | NEW_LocationResolved + currentLocation?: undefined | NEW_LocationResolved ] | [ relativeLocation: string, - currentLocation: NEW_LocationResolved + currentLocation: NEW_LocationResolved ] | [absoluteLocation: MatcherLocationAsPathAbsolute] | [ relativeLocation: MatcherLocationAsPathRelative, - currentLocation: NEW_LocationResolved + currentLocation: NEW_LocationResolved ] | [location: MatcherLocationAsNamed] | [ relativeLocation: MatcherLocationAsRelative, - currentLocation: NEW_LocationResolved + currentLocation: NEW_LocationResolved ] function resolve( ...args: MatcherResolveArgs - ): NEW_LocationResolved { + ): NEW_LocationResolved { const [location, currentLocation] = args // string location, e.g. '/foo', '../bar', 'baz', '?page=1' @@ -367,10 +377,8 @@ export function createCompiledMatcher( // parseURL handles relative paths const url = parseURL(parseQuery, location, currentLocation?.path) - let matcher: NEW_MatcherRecord | undefined - let matched: - | NEW_LocationResolved['matched'] - | undefined + let matcher: TMatcherRecord | undefined + let matched: NEW_LocationResolved['matched'] | undefined let parsedParams: MatcherParamsFormatted | null | undefined for (matcher of matchers.values()) { @@ -475,10 +483,11 @@ export function createCompiledMatcher( } } - function addRoute(record: NEW_MatcherRecordRaw, parent?: NEW_MatcherRecord) { + function addRoute(record: NEW_MatcherRecordRaw, parent?: TMatcherRecord) { const name = record.name ?? (__DEV__ ? Symbol('unnamed-route') : Symbol()) // FIXME: proper normalization of the record - const normalizedRecord: NEW_MatcherRecord = { + // @ts-expect-error: we are not properly normalizing the record yet + const normalizedRecord: TMatcherRecord = { ...record, name, parent, @@ -491,7 +500,7 @@ export function createCompiledMatcher( addRoute(record) } - function removeRoute(matcher: NEW_MatcherRecord) { + function removeRoute(matcher: TMatcherRecord) { matchers.delete(matcher.name) // TODO: delete children and aliases } @@ -511,9 +520,9 @@ export function createCompiledMatcher( return { resolve, - addRoute, - removeRoute, - clearRoutes, + addMatcher: addRoute, + removeMatcher: removeRoute, + clearMatchers: clearRoutes, getMatcher, getMatchers, } diff --git a/packages/router/src/utils/index.ts b/packages/router/src/utils/index.ts index a7c42f4c..c6d62209 100644 --- a/packages/router/src/utils/index.ts +++ b/packages/router/src/utils/index.ts @@ -58,3 +58,15 @@ export const noop = () => {} */ export const isArray: (arg: ArrayLike | any) => arg is ReadonlyArray = Array.isArray + +export function mergeOptions( + defaults: T, + partialOptions: Partial +): T { + const options = {} as T + for (const key in defaults) { + options[key] = key in partialOptions ? partialOptions[key]! : defaults[key] + } + + return options +}