/* @flow */
|
|
import type VueRouter from './index'
|
import { resolvePath } from './util/path'
|
import { assert, warn } from './util/warn'
|
import { createRoute } from './util/route'
|
import { fillParams } from './util/params'
|
import { createRouteMap } from './create-route-map'
|
import { normalizeLocation } from './util/location'
|
import { decode } from './util/query'
|
|
export type Matcher = {
|
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
|
addRoutes: (routes: Array<RouteConfig>) => void;
|
addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
|
getRoutes: () => Array<RouteRecord>;
|
};
|
|
export function createMatcher (
|
routes: Array<RouteConfig>,
|
router: VueRouter
|
): Matcher {
|
const { pathList, pathMap, nameMap } = createRouteMap(routes)
|
|
function addRoutes (routes) {
|
createRouteMap(routes, pathList, pathMap, nameMap)
|
}
|
|
function addRoute (parentOrRoute, route) {
|
const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
|
// $flow-disable-line
|
createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
|
|
// add aliases of parent
|
if (parent && parent.alias.length) {
|
createRouteMap(
|
// $flow-disable-line route is defined if parent is
|
parent.alias.map(alias => ({ path: alias, children: [route] })),
|
pathList,
|
pathMap,
|
nameMap,
|
parent
|
)
|
}
|
}
|
|
function getRoutes () {
|
return pathList.map(path => pathMap[path])
|
}
|
|
function match (
|
raw: RawLocation,
|
currentRoute?: Route,
|
redirectedFrom?: Location
|
): Route {
|
const location = normalizeLocation(raw, currentRoute, false, router)
|
const { name } = location
|
|
if (name) {
|
const record = nameMap[name]
|
if (process.env.NODE_ENV !== 'production') {
|
warn(record, `Route with name '${name}' does not exist`)
|
}
|
if (!record) return _createRoute(null, location)
|
const paramNames = record.regex.keys
|
.filter(key => !key.optional)
|
.map(key => key.name)
|
|
if (typeof location.params !== 'object') {
|
location.params = {}
|
}
|
|
if (currentRoute && typeof currentRoute.params === 'object') {
|
for (const key in currentRoute.params) {
|
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
|
location.params[key] = currentRoute.params[key]
|
}
|
}
|
}
|
|
location.path = fillParams(record.path, location.params, `named route "${name}"`)
|
return _createRoute(record, location, redirectedFrom)
|
} else if (location.path) {
|
location.params = {}
|
for (let i = 0; i < pathList.length; i++) {
|
const path = pathList[i]
|
const record = pathMap[path]
|
if (matchRoute(record.regex, location.path, location.params)) {
|
return _createRoute(record, location, redirectedFrom)
|
}
|
}
|
}
|
// no match
|
return _createRoute(null, location)
|
}
|
|
function redirect (
|
record: RouteRecord,
|
location: Location
|
): Route {
|
const originalRedirect = record.redirect
|
let redirect = typeof originalRedirect === 'function'
|
? originalRedirect(createRoute(record, location, null, router))
|
: originalRedirect
|
|
if (typeof redirect === 'string') {
|
redirect = { path: redirect }
|
}
|
|
if (!redirect || typeof redirect !== 'object') {
|
if (process.env.NODE_ENV !== 'production') {
|
warn(
|
false, `invalid redirect option: ${JSON.stringify(redirect)}`
|
)
|
}
|
return _createRoute(null, location)
|
}
|
|
const re: Object = redirect
|
const { name, path } = re
|
let { query, hash, params } = location
|
query = re.hasOwnProperty('query') ? re.query : query
|
hash = re.hasOwnProperty('hash') ? re.hash : hash
|
params = re.hasOwnProperty('params') ? re.params : params
|
|
if (name) {
|
// resolved named direct
|
const targetRecord = nameMap[name]
|
if (process.env.NODE_ENV !== 'production') {
|
assert(targetRecord, `redirect failed: named route "${name}" not found.`)
|
}
|
return match({
|
_normalized: true,
|
name,
|
query,
|
hash,
|
params
|
}, undefined, location)
|
} else if (path) {
|
// 1. resolve relative redirect
|
const rawPath = resolveRecordPath(path, record)
|
// 2. resolve params
|
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
|
// 3. rematch with existing query and hash
|
return match({
|
_normalized: true,
|
path: resolvedPath,
|
query,
|
hash
|
}, undefined, location)
|
} else {
|
if (process.env.NODE_ENV !== 'production') {
|
warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
|
}
|
return _createRoute(null, location)
|
}
|
}
|
|
function alias (
|
record: RouteRecord,
|
location: Location,
|
matchAs: string
|
): Route {
|
const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
|
const aliasedMatch = match({
|
_normalized: true,
|
path: aliasedPath
|
})
|
if (aliasedMatch) {
|
const matched = aliasedMatch.matched
|
const aliasedRecord = matched[matched.length - 1]
|
location.params = aliasedMatch.params
|
return _createRoute(aliasedRecord, location)
|
}
|
return _createRoute(null, location)
|
}
|
|
function _createRoute (
|
record: ?RouteRecord,
|
location: Location,
|
redirectedFrom?: Location
|
): Route {
|
if (record && record.redirect) {
|
return redirect(record, redirectedFrom || location)
|
}
|
if (record && record.matchAs) {
|
return alias(record, location, record.matchAs)
|
}
|
return createRoute(record, location, redirectedFrom, router)
|
}
|
|
return {
|
match,
|
addRoute,
|
getRoutes,
|
addRoutes
|
}
|
}
|
|
function matchRoute (
|
regex: RouteRegExp,
|
path: string,
|
params: Object
|
): boolean {
|
const m = path.match(regex)
|
|
if (!m) {
|
return false
|
} else if (!params) {
|
return true
|
}
|
|
for (let i = 1, len = m.length; i < len; ++i) {
|
const key = regex.keys[i - 1]
|
if (key) {
|
// Fix #1994: using * with props: true generates a param named 0
|
params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
|
}
|
}
|
|
return true
|
}
|
|
function resolveRecordPath (path: string, record: RouteRecord): string {
|
return resolvePath(path, record.parent ? record.parent.path : '/', true)
|
}
|