create-matcher.js 6.4 KB

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