Skip to content

Commit 4de549a

Browse files
committed
refactor route regex compilation + warn duplicate param keys (close #1345)
1 parent 869f3f7 commit 4de549a

File tree

7 files changed

+53
-39
lines changed

7 files changed

+53
-39
lines changed

flow/declarations.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
declare var document: Document;
22

3+
declare class RouteRegExp extends RegExp {
4+
keys: Array<{ name: string, optional: boolean }>;
5+
}
6+
37
declare module 'path-to-regexp' {
48
declare var exports: {
5-
(path: string, keys: Array<?{ name: string }>): RegExp;
9+
(path: string, keys?: Array<?{ name: string }>): RouteRegExp;
610
compile: (path: string) => (params: Object) => string;
711
}
812
}
@@ -48,6 +52,7 @@ declare type RouteConfig = {
4852

4953
declare type RouteRecord = {
5054
path: string;
55+
regex: RouteRegExp;
5156
components: Dictionary<any>;
5257
instances: Dictionary<any>;
5358
name: ?string;

src/create-matcher.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* @flow */
22

33
import type VueRouter from './index'
4+
import { resolvePath } from './util/path'
45
import { assert, warn } from './util/warn'
56
import { createRoute } from './util/route'
7+
import { fillParams } from './util/params'
68
import { createRouteMap } from './create-route-map'
7-
import { resolvePath } from './util/path'
89
import { normalizeLocation } from './util/location'
9-
import { getRouteRegex, fillParams } from './util/params'
1010

1111
export type Matcher = {
1212
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
@@ -36,7 +36,7 @@ export function createMatcher (
3636
if (process.env.NODE_ENV !== 'production') {
3737
warn(record, `Route with name '${name}' does not exist`)
3838
}
39-
const paramNames = getRouteRegex(record.path).keys
39+
const paramNames = record.regex.keys
4040
.filter(key => !key.optional)
4141
.map(key => key.name)
4242

@@ -60,8 +60,9 @@ export function createMatcher (
6060
location.params = {}
6161
for (let i = 0; i < pathList.length; i++) {
6262
const path = pathList[i]
63-
if (matchRoute(path, location.params, location.path)) {
64-
return _createRoute(pathMap[path], location, redirectedFrom)
63+
const record = pathMap[path]
64+
if (matchRoute(record.regex, location.path, location.params)) {
65+
return _createRoute(record, location, redirectedFrom)
6566
}
6667
}
6768
}
@@ -171,12 +172,11 @@ export function createMatcher (
171172
}
172173

173174
function matchRoute (
175+
regex: RouteRegExp,
174176
path: string,
175-
params: Object,
176-
pathname: string
177+
params: Object
177178
): boolean {
178-
const { regexp, keys } = getRouteRegex(path)
179-
const m = pathname.match(regexp)
179+
const m = path.match(regex)
180180

181181
if (!m) {
182182
return false
@@ -185,9 +185,11 @@ function matchRoute (
185185
}
186186

187187
for (let i = 1, len = m.length; i < len; ++i) {
188-
const key = keys[i - 1]
188+
const key = regex.keys[i - 1]
189189
const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
190-
if (key) params[key.name] = val
190+
if (key) {
191+
params[key.name] = val
192+
}
191193
}
192194

193195
return true

src/create-route-map.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* @flow */
22

3-
import { assert, warn } from './util/warn'
3+
import Regexp from 'path-to-regexp'
44
import { cleanPath } from './util/path'
5+
import { assert, warn } from './util/warn'
56

67
export function createRouteMap (
78
routes: Array<RouteConfig>,
@@ -56,8 +57,10 @@ function addRouteRecord (
5657
)
5758
}
5859

60+
const normalizedPath = normalizePath(path, parent)
5961
const record: RouteRecord = {
60-
path: normalizePath(path, parent),
62+
path: normalizedPath,
63+
regex: compileRouteRegex(normalizedPath),
6164
components: route.components || { default: route.component },
6265
instances: {},
6366
name,
@@ -133,6 +136,18 @@ function addRouteRecord (
133136
}
134137
}
135138

139+
function compileRouteRegex (path: string): RouteRegExp {
140+
const regex = Regexp(path)
141+
if (process.env.NODE_ENV !== 'production') {
142+
const keys: any = {}
143+
regex.keys.forEach(key => {
144+
warn(!keys[key], `Duplicate param keys in route with path: "${path}"`)
145+
keys[key] = true
146+
})
147+
}
148+
return regex
149+
}
150+
136151
function normalizePath (path: string, parent?: RouteRecord): string {
137152
path = path.replace(/\/$/, '')
138153
if (path[0] === '/') return path

src/util/params.js

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,7 @@
11
/* @flow */
22

3-
import Regexp from 'path-to-regexp'
43
import { warn } from './warn'
5-
6-
const regexpCache: {
7-
[key: string]: {
8-
keys: Array<?{ name: string }>,
9-
regexp: RegExp
10-
}
11-
} = Object.create(null)
12-
13-
export function getRouteRegex (path: string): Object {
14-
const hit = regexpCache[path]
15-
let keys, regexp
16-
17-
if (hit) {
18-
keys = hit.keys
19-
regexp = hit.regexp
20-
} else {
21-
keys = []
22-
regexp = Regexp(path, keys)
23-
regexpCache[path] = { keys, regexp }
24-
}
25-
26-
return { keys, regexp }
27-
}
4+
import Regexp from 'path-to-regexp'
285

296
const regexpCompileCache: {
307
[key: string]: Function

test/unit/specs/create-map.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,18 @@ describe('Creating Route Map', function () {
6060
maps = createRouteMap(routes)
6161
expect(console.warn).not.toHaveBeenCalled()
6262
})
63+
64+
it('in development, warn duplicate param keys', () => {
65+
process.env.NODE_ENV = 'development'
66+
maps = createRouteMap([
67+
{
68+
path: '/foo/:id', component: Foo,
69+
children: [
70+
{ path: 'bar/:id', component: Bar }
71+
]
72+
}
73+
])
74+
expect(console.warn).toHaveBeenCalled()
75+
expect(console.warn.calls.argsFor(0)[0]).toMatch('vue-router] Duplicate param keys in route with path: "/foo/:id/bar/:id"')
76+
})
6377
})

test/unit/specs/create-matcher.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('Creating Matcher', function () {
2323
process.env.NODE_ENV = 'development'
2424
expect(() => {
2525
match({ name: 'bar' }, routes[0]);
26-
}).toThrow(new TypeError('Cannot read property \'path\' of undefined'));
26+
}).toThrow(new TypeError('Cannot read property \'regex\' of undefined'));
2727
expect(console.warn).toHaveBeenCalled()
2828
expect(console.warn.calls.argsFor(0)[0]).toMatch('Route with name \'bar\' does not exist');
2929
})

types/router.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface RouteConfig {
7575

7676
export interface RouteRecord {
7777
path: string;
78+
regex: RegExp;
7879
components: Dictionary<Component>;
7980
instances: Dictionary<Vue>;
8081
name?: string;

0 commit comments

Comments
 (0)