1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | import {
|
11 | parse,
|
12 | ParseOptions,
|
13 | tokensToFunction,
|
14 | TokensToFunctionOptions,
|
15 | PathFunction,
|
16 | } from 'path-to-regexp'
|
17 | import UniversalRouter, { Route, Routes } from './UniversalRouter'
|
18 |
|
19 | export interface UrlParams {
|
20 | [paramName: string]: string | number | (string | number)[]
|
21 | }
|
22 |
|
23 | export interface GenerateUrlsOptions extends ParseOptions, TokensToFunctionOptions {
|
24 | |
25 |
|
26 |
|
27 | stringifyQueryParams?: (params: UrlParams) => string
|
28 | }
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | declare const generateUrl: (routeName: string, params?: UrlParams) => string
|
34 |
|
35 | type GenerateUrl = typeof generateUrl
|
36 |
|
37 | type Keys = { [key: string]: boolean }
|
38 |
|
39 | function cacheRoutes(
|
40 | routesByName: Map<string, Route>,
|
41 | route: Route,
|
42 | routes: Routes | null | undefined,
|
43 | ): void {
|
44 | if (route.name && routesByName.has(route.name)) {
|
45 | throw new Error(`Route "${route.name}" already exists`)
|
46 | }
|
47 |
|
48 | if (route.name) {
|
49 | routesByName.set(route.name, route)
|
50 | }
|
51 |
|
52 | if (routes) {
|
53 | for (let i = 0; i < routes.length; i++) {
|
54 | const childRoute = routes[i]
|
55 | childRoute.parent = route
|
56 | cacheRoutes(routesByName, childRoute, childRoute.children)
|
57 | }
|
58 | }
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | function generateUrls(router: UniversalRouter, options?: GenerateUrlsOptions): GenerateUrl {
|
65 | if (!router) {
|
66 | throw new ReferenceError('Router is not defined')
|
67 | }
|
68 |
|
69 | const routesByName = new Map<string, Route>()
|
70 | const regexpByRoute = new Map<Route, { toPath: PathFunction<UrlParams>; keys: Keys }>()
|
71 | const opts: GenerateUrlsOptions = { encode: encodeURIComponent, ...options }
|
72 | return (routeName: string, params?: UrlParams): string => {
|
73 | let route = routesByName.get(routeName)
|
74 | if (!route) {
|
75 | routesByName.clear()
|
76 | regexpByRoute.clear()
|
77 | cacheRoutes(routesByName, router.root, router.root.children)
|
78 |
|
79 | route = routesByName.get(routeName)
|
80 | if (!route) {
|
81 | throw new Error(`Route "${routeName}" not found`)
|
82 | }
|
83 | }
|
84 |
|
85 | let regexp = regexpByRoute.get(route)
|
86 | if (!regexp) {
|
87 | let fullPath = ''
|
88 | let rt: Route | null | undefined = route
|
89 | while (rt) {
|
90 | const path = Array.isArray(rt.path) ? rt.path[0] : rt.path
|
91 | if (path) {
|
92 | fullPath = path + fullPath
|
93 | }
|
94 | rt = rt.parent
|
95 | }
|
96 | const tokens = parse(fullPath, opts)
|
97 | const toPath = tokensToFunction(tokens, opts)
|
98 | const keys: Keys = Object.create(null)
|
99 | for (let i = 0; i < tokens.length; i++) {
|
100 | const token = tokens[i]
|
101 | if (typeof token !== 'string') {
|
102 | keys[token.name] = true
|
103 | }
|
104 | }
|
105 | regexp = { toPath, keys }
|
106 | regexpByRoute.set(route, regexp)
|
107 | }
|
108 |
|
109 | let url = router.baseUrl + regexp.toPath(params) || '/'
|
110 |
|
111 | if (opts.stringifyQueryParams && params) {
|
112 | const queryParams: UrlParams = {}
|
113 | const keys = Object.keys(params)
|
114 | for (let i = 0; i < keys.length; i++) {
|
115 | const key = keys[i]
|
116 | if (!regexp.keys[key]) {
|
117 | queryParams[key] = params[key]
|
118 | }
|
119 | }
|
120 | const query = opts.stringifyQueryParams(queryParams)
|
121 | if (query) {
|
122 | url += query.charAt(0) === '?' ? query : `?${query}`
|
123 | }
|
124 | }
|
125 |
|
126 | return url
|
127 | }
|
128 | }
|
129 |
|
130 | export default generateUrls
|