Skip to content

Commit 9e449f5

Browse files
committed
[fixed] Match routes piece-by-piece
1 parent fbc109c commit 9e449f5

File tree

3 files changed

+107
-47
lines changed

3 files changed

+107
-47
lines changed

modules/PatternUtils.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,28 +82,57 @@ export function compilePattern(pattern) {
8282
* - paramValues
8383
*/
8484
export function matchPattern(pattern, pathname) {
85+
// Make leading slashes consistent between pattern and pathname.
86+
if (pattern.charAt(0) !== '/') {
87+
pattern = `/${pattern}`
88+
}
89+
if (pathname.charAt(0) !== '/') {
90+
pathname = `/${pathname}`
91+
}
92+
8593
let { regexpSource, paramNames, tokens } = compilePattern(pattern)
8694

8795
regexpSource += '/*' // Ignore trailing slashes
8896

97+
// Special-case patterns like '*' for catch-all routes.
8998
const captureRemaining = tokens[tokens.length - 1] !== '*'
9099

91-
if (captureRemaining)
100+
if (captureRemaining) {
101+
// This will match newlines in the remaining path.
92102
regexpSource += '([\\s\\S]*?)'
103+
}
93104

94105
const match = pathname.match(new RegExp('^' + regexpSource + '$', 'i'))
95106

96107
let remainingPathname, paramValues
97108
if (match != null) {
98-
paramValues = Array.prototype.slice.call(match, 1).map(function (v) {
99-
return v != null ? decodeURIComponent(v) : v
100-
})
101-
109+
let matchedPath
102110
if (captureRemaining) {
103-
remainingPathname = paramValues.pop()
111+
remainingPathname = match.pop()
112+
matchedPath =
113+
match[0].substr(0, match[0].length - remainingPathname.length)
104114
} else {
105-
remainingPathname = pathname.replace(match[0], '')
115+
// If this matched at all, then the match was the entire pathname.
116+
matchedPath = match[0]
117+
remainingPathname = ''
118+
}
119+
120+
// Ensure we actually match at a path boundary.
121+
if (remainingPathname && remainingPathname.charAt(0) !== '/') {
122+
// This depends on the leading slash getting added to pathname above to
123+
// work in all cases.
124+
if (!matchedPath || matchedPath.charAt(matchedPath.length - 1) !== '/') {
125+
return {
126+
remainingPathname: null,
127+
paramNames,
128+
paramValues: null
129+
}
130+
}
106131
}
132+
133+
paramValues = match.slice(1).map(
134+
v => v != null ? decodeURIComponent(v) : v
135+
)
107136
} else {
108137
remainingPathname = paramValues = null
109138
}

modules/isActive.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,39 @@ function deepEqual(a, b) {
3737
}
3838

3939
function paramsAreActive(paramNames, paramValues, activeParams) {
40+
// FIXME: This doesn't work on repeated params in activeParams.
4041
return paramNames.every(function (paramName, index) {
4142
return String(paramValues[index]) === String(activeParams[paramName])
4243
})
4344
}
4445

4546
function getMatchingRoute(pathname, activeRoutes, activeParams) {
46-
let route, pattern, basename = ''
47+
let route, pattern
48+
let remainingPathname = pathname, paramNames = [], paramValues = []
49+
4750
for (let i = 0, len = activeRoutes.length; i < len; ++i) {
4851
route = activeRoutes[i]
4952
pattern = route.path || ''
5053

51-
if (pattern.charAt(0) !== '/')
52-
pattern = basename.replace(/\/*$/, '/') + pattern // Relative paths build on the parent's path.
54+
if (pattern.charAt(0) === '/') {
55+
remainingPathname = pathname
56+
paramNames = []
57+
paramValues = []
58+
}
5359

54-
let { remainingPathname, paramNames, paramValues } = matchPattern(pattern, pathname)
60+
if (remainingPathname !== null) {
61+
const matched = matchPattern(pattern, remainingPathname)
62+
remainingPathname = matched.remainingPathname
63+
paramNames = [ ...paramNames, ...matched.paramNames ]
64+
paramValues = [ ...paramValues, ...matched.paramValues ]
65+
}
5566

56-
if (remainingPathname === '' && route.path && paramsAreActive(paramNames, paramValues, activeParams))
67+
if (
68+
remainingPathname === '' &&
69+
route.path &&
70+
paramsAreActive(paramNames, paramValues, activeParams)
71+
)
5772
return route
58-
59-
basename = pattern
6073
}
6174

6275
return null

modules/matchRoutes.js

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ function getIndexRoute(route, location, callback) {
4444
}
4545

4646
function assignParams(params, paramNames, paramValues) {
47-
return paramNames.reduceRight(function (params, paramName, index) {
47+
return paramNames.reduce(function (params, paramName, index) {
4848
const paramValue = paramValues && paramValues[index]
4949

5050
if (Array.isArray(params[paramName])) {
51-
params[paramName].unshift(paramValue)
51+
params[paramName].push(paramValue)
5252
} else if (paramName in params) {
53-
params[paramName] = [ paramValue, params[paramName] ]
53+
params[paramName] = [ params[paramName], paramValue ]
5454
} else {
5555
params[paramName] = paramValue
5656
}
@@ -63,34 +63,46 @@ function createParams(paramNames, paramValues) {
6363
return assignParams({}, paramNames, paramValues)
6464
}
6565

66-
function matchRouteDeep(basename, route, location, callback) {
66+
function matchRouteDeep(
67+
route, location, remainingPathname, paramNames, paramValues, callback
68+
) {
6769
let pattern = route.path || ''
6870

69-
if (pattern.charAt(0) !== '/')
70-
pattern = basename.replace(/\/*$/, '/') + pattern // Relative paths build on the parent's path.
71+
if (pattern.charAt(0) === '/') {
72+
remainingPathname = location.pathname
73+
paramNames = []
74+
paramValues = []
75+
}
7176

72-
const { remainingPathname, paramNames, paramValues } = matchPattern(pattern, location.pathname)
73-
const isExactMatch = remainingPathname === ''
77+
if (remainingPathname !== null) {
78+
const matched = matchPattern(pattern, remainingPathname)
79+
remainingPathname = matched.remainingPathname
80+
paramNames = [ ...paramNames, ...matched.paramNames ]
81+
paramValues = [ ...paramValues, ...matched.paramValues ]
7482

75-
if (isExactMatch && route.path) {
76-
const match = {
77-
routes: [ route ],
78-
params: createParams(paramNames, paramValues)
79-
}
83+
if (remainingPathname === '' && route.path) {
84+
const match = {
85+
routes: [ route ],
86+
params: createParams(paramNames, paramValues)
87+
}
8088

81-
getIndexRoute(route, location, function (error, indexRoute) {
82-
if (error) {
83-
callback(error)
84-
} else {
85-
if (Array.isArray(indexRoute))
86-
match.routes.push(...indexRoute)
87-
else if (indexRoute)
88-
match.routes.push(indexRoute)
89+
getIndexRoute(route, location, function (error, indexRoute) {
90+
if (error) {
91+
callback(error)
92+
} else {
93+
if (Array.isArray(indexRoute))
94+
match.routes.push(...indexRoute)
95+
else if (indexRoute)
96+
match.routes.push(indexRoute)
8997

90-
callback(null, match)
91-
}
92-
})
93-
} else if (remainingPathname != null || route.childRoutes) {
98+
callback(null, match)
99+
}
100+
})
101+
return
102+
}
103+
}
104+
105+
if (remainingPathname != null || route.childRoutes) {
94106
// Either a) this route matched at least some of the path or b)
95107
// we don't have to load this route's children asynchronously. In
96108
// either case continue checking for matches in the subtree.
@@ -109,7 +121,7 @@ function matchRouteDeep(basename, route, location, callback) {
109121
} else {
110122
callback()
111123
}
112-
}, pattern)
124+
}, remainingPathname, paramNames, paramValues)
113125
} else {
114126
callback()
115127
}
@@ -130,15 +142,21 @@ function matchRouteDeep(basename, route, location, callback) {
130142
* Note: This operation may finish synchronously if no routes have an
131143
* asynchronous getChildRoutes method.
132144
*/
133-
function matchRoutes(routes, location, callback, basename='') {
145+
function matchRoutes(
146+
routes, location, callback,
147+
remainingPathname=location.pathname, paramNames=[], paramValues=[]
148+
) {
134149
loopAsync(routes.length, function (index, next, done) {
135-
matchRouteDeep(basename, routes[index], location, function (error, match) {
136-
if (error || match) {
137-
done(error, match)
138-
} else {
139-
next()
150+
matchRouteDeep(
151+
routes[index], location, remainingPathname, paramNames, paramValues,
152+
function (error, match) {
153+
if (error || match) {
154+
done(error, match)
155+
} else {
156+
next()
157+
}
140158
}
141-
})
159+
)
142160
}, callback)
143161
}
144162

0 commit comments

Comments
 (0)