1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
import {type Route, type RouteParams} from './types'
export class Router<T extends Record<string, any>> {
routes: [string, Route][] = []
constructor(description: Record<keyof T, string | string[]>) {
for (const [screen, pattern] of Object.entries(description)) {
if (typeof pattern === 'string') {
this.routes.push([screen, createRoute(pattern)])
} else {
pattern.forEach(subPattern => {
this.routes.push([screen, createRoute(subPattern)])
})
}
}
}
matchName(name: keyof T | (string & {})): Route | undefined {
for (const [screenName, route] of this.routes) {
if (screenName === name) {
return route
}
}
}
matchPath(path: string): [string, RouteParams] {
let name = 'NotFound'
let params: RouteParams = {}
for (const [screenName, route] of this.routes) {
const res = route.match(path)
if (res) {
name = screenName
params = res.params
break
}
}
return [name, params]
}
}
function createRoute(pattern: string): Route {
const pathParamNames: Set<string> = new Set()
let matcherReInternal = pattern.replace(/:([\w]+)/g, (_m, name) => {
pathParamNames.add(name)
return `(?<${name}>[^/]+)`
})
const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i')
return {
match(path) {
const {pathname, searchParams} = new URL(path, 'http://throwaway.com')
const addedParams = Object.fromEntries(searchParams.entries())
const res = matcherRe.exec(pathname)
if (res) {
return {params: Object.assign(addedParams, res.groups || {})}
}
return undefined
},
build(params = {}) {
const str = pattern.replace(
/:([\w]+)/g,
(_m, name) => params[encodeURIComponent(name)] || 'undefined',
)
let hasQp = false
const qp = new URLSearchParams()
for (const paramName in params) {
if (!pathParamNames.has(paramName)) {
qp.set(paramName, params[paramName])
hasQp = true
}
}
return str + (hasQp ? `?${qp.toString()}` : '')
},
}
}
|