74560d120eca81a79d456549fcaa97371ce7db262e5adf8498883154855b9481396ed96207dd8e07b44c0ffd5627115b9b36c33afda921ada534147755a01a 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /* @flow */
  2. import Regexp from 'path-to-regexp'
  3. import { cleanPath } from './util/path'
  4. import { assert, warn } from './util/warn'
  5. export function createRouteMap (
  6. routes: Array<RouteConfig>,
  7. oldPathList?: Array<string>,
  8. oldPathMap?: Dictionary<RouteRecord>,
  9. oldNameMap?: Dictionary<RouteRecord>
  10. ): {
  11. pathList: Array<string>,
  12. pathMap: Dictionary<RouteRecord>,
  13. nameMap: Dictionary<RouteRecord>
  14. } {
  15. // the path list is used to control path matching priority
  16. const pathList: Array<string> = oldPathList || []
  17. // $flow-disable-line
  18. const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  19. // $flow-disable-line
  20. const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  21. routes.forEach(route => {
  22. addRouteRecord(pathList, pathMap, nameMap, route)
  23. })
  24. // ensure wildcard routes are always at the end
  25. for (let i = 0, l = pathList.length; i < l; i++) {
  26. if (pathList[i] === '*') {
  27. pathList.push(pathList.splice(i, 1)[0])
  28. l--
  29. i--
  30. }
  31. }
  32. if (process.env.NODE_ENV === 'development') {
  33. // warn if routes do not include leading slashes
  34. const found = pathList
  35. // check for missing leading slash
  36. .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')
  37. if (found.length > 0) {
  38. const pathNames = found.map(path => `- ${path}`).join('\n')
  39. warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)
  40. }
  41. }
  42. return {
  43. pathList,
  44. pathMap,
  45. nameMap
  46. }
  47. }
  48. function addRouteRecord (
  49. pathList: Array<string>,
  50. pathMap: Dictionary<RouteRecord>,
  51. nameMap: Dictionary<RouteRecord>,
  52. route: RouteConfig,
  53. parent?: RouteRecord,
  54. matchAs?: string
  55. ) {
  56. const { path, name } = route
  57. if (process.env.NODE_ENV !== 'production') {
  58. assert(path != null, `"path" is required in a route configuration.`)
  59. assert(
  60. typeof route.component !== 'string',
  61. `route config "component" for path: ${String(
  62. path || name
  63. )} cannot be a ` + `string id. Use an actual component instead.`
  64. )
  65. warn(
  66. // eslint-disable-next-line no-control-regex
  67. !/[^\u0000-\u007F]+/.test(path),
  68. `Route with path "${path}" contains unencoded characters, make sure ` +
  69. `your path is correctly encoded before passing it to the router. Use ` +
  70. `encodeURI to encode static segments of your path.`
  71. )
  72. }
  73. const pathToRegexpOptions: PathToRegexpOptions =
  74. route.pathToRegexpOptions || {}
  75. const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
  76. if (typeof route.caseSensitive === 'boolean') {
  77. pathToRegexpOptions.sensitive = route.caseSensitive
  78. }
  79. const record: RouteRecord = {
  80. path: normalizedPath,
  81. regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
  82. components: route.components || { default: route.component },
  83. instances: {},
  84. enteredCbs: {},
  85. name,
  86. parent,
  87. matchAs,
  88. redirect: route.redirect,
  89. beforeEnter: route.beforeEnter,
  90. meta: route.meta || {},
  91. props:
  92. route.props == null
  93. ? {}
  94. : route.components
  95. ? route.props
  96. : { default: route.props }
  97. }
  98. if (route.children) {
  99. // Warn if route is named, does not redirect and has a default child route.
  100. // If users navigate to this route by name, the default child will
  101. // not be rendered (GH Issue #629)
  102. if (process.env.NODE_ENV !== 'production') {
  103. if (
  104. route.name &&
  105. !route.redirect &&
  106. route.children.some(child => /^\/?$/.test(child.path))
  107. ) {
  108. warn(
  109. false,
  110. `Named Route '${route.name}' has a default child route. ` +
  111. `When navigating to this named route (:to="{name: '${
  112. route.name
  113. }'"), ` +
  114. `the default child route will not be rendered. Remove the name from ` +
  115. `this route and use the name of the default child route for named ` +
  116. `links instead.`
  117. )
  118. }
  119. }
  120. route.children.forEach(child => {
  121. const childMatchAs = matchAs
  122. ? cleanPath(`${matchAs}/${child.path}`)
  123. : undefined
  124. addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
  125. })
  126. }
  127. if (!pathMap[record.path]) {
  128. pathList.push(record.path)
  129. pathMap[record.path] = record
  130. }
  131. if (route.alias !== undefined) {
  132. const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
  133. for (let i = 0; i < aliases.length; ++i) {
  134. const alias = aliases[i]
  135. if (process.env.NODE_ENV !== 'production' && alias === path) {
  136. warn(
  137. false,
  138. `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
  139. )
  140. // skip in dev to make it work
  141. continue
  142. }
  143. const aliasRoute = {
  144. path: alias,
  145. children: route.children
  146. }
  147. addRouteRecord(
  148. pathList,
  149. pathMap,
  150. nameMap,
  151. aliasRoute,
  152. parent,
  153. record.path || '/' // matchAs
  154. )
  155. }
  156. }
  157. if (name) {
  158. if (!nameMap[name]) {
  159. nameMap[name] = record
  160. } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
  161. warn(
  162. false,
  163. `Duplicate named routes definition: ` +
  164. `{ name: "${name}", path: "${record.path}" }`
  165. )
  166. }
  167. }
  168. }
  169. function compileRouteRegex (
  170. path: string,
  171. pathToRegexpOptions: PathToRegexpOptions
  172. ): RouteRegExp {
  173. const regex = Regexp(path, [], pathToRegexpOptions)
  174. if (process.env.NODE_ENV !== 'production') {
  175. const keys: any = Object.create(null)
  176. regex.keys.forEach(key => {
  177. warn(
  178. !keys[key.name],
  179. `Duplicate param keys in route with path: "${path}"`
  180. )
  181. keys[key.name] = true
  182. })
  183. }
  184. return regex
  185. }
  186. function normalizePath (
  187. path: string,
  188. parent?: RouteRecord,
  189. strict?: boolean
  190. ): string {
  191. if (!strict) path = path.replace(/\/$/, '')
  192. if (path[0] === '/') return path
  193. if (parent == null) return path
  194. return cleanPath(`${parent.path}/${path}`)
  195. }