/* @flow */ import Regexp from 'path-to-regexp' import { cleanPath } from './util/path' import { assert, warn } from './util/warn' export function createRouteMap ( routes: Array, oldPathList?: Array, oldPathMap?: Dictionary, oldNameMap?: Dictionary ): { pathList: Array; pathMap: Dictionary; nameMap: Dictionary; } { // the path list is used to control path matching priority const pathList: Array = oldPathList || [] // $flow-disable-line const pathMap: Dictionary = oldPathMap || Object.create(null) // $flow-disable-line const nameMap: Dictionary = oldNameMap || Object.create(null) routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route) }) // ensure wildcard routes are always at the end for (let i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]) l-- i-- } } return { pathList, pathMap, nameMap } } function addRouteRecord ( pathList: Array, pathMap: Dictionary, nameMap: Dictionary, route: RouteConfig, parent?: RouteRecord, matchAs?: string ) { const { path, name } = route if (process.env.NODE_ENV !== 'production') { assert(path != null, `"path" is required in a route configuration.`) assert( typeof route.component !== 'string', `route config "component" for path: ${String(path || name)} cannot be a ` + `string id. Use an actual component instead.` ) } const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {} const normalizedPath = normalizePath( path, parent, pathToRegexpOptions.strict ) if (typeof route.caseSensitive === 'boolean') { pathToRegexpOptions.sensitive = route.caseSensitive } const record: RouteRecord = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, instances: {}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {}, props: route.props == null ? {} : route.components ? route.props : { default: route.props } } if (route.children) { // Warn if route is named, does not redirect and has a default child route. // If users navigate to this route by name, the default child will // not be rendered (GH Issue #629) if (process.env.NODE_ENV !== 'production') { if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) { warn( false, `Named Route '${route.name}' has a default child route. ` + `When navigating to this named route (:to="{name: '${route.name}'"), ` + `the default child route will not be rendered. Remove the name from ` + `this route and use the name of the default child route for named ` + `links instead.` ) } } route.children.forEach(child => { const childMatchAs = matchAs ? cleanPath(`${matchAs}/${child.path}`) : undefined addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs) }) } if (route.alias !== undefined) { const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] aliases.forEach(alias => { const aliasRoute = { path: alias, children: route.children } addRouteRecord( pathList, pathMap, nameMap, aliasRoute, parent, record.path || '/' // matchAs ) }) } if (!pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record } if (name) { if (!nameMap[name]) { nameMap[name] = record } else if (process.env.NODE_ENV !== 'production' && !matchAs) { warn( false, `Duplicate named routes definition: ` + `{ name: "${name}", path: "${record.path}" }` ) } } } function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp { const regex = Regexp(path, [], pathToRegexpOptions) if (process.env.NODE_ENV !== 'production') { const keys: any = Object.create(null) regex.keys.forEach(key => { warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`) keys[key.name] = true }) } return regex } function normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string { if (!strict) path = path.replace(/\/$/, '') if (path[0] === '/') return path if (parent == null) return path return cleanPath(`${parent.path}/${path}`) }