From 7852bbf591691ffcdea70a63eea99d8c6f8967dc Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 5 Feb 2022 14:37:14 -0300 Subject: [PATCH 1/6] feat(static matching): Static matching optimization. --- src/create-matcher.js | 6 + test/unit/specs/create-matcher.spec.js | 159 +++++++++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/src/create-matcher.js b/src/create-matcher.js index f7d72b93e..366b4878a 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -82,6 +82,12 @@ export function createMatcher ( return _createRoute(record, location, redirectedFrom) } else if (location.path) { location.params = {} + const staticRecord = pathMap[location.path] + if (staticRecord) { + if (matchRoute(staticRecord.regex, location.path, location.params)) { + return _createRoute(staticRecord, location, redirectedFrom) + } + } for (let i = 0; i < pathList.length; i++) { const path = pathList[i] const record = pathMap[path] diff --git a/test/unit/specs/create-matcher.spec.js b/test/unit/specs/create-matcher.spec.js index fcc2587c2..97da43b51 100644 --- a/test/unit/specs/create-matcher.spec.js +++ b/test/unit/specs/create-matcher.spec.js @@ -151,4 +151,163 @@ describe('Creating Matcher', function () { expect(pathForErrorRoute).toEqual('/error/') expect(pathForNotFoundRoute).toEqual('/') }) + + it('respect ordering', function () { + const matcher = createMatcher([ + { + path: '/p/staticbefore', + name: 'staticbefore', + component: { name: 'staticbefore' } + }, + { + path: '/p/:id', + name: 'dynamic', + component: { name: 'dynamic' } + }, + { + path: '/p/staticafter', + name: 'staticafter', + component: { name: 'staticafter' } + } + ]) + expect(matcher.match('/p/staticbefore').name).toBe('staticbefore') + expect(matcher.match('/p/staticafter').name).toBe('dynamic') + }) + + it('respect ordering for full dynamic', function () { + const matcher = createMatcher([ + { + path: '/before/static', + name: 'staticbefore', + component: { name: 'staticbefore' } + }, + { + path: '/:foo/static', + name: 'dynamic', + component: { name: 'dynamic' } + }, + { + path: '/after/static', + name: 'staticafter', + component: { name: 'staticafter' } + } + ]) + expect(matcher.match('/before/static').name).toBe('staticbefore') + expect(matcher.match('/after/static').name).toBe('dynamic') + }) + + it('respect ordering between full dynamic and first level static', function () { + const matcher = createMatcher([ + { + path: '/before/:foo', + name: 'staticbefore', + component: { name: 'staticbefore' } + }, + { + path: '/:foo/static', + name: 'dynamic', + component: { name: 'dynamic' } + }, + { + path: '/after/:foo', + name: 'staticafter', + component: { name: 'staticafter' } + } + ]) + expect(matcher.match('/before/static').name).toBe('staticbefore') + expect(matcher.match('/after/static').name).toBe('dynamic') + }) + + it('static can use sensitive flag', function () { + const matcher = createMatcher([ + { + path: '/p/sensitive', + name: 'sensitive', + pathToRegexpOptions: { + sensitive: true + }, + component: { name: 'sensitive' } + }, + { + path: '/p/insensitive', + name: 'insensitive', + component: { name: 'insensitive' } + }, + { + path: '*', name: 'not-found', component: { name: 'not-found ' } + } + ]) + + expect(matcher.match('/p/SENSITIVE').name).toBe('not-found') + expect(matcher.match('/p/INSENSITIVE').name).toBe('insensitive') + }) + + it('static can use strict flag', function () { + const matcher = createMatcher([ + { + path: '/p/strict', + name: 'strict', + pathToRegexpOptions: { + strict: true + }, + component: { name: 'strict' } + }, + { + path: '/p/unstrict', + name: 'unstrict', + component: { name: 'unstrict' } + }, + { + path: '*', name: 'not-found', component: { name: 'not-found ' } + } + ]) + + expect(matcher.match('/p/strict/').name).toBe('not-found') + expect(matcher.match('/p/unstrict/').name).toBe('unstrict') + }) + + it('static can use end flag', function () { + const matcher = createMatcher([ + { + path: '/p/end', + name: 'end', + component: { name: 'end' } + }, + { + path: '/p/not-end', + name: 'not-end', + pathToRegexpOptions: { + end: false + }, + component: { name: 'not-end' } + }, + { + path: '*', name: 'not-found', component: { name: 'not-found ' } + } + ]) + + expect(matcher.match('/p/end/foo').name).toBe('not-found') + expect(matcher.match('/p/not-end/foo').name).toBe('not-end') + }) + + it('first level dynamic must work', function () { + const matcher = createMatcher([ + { + path: '/:foo/b', + name: 'b', + component: { name: 'b' } + }, + { + path: '/p/c', + name: 'c', + component: { name: 'c' } + }, + { + path: '*', name: 'not-found', component: { name: 'not-found ' } + } + ]) + + expect(matcher.match('/p/b').name).toBe('b') + expect(matcher.match('/p/c').name).toBe('c') + }) }) From 34b32fc659b3e86e1f4f645f2462ab1f9733066a Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 5 Feb 2022 15:11:02 -0300 Subject: [PATCH 2/6] feat(static matching): Ignore routes that will never match. --- src/create-route-map.js | 8 ++++++-- test/unit/specs/create-matcher.spec.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/create-route-map.js b/src/create-route-map.js index 93a558f8a..ffcaa241f 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -145,8 +145,12 @@ function addRouteRecord ( } if (!pathMap[record.path]) { - pathList.push(record.path) - pathMap[record.path] = record + if (!Object.values(pathMap).some(_record => { + return _record.path !== '*' && record.path.match(_record.regex) + })) { + pathList.push(record.path) + pathMap[record.path] = record + } } if (route.alias !== undefined) { diff --git a/test/unit/specs/create-matcher.spec.js b/test/unit/specs/create-matcher.spec.js index 97da43b51..47c434a72 100644 --- a/test/unit/specs/create-matcher.spec.js +++ b/test/unit/specs/create-matcher.spec.js @@ -54,6 +54,24 @@ describe('Creating Matcher', function () { expect(matcher.match('/p/c/n').name).toBe('nested') }) + it('match default child', function () { + const component = { name: 'fake' } + const matcher = createMatcher([ + { + path: '/p', + name: 'parent', + children: [ + { path: '', name: 'parent.defaultchild', component }, + { path: 'a', name: 'parent.achild', component } + ] + }, + { path: '*', name: 'not-found', component } + ]) + expect(matcher.match('/p').name).toBe('parent') + expect(matcher.match('/p/').name).toBe('parent.defaultchild') + expect(matcher.match('/p/a').name).toBe('parent.achild') + }) + it('can get all routes', function () { const component = { name: 'fake' } const matcher = createMatcher([]) From 645780e37c29ee254de70c7d7c1dc0dc1331ce8e Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 5 Feb 2022 15:34:41 -0300 Subject: [PATCH 3/6] test(static matching): Remove create map spec implementation detail. --- test/unit/specs/create-map.spec.js | 2 -- test/unit/specs/create-matcher.spec.js | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/unit/specs/create-map.spec.js b/test/unit/specs/create-map.spec.js index 57c81afe1..f8aa31e9f 100644 --- a/test/unit/specs/create-map.spec.js +++ b/test/unit/specs/create-map.spec.js @@ -61,9 +61,7 @@ describe('Creating Route Map', function () { '', '/foo', '/bar/', - '/bar', '/bar-redirect/', - '/bar-redirect', '*' ]) }) diff --git a/test/unit/specs/create-matcher.spec.js b/test/unit/specs/create-matcher.spec.js index 47c434a72..2279e8d49 100644 --- a/test/unit/specs/create-matcher.spec.js +++ b/test/unit/specs/create-matcher.spec.js @@ -61,15 +61,15 @@ describe('Creating Matcher', function () { path: '/p', name: 'parent', children: [ - { path: '', name: 'parent.defaultchild', component }, - { path: 'a', name: 'parent.achild', component } + { path: '', name: 'parent.default', component }, + { path: 'a', name: 'parent.a', component } ] }, { path: '*', name: 'not-found', component } ]) - expect(matcher.match('/p').name).toBe('parent') - expect(matcher.match('/p/').name).toBe('parent.defaultchild') - expect(matcher.match('/p/a').name).toBe('parent.achild') + expect(matcher.match('/p').name).toBe('parent.default') + expect(matcher.match('/p/').name).toBe('parent.default') + expect(matcher.match('/p/a').name).toBe('parent.a') }) it('can get all routes', function () { From 60ff22c107a84e72287bc3c81819adf871e09d47 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 5 Feb 2022 15:39:48 -0300 Subject: [PATCH 4/6] chore: Cleanup. --- src/create-route-map.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/create-route-map.js b/src/create-route-map.js index ffcaa241f..9990c7e80 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -144,13 +144,12 @@ function addRouteRecord ( }) } - if (!pathMap[record.path]) { - if (!Object.values(pathMap).some(_record => { - return _record.path !== '*' && record.path.match(_record.regex) - })) { - pathList.push(record.path) - pathMap[record.path] = record - } + if ( + !pathMap[record.path] && + !Object.values(pathMap).some(r => r.path.indexOf('*') === -1 && record.path.match(r.regex)) + ) { + pathList.push(record.path) + pathMap[record.path] = record } if (route.alias !== undefined) { From f533dfa5d145e30d777ed21ac1b9c162a0ca1252 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 5 Feb 2022 16:33:44 -0300 Subject: [PATCH 5/6] fix: flow --- src/create-matcher.js | 13 ++++++------- src/create-route-map.js | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/create-matcher.js b/src/create-matcher.js index 366b4878a..0b8f1b7e1 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -54,7 +54,7 @@ export function createMatcher ( redirectedFrom?: Location ): Route { const location = normalizeLocation(raw, currentRoute, false, router) - const { name } = location + const { name, path } = location if (name) { const record = nameMap[name] @@ -80,18 +80,17 @@ export function createMatcher ( location.path = fillParams(record.path, location.params, `named route "${name}"`) return _createRoute(record, location, redirectedFrom) - } else if (location.path) { + } else if (path) { location.params = {} - const staticRecord = pathMap[location.path] + const staticRecord = pathMap[path] if (staticRecord) { - if (matchRoute(staticRecord.regex, location.path, location.params)) { + if (matchRoute(staticRecord.regex, path, location.params)) { return _createRoute(staticRecord, location, redirectedFrom) } } for (let i = 0; i < pathList.length; i++) { - const path = pathList[i] - const record = pathMap[path] - if (matchRoute(record.regex, location.path, location.params)) { + const record = pathMap[pathList[i]] + if (matchRoute(record.regex, path, location.params)) { return _createRoute(record, location, redirectedFrom) } } diff --git a/src/create-route-map.js b/src/create-route-map.js index 9990c7e80..9315c1a8f 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -144,10 +144,10 @@ function addRouteRecord ( }) } - if ( - !pathMap[record.path] && - !Object.values(pathMap).some(r => r.path.indexOf('*') === -1 && record.path.match(r.regex)) - ) { + if (!pathMap[record.path] && !pathList.some(pathItem => { + const r = pathMap[pathItem] + return r.path.indexOf('*') === -1 && record.path.match(r.regex) + })) { pathList.push(record.path) pathMap[record.path] = record } From 5b6ed7c3ccf3633b73d538680bd3aceb1e34c517 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Tue, 8 Feb 2022 16:12:27 -0300 Subject: [PATCH 6/6] test(static matching): Test strict child. --- test/unit/specs/create-map.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/specs/create-map.spec.js b/test/unit/specs/create-map.spec.js index f8aa31e9f..bdd24ef2f 100644 --- a/test/unit/specs/create-map.spec.js +++ b/test/unit/specs/create-map.spec.js @@ -19,6 +19,7 @@ const routes = [ children: [ { path: '', + pathToRegexpOptions: { strict: true }, component: Baz, name: 'bar.baz' } @@ -61,6 +62,7 @@ describe('Creating Route Map', function () { '', '/foo', '/bar/', + '/bar', '/bar-redirect/', '*' ])