UNPKG

3.58 kBPlain TextView Raw
1/**
2 * Universal Router (https://www.kriasoft.com/universal-router/)
3 *
4 * Copyright (c) 2015-present Kriasoft.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.txt file in the root directory of this source tree.
8 */
9
10import {
11 parse,
12 ParseOptions,
13 tokensToFunction,
14 TokensToFunctionOptions,
15 PathFunction,
16} from 'path-to-regexp'
17import UniversalRouter, { Route, Routes } from './UniversalRouter'
18
19export interface UrlParams {
20 [paramName: string]: string | number | (string | number)[]
21}
22
23export interface GenerateUrlsOptions extends ParseOptions, TokensToFunctionOptions {
24 /**
25 * Add a query string to generated url based on unknown route params.
26 */
27 stringifyQueryParams?: (params: UrlParams) => string
28}
29
30/**
31 * Create a url by route name from route path.
32 */
33declare const generateUrl: (routeName: string, params?: UrlParams) => string
34
35type GenerateUrl = typeof generateUrl
36
37type Keys = { [key: string]: boolean }
38
39function 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 * Create a function to generate urls by route names.
63 */
64function 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
130export default generateUrls