Skip to content

Commit f9dcad6

Browse files
committed
feat: support optional trailing slashes when matching
Issue router5/router5#16
1 parent 4f4aa47 commit f9dcad6

3 files changed

Lines changed: 40 additions & 10 deletions

File tree

modules/RouteNode.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,31 @@ export default class RouteNode {
103103
return matched ? segments : null
104104
}
105105

106-
getSegmentsMatchingPath(path) {
106+
getSegmentsMatchingPath(path, trailingSlash = false) {
107107
let matchChildren = (nodes, pathSegment, segments) => {
108108
// for (child of node.children) {
109109
for (let i in nodes) {
110110
let child = nodes[i]
111111
// Partially match path
112112
let match = child.parser.partialMatch(pathSegment)
113+
let remainingPath
114+
115+
if (!match && trailingSlash) {
116+
// Try with optional trailing slash
117+
match = child.parser.match(pathSegment, true)
118+
remainingPath = ''
119+
} else if (match) {
120+
// Remove consumed segment from path
121+
let consumedPath = child.parser.build(match)
122+
remainingPath = pathSegment.replace(consumedPath, '')
123+
if (trailingSlash && remainingPath === '/' && !/\/$/.test(consumedPath)) {
124+
remainingPath = ''
125+
}
126+
}
127+
113128
if (match) {
114129
segments.push(child)
115130
Object.keys(match).forEach(param => segments.params[param] = match[param])
116-
// Remove consumed segment from path
117-
let remainingPath = pathSegment.replace(child.parser.build(match), '')
118131
// If fully matched
119132
if (!remainingPath.length) {
120133
return segments
@@ -154,15 +167,15 @@ export default class RouteNode {
154167
}
155168

156169
getMatchPathFromSegments(segments) {
157-
if (!segments) return null
170+
if (!segments || !segments.length) return null
158171

159172
let name = segments.map(segment => segment.name).join('.')
160173
let params = segments.params
161174

162175
return {name, params}
163176
}
164177

165-
matchPath(path) {
166-
return this.getMatchPathFromSegments(this.getSegmentsMatchingPath(path))
178+
matchPath(path, trailingSlash = false) {
179+
return this.getMatchPathFromSegments(this.getSegmentsMatchingPath(path, trailingSlash))
167180
}
168181
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
"async": "^1.3.0"
3333
},
3434
"dependencies": {
35-
"path-parser": "~0.1.1"
35+
"path-parser": "~0.2.1"
3636
}
3737
}

test/main.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,30 @@ describe('RouteNode', function () {
165165
rootNode.matchPath('/abc').should.eql({name: 'id', params: {id: 'abc'}});
166166
rootNode.matchPath('/section/abc').should.eql({name: 'section', params: {id: 'abc'}});
167167
});
168+
169+
it('should match paths with optional trailing slashes', function () {
170+
var rootNode = getRoutes();
171+
should.not.exists(rootNode.matchPath('/users/list/'));
172+
rootNode.matchPath('/users/list', true).should.eql({name: 'users.list', params: {}});
173+
rootNode.matchPath('/users/list').should.eql({name: 'users.list', params: {}});
174+
rootNode.matchPath('/users/list/', true).should.eql({name: 'users.list', params: {}});
175+
should.not.exists(rootNode.matchPath('/users/list//', true));
176+
177+
var rootNode = getRoutes(true);
178+
should.not.exists(rootNode.matchPath('/users/list'));
179+
rootNode.matchPath('/users/list', true).should.eql({name: 'users.list', params: {}});
180+
rootNode.matchPath('/users/list/', true).should.eql({name: 'users.list', params: {}});
181+
rootNode.matchPath('/users/list/').should.eql({name: 'users.list', params: {}});
182+
should.not.exists(rootNode.matchPath('/users/list//', true));
183+
});
168184
});
169185

170186

171-
function getRoutes() {
187+
function getRoutes(trailingSlash) {
188+
var suffix = trailingSlash ? '/' : '';
172189
var usersNode = new RouteNode('users', '/users', [
173-
new RouteNode('list', '/list'),
174-
new RouteNode('view', '/view/:id')
190+
new RouteNode('list', '/list' + suffix),
191+
new RouteNode('view', '/view/:id' + suffix)
175192
]);
176193

177194
return new RouteNode('', '', [

0 commit comments

Comments
 (0)