1 |
|
2 |
|
3 | import type VueRouter from './index'
|
4 | import { resolvePath } from './util/path'
|
5 | import { assert, warn } from './util/warn'
|
6 | import { createRoute } from './util/route'
|
7 | import { fillParams } from './util/params'
|
8 | import { createRouteMap } from './create-route-map'
|
9 | import { normalizeLocation } from './util/location'
|
10 |
|
11 | export type Matcher = {
|
12 | match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
|
13 | addRoutes: (routes: Array<RouteConfig>) => void;
|
14 | };
|
15 |
|
16 | export function createMatcher (
|
17 | routes: Array<RouteConfig>,
|
18 | router: VueRouter
|
19 | ): Matcher {
|
20 | const { pathList, pathMap, nameMap } = createRouteMap(routes)
|
21 |
|
22 | function addRoutes (routes) {
|
23 | createRouteMap(routes, pathList, pathMap, nameMap)
|
24 | }
|
25 |
|
26 | function match (
|
27 | raw: RawLocation,
|
28 | currentRoute?: Route,
|
29 | redirectedFrom?: Location
|
30 | ): Route {
|
31 | const location = normalizeLocation(raw, currentRoute, false, router)
|
32 | const { name } = location
|
33 |
|
34 | if (name) {
|
35 | const record = nameMap[name]
|
36 | if (process.env.NODE_ENV !== 'production') {
|
37 | warn(record, `Route with name '${name}' does not exist`)
|
38 | }
|
39 | if (!record) return _createRoute(null, location)
|
40 | const paramNames = record.regex.keys
|
41 | .filter(key => !key.optional)
|
42 | .map(key => key.name)
|
43 |
|
44 | if (typeof location.params !== 'object') {
|
45 | location.params = {}
|
46 | }
|
47 |
|
48 | if (currentRoute && typeof currentRoute.params === 'object') {
|
49 | for (const key in currentRoute.params) {
|
50 | if (!(key in location.params) && paramNames.indexOf(key) > -1) {
|
51 | location.params[key] = currentRoute.params[key]
|
52 | }
|
53 | }
|
54 | }
|
55 |
|
56 | if (record) {
|
57 | location.path = fillParams(record.path, location.params, `named route "${name}"`)
|
58 | return _createRoute(record, location, redirectedFrom)
|
59 | }
|
60 | } else if (location.path) {
|
61 | location.params = {}
|
62 | for (let i = 0; i < pathList.length; i++) {
|
63 | const path = pathList[i]
|
64 | const record = pathMap[path]
|
65 | if (matchRoute(record.regex, location.path, location.params)) {
|
66 | return _createRoute(record, location, redirectedFrom)
|
67 | }
|
68 | }
|
69 | }
|
70 |
|
71 | return _createRoute(null, location)
|
72 | }
|
73 |
|
74 | function redirect (
|
75 | record: RouteRecord,
|
76 | location: Location
|
77 | ): Route {
|
78 | const originalRedirect = record.redirect
|
79 | let redirect = typeof originalRedirect === 'function'
|
80 | ? originalRedirect(createRoute(record, location, null, router))
|
81 | : originalRedirect
|
82 |
|
83 | if (typeof redirect === 'string') {
|
84 | redirect = { path: redirect }
|
85 | }
|
86 |
|
87 | if (!redirect || typeof redirect !== 'object') {
|
88 | if (process.env.NODE_ENV !== 'production') {
|
89 | warn(
|
90 | false, `invalid redirect option: ${JSON.stringify(redirect)}`
|
91 | )
|
92 | }
|
93 | return _createRoute(null, location)
|
94 | }
|
95 |
|
96 | const re: Object = redirect
|
97 | const { name, path } = re
|
98 | let { query, hash, params } = location
|
99 | query = re.hasOwnProperty('query') ? re.query : query
|
100 | hash = re.hasOwnProperty('hash') ? re.hash : hash
|
101 | params = re.hasOwnProperty('params') ? re.params : params
|
102 |
|
103 | if (name) {
|
104 |
|
105 | const targetRecord = nameMap[name]
|
106 | if (process.env.NODE_ENV !== 'production') {
|
107 | assert(targetRecord, `redirect failed: named route "${name}" not found.`)
|
108 | }
|
109 | return match({
|
110 | _normalized: true,
|
111 | name,
|
112 | query,
|
113 | hash,
|
114 | params
|
115 | }, undefined, location)
|
116 | } else if (path) {
|
117 |
|
118 | const rawPath = resolveRecordPath(path, record)
|
119 |
|
120 | const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
|
121 |
|
122 | return match({
|
123 | _normalized: true,
|
124 | path: resolvedPath,
|
125 | query,
|
126 | hash
|
127 | }, undefined, location)
|
128 | } else {
|
129 | if (process.env.NODE_ENV !== 'production') {
|
130 | warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
|
131 | }
|
132 | return _createRoute(null, location)
|
133 | }
|
134 | }
|
135 |
|
136 | function alias (
|
137 | record: RouteRecord,
|
138 | location: Location,
|
139 | matchAs: string
|
140 | ): Route {
|
141 | const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
|
142 | const aliasedMatch = match({
|
143 | _normalized: true,
|
144 | path: aliasedPath
|
145 | })
|
146 | if (aliasedMatch) {
|
147 | const matched = aliasedMatch.matched
|
148 | const aliasedRecord = matched[matched.length - 1]
|
149 | location.params = aliasedMatch.params
|
150 | return _createRoute(aliasedRecord, location)
|
151 | }
|
152 | return _createRoute(null, location)
|
153 | }
|
154 |
|
155 | function _createRoute (
|
156 | record: ?RouteRecord,
|
157 | location: Location,
|
158 | redirectedFrom?: Location
|
159 | ): Route {
|
160 | if (record && record.redirect) {
|
161 | return redirect(record, redirectedFrom || location)
|
162 | }
|
163 | if (record && record.matchAs) {
|
164 | return alias(record, location, record.matchAs)
|
165 | }
|
166 | return createRoute(record, location, redirectedFrom, router)
|
167 | }
|
168 |
|
169 | return {
|
170 | match,
|
171 | addRoutes
|
172 | }
|
173 | }
|
174 |
|
175 | function matchRoute (
|
176 | regex: RouteRegExp,
|
177 | path: string,
|
178 | params: Object
|
179 | ): boolean {
|
180 | const m = path.match(regex)
|
181 |
|
182 | if (!m) {
|
183 | return false
|
184 | } else if (!params) {
|
185 | return true
|
186 | }
|
187 |
|
188 | for (let i = 1, len = m.length; i < len; ++i) {
|
189 | const key = regex.keys[i - 1]
|
190 | const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
|
191 | if (key) {
|
192 |
|
193 | params[key.name || 'pathMatch'] = val
|
194 | }
|
195 | }
|
196 |
|
197 | return true
|
198 | }
|
199 |
|
200 | function resolveRecordPath (path: string, record: RouteRecord): string {
|
201 | return resolvePath(path, record.parent ? record.parent.path : '/', true)
|
202 | }
|