Skip to content

Commit

Permalink
fix(router): fix the sort function of the router
Browse files Browse the repository at this point in the history
The router has an issue where it fails to prioritize dynamic segment over catchAll ones. We now
create a route-weight map on router creation. This map is used by the sorting function to prioritize
static > dynamic > catchAll

fix #248
  • Loading branch information
zto-sbenning committed Jul 23, 2024
1 parent 5b659c0 commit 9dc055c
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 10 deletions.
8 changes: 8 additions & 0 deletions src/router/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const inputSchema: RouterSchema = {
name: '/blog/articles/[articleId]',
href: '/es/blog/articulos/:articleId',
},
{
name: '/[...catchAll]',
href: '/es/:catchAll+',
},
{
name: '/[slug]',
href: '/es/:slug',
Expand All @@ -41,6 +45,10 @@ const inputSchema: RouterSchema = {
name: '/blog/articles/[articleId]',
href: '/cs/blog/clanky/:articleId',
},
{
name: '/[...catchAll]',
href: '/cs/:catchAll+',
},
{
name: '/[slug]',
href: '/cs/:slug',
Expand Down
95 changes: 86 additions & 9 deletions src/router/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { compile, match } from 'path-to-regexp'
import type { Route, RouterSchema } from '~/types'
import type { Route, RouterSchema, RouteWeightsMap } from '~/types'
import { getLocaleFactory } from '~/utils/locale-utils'
import { StaticRouter } from './static-router'

Expand All @@ -8,6 +8,7 @@ import { StaticRouter } from './static-router'
*/
export class Router extends StaticRouter {
private schema: RouterSchema
private routeWeightsMap: RouteWeightsMap

/**
* Constructor for the Router class
Expand All @@ -17,6 +18,7 @@ export class Router extends StaticRouter {
constructor(schema: RouterSchema) {
super()
this.schema = schema
this.routeWeightsMap = createRouteWeightsMap(schema)
}

/**
Expand Down Expand Up @@ -76,14 +78,9 @@ export class Router extends StaticRouter {
private getLocalizedRoutes(locale: string) {
return (
this.schema.routes[locale]?.sort((a, b) => {
const dynamicIndexA = a.name.indexOf('[')
const dynamicIndexB = b.name.indexOf('[')
const isDynamicA = dynamicIndexA !== -1
const isDynamicB = dynamicIndexB !== -1
if (isDynamicA && isDynamicB) {
return dynamicIndexB - dynamicIndexA
}
return isDynamicA ? 1 : -1
const weightA = this.routeWeightsMap[a.name]
const weightB = this.routeWeightsMap[b.name]
return weightA - weightB
}) || []
)
}
Expand Down Expand Up @@ -153,3 +150,83 @@ export function formatHref(...hrefSegments: string[]): string {
.replaceAll('%2F', '/')
return href.startsWith('/') ? href : `/${href}`
}

/**
* Checks if given route segment is static
* @param {string} segment - The route segment
* @returns {boolean} - Whether the segment is static
*/

export function isStaticRouteSegment(segment: string): boolean {
return !segment.includes('[')
}

/**
* Checks if given route segment is catch-all
* @param {string} segment - The route segment
* @returns {boolean} - Whether the segment is catch-all
*/

export function isCatchAllRouteSegment(segment: string): boolean {
return segment.includes('...')
}

/**
* Checks if given route segment is dynamic
* @param {string} segment - The route segment
* @returns {boolean} - Whether the segment is dynamic
*/

export function isDynamicRouteSegment(segment: string): boolean {
return segment.includes('[') && !isCatchAllRouteSegment(segment)
}

/**
* Gets weight of a route segment based on its nature
* @param {string} segment - The route segment
* @returns {number} - The weight of the segment
*/

export function getRouteSegmentWeight(segment: string): number {
if (isStaticRouteSegment(segment)) {
return 1
} else if (isDynamicRouteSegment(segment)) {
return 2
} else if (isCatchAllRouteSegment(segment)) {
return 3
}
return 0
}

/**
* Computes weight of a route based on its segments nature
* @param {Route} route - The route to compute weight for
* @returns {number} - The weight of the route
*/

export function computeRouteWeight(route: Route): number {
const segments = route.name.split('/').filter((segment) => segment.length > 0) // filter out empty segments
let weight = '0.'
for (const segment of segments) {
const segmentWeight = getRouteSegmentWeight(segment)
weight += segmentWeight
}
return parseFloat(weight)
}

/**
* Creates a map of route weights based on the routing schema
* @param {RouterSchema} schema - The routing schema
* @returns {RouteWeightsMap} - The map of route weights
*/

export function createRouteWeightsMap(schema: RouterSchema): RouteWeightsMap {
const routeWeightsMap: RouteWeightsMap = {}
const routes = schema.routes[schema.defaultLocale] // No need to create a map for each locale as the names are the same
if (routes) {
for (const route of routes) {
routeWeightsMap[route.name] = computeRouteWeight(route)
}
}
return routeWeightsMap
}
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export type RouterSchema = {
routes: Record<string, Route[]>
locales: string[]
defaultLocale: string
}
}

export type RouteWeightsMap = Record<`/${string}`, number>

0 comments on commit 9dc055c

Please sign in to comment.