create-route-map.js 6.1 KB

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