Skip to content

Commit

Permalink
fix(router): add path-to-regexp back
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhengYuTay authored Sep 8, 2020
1 parent a032c14 commit 6a2266a
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 36 deletions.
1 change: 1 addition & 0 deletions packages/router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"dependencies": {
"@shuvi/utils": "^0.0.1-beta.50",
"path-to-regexp": "1.7.0",
"query-string": "6.13.1"
},
"devDependencies": {}
Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/__tests__/matchPath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('matchPath', () => {
'*': '123'
},
path: '/*',
pathname: ''
pathname: '/123'
});

expect(
Expand Down
177 changes: 177 additions & 0 deletions packages/router/src/__tests__/matchRoutes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,180 @@ describe('matchRoutes', () => {
]);
});
});

describe('path matching', () => {
function pickPaths(routes: any, pathname: any) {
let matches = matchRoutes(routes, { pathname });
return matches ? matches.map(match => match.route.path) : null;
}

test('root vs. dynamic', () => {
let routes = [{ path: '/' }, { path: ':id' }];
expect(pickPaths(routes, '/')).toEqual(['/']);
expect(pickPaths(routes, '/123')).toEqual([':id']);
});

test('precedence of a bunch of routes in a flat route config', () => {
let routes = [
{ path: '/groups/main/users/me' },
{ path: '/groups/:groupId/users/me' },
{ path: '/groups/:groupId/users/:userId' },
{ path: '/groups/:groupId/users/*' },
{ path: '/groups/main/users' },
{ path: '/groups/:groupId/users' },
{ path: '/groups/main' },
{ path: '/groups/:groupId' },
{ path: '/groups' },
{ path: '/files/*' },
{ path: '/files' },
{ path: '/:one/:two/:three/:four/:five' },
{ path: '/' },
{ path: '*' }
];

expect(pickPaths(routes, '/groups/main/users/me')).toEqual([
'/groups/main/users/me'
]);
expect(pickPaths(routes, '/groups/other/users/me')).toEqual([
'/groups/:groupId/users/me'
]);
expect(pickPaths(routes, '/groups/123/users/456')).toEqual([
'/groups/:groupId/users/:userId'
]);
expect(pickPaths(routes, '/groups/main/users/a/b')).toEqual([
'/groups/:groupId/users/*'
]);
expect(pickPaths(routes, '/groups/main/users')).toEqual([
'/groups/main/users'
]);
expect(pickPaths(routes, '/groups/123/users')).toEqual([
'/groups/:groupId/users'
]);
expect(pickPaths(routes, '/groups/main')).toEqual(['/groups/main']);
expect(pickPaths(routes, '/groups/123')).toEqual(['/groups/:groupId']);
expect(pickPaths(routes, '/groups')).toEqual(['/groups']);
expect(pickPaths(routes, '/files/some/long/path')).toEqual(['/files/*']);
expect(pickPaths(routes, '/files')).toEqual(['/files']);
expect(pickPaths(routes, '/one/two/three/four/five')).toEqual([
'/:one/:two/:three/:four/:five'
]);
expect(pickPaths(routes, '/')).toEqual(['/']);
expect(pickPaths(routes, '/no/where')).toEqual(['*']);
});

test('precedence of a bunch of routes in a nested route config', () => {
let routes = [
{
path: 'courses',
children: [
{
path: ':id',
children: [{ path: 'subjects' }]
},
{ path: 'new' },
{ path: '/' },
{ path: '*' }
]
},
{
path: 'courses',
children: [
{ path: 'react-fundamentals' },
{ path: 'advanced-react' },
{ path: '*' }
]
},
{ path: '/' },
{ path: '*' }
];

expect(pickPaths(routes, '/courses')).toEqual(['courses', '/']);
expect(pickPaths(routes, '/courses/routing')).toEqual(['courses', ':id']);
expect(pickPaths(routes, '/courses/routing/subjects')).toEqual([
'courses',
':id',
'subjects'
]);
expect(pickPaths(routes, '/courses/new')).toEqual(['courses', 'new']);
expect(pickPaths(routes, '/courses/whatever/path')).toEqual([
'courses',
'*'
]);
expect(pickPaths(routes, '/courses/react-fundamentals')).toEqual([
'courses',
'react-fundamentals'
]);
expect(pickPaths(routes, '/courses/advanced-react')).toEqual([
'courses',
'advanced-react'
]);
expect(pickPaths(routes, '/')).toEqual(['/']);
expect(pickPaths(routes, '/whatever')).toEqual(['*']);
});

test('nested index route vs sibling static route', () => {
let routes = [
{
path: ':page',
children: [{ path: '/' }]
},
{ path: 'page' }
];

expect(pickPaths(routes, '/page')).toEqual(['page']);
});
});

describe('path matching with a basename', () => {
let routes = [
{
path: '/users/:userId',
children: [
{
path: 'subjects',
children: [
{
path: ':courseId'
}
]
}
]
}
];

test('top-level route', () => {
let location = { pathname: '/app/users/michael' };
let matches = matchRoutes(routes, location, '/app');

expect(matches).not.toBeNull();
expect(matches).toHaveLength(1);
expect(matches).toMatchObject([
{
pathname: '/users/michael',
params: { userId: 'michael' }
}
]);
});

test('deeply nested route', () => {
let location = { pathname: '/app/users/michael/subjects/react' };
let matches = matchRoutes(routes, location, '/app');

expect(matches).not.toBeNull();
expect(matches).toHaveLength(3);
expect(matches).toMatchObject([
{
pathname: '/users/michael',
params: { userId: 'michael' }
},
{
pathname: '/users/michael/subjects',
params: { userId: 'michael' }
},
{
pathname: '/users/michael/subjects/react',
params: { userId: 'michael', courseId: 'react' }
}
]);
});
});
51 changes: 17 additions & 34 deletions packages/router/src/matchPath.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
import pathToRegexp, { Key, PathRegExp } from 'path-to-regexp';
import { IPathPattern, IPathMatch, IParams } from './types';

function compilePath(
path: string,
caseSensitive: boolean,
end: boolean
): [RegExp, string[]] {
let keys: string[] = [];
let source =
'^(' +
path
.replace(/^\/*/, '/') // Make sure it has a leading /
.replace(/\/?\*?$/, '') // Ignore trailing / and /*, we'll handle it below
.replace(/[\\.*+^$?{}|()[\]]/g, '\\$&') // Escape special regex chars
.replace(/:(\w+)/g, (_: string, key: string) => {
keys.push(key);
return '([^\\/]+)';
}) +
')';
): [PathRegExp, Key[]] {
const keys: Key[] = [];

if (path.endsWith('*')) {
if (path.endsWith('/*')) {
source += '\\/?'; // Don't include the / in params['*']
}
keys.push('*');
source += '(.*)';
} else if (end) {
source += '\\/?';
}

if (end) source += '$';
const source = path.replace(/^\/*/, '/'); // Make sure it has a leading /
const regexp = pathToRegexp(source, keys, { end, sensitive: caseSensitive });

let flags = caseSensitive ? undefined : 'i';
let matcher = new RegExp(source, flags);

return [matcher, keys];
return [regexp, keys];
}

function safelyDecodeURIComponent(value: string, paramName: string) {
Expand All @@ -59,15 +37,20 @@ export function matchPath(
}

let { path, caseSensitive = false, end = true } = pattern;
let [matcher, paramNames] = compilePath(path, caseSensitive, end);
let match = pathname.match(matcher);

let [matcher, keys] = compilePath(path, caseSensitive, end);

const match = matcher.exec(pathname);

if (!match) return null;

let matchedPathname = match[1];
let values = match.slice(2);
let params = paramNames.reduce((memo, paramName, index) => {
memo[paramName] = safelyDecodeURIComponent(values[index], paramName);
let [matchedPathname, ...values] = match;

let params = keys.reduce((memo, key, index) => {
memo[key.name || '*'] = safelyDecodeURIComponent(
values[index],
String(key.name)
);
return memo;
}, {} as IParams);

Expand Down
14 changes: 13 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1336,7 +1336,7 @@
globals "^11.1.0"
lodash "^4.17.13"

"@babel/types@7.10.1", "@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.7.2", "@babel/types@^7.7.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.7.2", "@babel/types@^7.7.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
version "7.10.1"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921"
integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==
Expand Down Expand Up @@ -6710,6 +6710,11 @@ is-wsl@^2.1.1:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==

[email protected]:
version "0.0.1"
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=

[email protected], isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
Expand Down Expand Up @@ -8983,6 +8988,13 @@ [email protected]:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=

[email protected]:
version "1.7.0"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
dependencies:
isarray "0.0.1"

path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
Expand Down

0 comments on commit 6a2266a

Please sign in to comment.