Skip to content

Commit 3c0428c

Browse files
committed
Merge branch 'resolve-route' into route-option
2 parents 5cda79f + 31ac4c9 commit 3c0428c

12 files changed

+152
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Resolve Route FullPath
2+
3+
## Includes Query And Hash
4+
5+
- Search Query: {{ JSON.stringify(resolveRoute('/?query=1')) }}
6+
- Hash: {{ JSON.stringify(resolveRoute('/#hash')) }}
7+
- Search Query And Hash: {{ JSON.stringify(resolveRoute('/?query=1#hash')) }}
8+
- Permalink And Search Query: {{ JSON.stringify(resolveRoute('/routes/permalinks/ascii-non-ascii.md?query=1')) }}
9+
10+
<script setup>
11+
import { resolveRoute } from 'vuepress/client'
12+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
const testCases = [
4+
{
5+
path: '/?query=1',
6+
notFound: false,
7+
},
8+
{
9+
path: '/#hash',
10+
notFound: false,
11+
},
12+
{
13+
path: '/?query=1#hash',
14+
notFound: false,
15+
},
16+
{
17+
path: encodeURI('/永久链接-ascii-中文/?query=1'),
18+
notFound: false,
19+
},
20+
]
21+
22+
test('should resolve routes when including both the query and hash', async ({
23+
page,
24+
}) => {
25+
const listItemsLocator = await page
26+
.locator('.e2e-theme-content #includes-query-and-hash + ul > li')
27+
.all()
28+
29+
for (const [index, li] of listItemsLocator.entries()) {
30+
const textContent = await li.textContent()
31+
const resolvedRoute = JSON.parse(/: (\{.*\})\s*$/.exec(textContent!)![1])
32+
expect(resolvedRoute.path).toEqual(testCases[index].path)
33+
expect(resolvedRoute.notFound).toEqual(testCases[index].notFound)
34+
}
35+
})

packages/client/src/components/RouteLink.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { computed, defineComponent, h } from 'vue'
22
import type { SlotsType, VNode } from 'vue'
33
import { useRoute, useRouter } from 'vue-router'
4-
import { resolveRoutePath } from '../router/index.js'
4+
import { resolveRouteFullPath } from '../router/index.js'
55

66
/**
77
* Forked from https://github.com/vuejs/router/blob/941b2131e80550009e5221d4db9f366b1fea3fd5/packages/router/src/RouterLink.ts#L293
@@ -91,7 +91,7 @@ export const RouteLink = defineComponent({
9191
const path = computed(() =>
9292
props.to.startsWith('#') || props.to.startsWith('?')
9393
? props.to
94-
: `${__VUEPRESS_BASE__}${resolveRoutePath(props.to, route.path).substring(1)}`,
94+
: `${__VUEPRESS_BASE__}${resolveRouteFullPath(props.to, route.path).substring(1)}`,
9595
)
9696

9797
return () =>

packages/client/src/router/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
22
export { useRoute, useRouter } from 'vue-router'
33

44
export * from './resolveRoute.js'
5+
export * from './resolveRouteFullPath.js'
56
export * from './resolveRoutePath.js'

packages/client/src/router/resolveRoute.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolveRoutePathInfo } from '@vuepress/shared'
12
import { routes } from '../internal/routes.js'
23
import type { Route, RouteMeta } from '../types/index.js'
34
import { resolveRoutePath } from './resolveRoutePath.js'
@@ -15,15 +16,25 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
1516
path: string,
1617
currentPath?: string,
1718
): ResolvedRoute<T> => {
18-
const routePath = resolveRoutePath(path, currentPath)
19-
const route = routes.value[routePath] ?? {
20-
...routes.value['/404.html'],
21-
notFound: true,
19+
// get only the pathname from the path
20+
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)
21+
22+
// resolve the route path
23+
const routePath = resolveRoutePath(pathname, currentPath)
24+
const routeFullPath = routePath + hashAndQueries
25+
26+
// the route not found
27+
if (!routes.value[routePath]) {
28+
return {
29+
...routes.value['/404.html'],
30+
path: routeFullPath,
31+
notFound: true,
32+
} as ResolvedRoute<T>
2233
}
2334

2435
return {
25-
path: routePath,
36+
...routes.value[routePath],
37+
path: routeFullPath,
2638
notFound: false,
27-
...route,
2839
} as ResolvedRoute<T>
2940
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { resolveRoutePathInfo } from '@vuepress/shared'
2+
import { resolveRoutePath } from './resolveRoutePath.js'
3+
4+
/**
5+
* Resolve route full path with given raw path
6+
*/
7+
export const resolveRouteFullPath = (
8+
path: string,
9+
currentPath?: string,
10+
): string => {
11+
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)
12+
13+
return resolveRoutePath(pathname, currentPath) + hashAndQueries
14+
}

packages/client/src/router/resolveRoutePath.ts

+22-13
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,34 @@ declare const __VUEPRESS_CLEAN_URL__: boolean
77
* Resolve route path with given raw path
88
*/
99
export const resolveRoutePath = (
10-
path: string,
10+
pathname: string,
1111
currentPath?: string,
1212
): string => {
1313
// normalized path
14-
const normalizedPath = normalizeRoutePath(
15-
path,
14+
const normalizedRoutePath = normalizeRoutePath(
15+
pathname,
1616
currentPath,
1717
__VUEPRESS_CLEAN_URL__,
1818
)
19-
if (routes.value[normalizedPath]) return normalizedPath
2019

21-
// encoded path
22-
const encodedPath = encodeURI(normalizedPath)
23-
if (routes.value[encodedPath]) return encodedPath
20+
// check if the normalized path is in routes
21+
if (routes.value[normalizedRoutePath]) return normalizedRoutePath
2422

25-
// redirected path or fallback to the normalized path
26-
return (
27-
redirects.value[normalizedPath] ||
28-
redirects.value[encodedPath] ||
29-
normalizedPath
30-
)
23+
// check encoded path
24+
const encodedRoutePath = encodeURI(normalizedRoutePath)
25+
26+
if (routes.value[encodedRoutePath]) {
27+
return encodedRoutePath
28+
}
29+
30+
// check redirected path with normalized path and encoded path
31+
const redirectedRoutePath =
32+
redirects.value[normalizedRoutePath] || redirects.value[encodedRoutePath]
33+
34+
if (redirectedRoutePath) {
35+
return redirectedRoutePath
36+
}
37+
38+
// default to normalized route path
39+
return normalizedRoutePath
3140
}

packages/shared/src/utils/routes/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './inferRoutePath'
22
export * from './normalizeRoutePath.js'
33
export * from './resolveLocalePath.js'
44
export * from './resolveRoutePathFromUrl.js'
5+
export * from './resolveRoutePathInfo.js'

packages/shared/src/utils/routes/normalizeRoutePath.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,22 @@ import { inferRoutePath } from './inferRoutePath.js'
33
const FAKE_HOST = 'http://.'
44

55
/**
6-
* Normalize the given path to the final route path
6+
* Normalize the given pathname path to the final route path
77
*/
88
export const normalizeRoutePath = (
9-
path: string,
9+
pathname: string,
1010
current?: string,
1111
cleanUrl = false,
1212
): string => {
13-
if (!path.startsWith('/') && current) {
13+
if (!pathname.startsWith('/') && current) {
1414
// the relative path should be resolved against the current path
1515
const loc = current.slice(0, current.lastIndexOf('/'))
1616

17-
const { pathname, search, hash } = new URL(`${loc}/${path}`, FAKE_HOST)
18-
19-
return inferRoutePath(pathname, cleanUrl) + search + hash
17+
return inferRoutePath(
18+
new URL(`${loc}/${pathname}`, FAKE_HOST).pathname,
19+
cleanUrl,
20+
)
2021
}
2122

22-
const [pathname, ...queryAndHash] = path.split(/(\?|#)/)
23-
24-
return inferRoutePath(pathname, cleanUrl) + queryAndHash.join('')
23+
return inferRoutePath(pathname, cleanUrl)
2524
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const SPLIT_CHAR_REGEXP = /(#|\?)/
2+
3+
/**
4+
* Extract pathname / hash and queries from a full route path
5+
*/
6+
export const resolveRoutePathInfo = (
7+
path: string,
8+
): [pathname: string, hashAndQueries: string] => {
9+
const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP)
10+
11+
return [pathname, hashAndQueries.join('')]
12+
}

packages/shared/tests/routes/normalizeRoutePath.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ describe('default', () => {
198198
[['/foo/.md', '/a/b.html'], '/foo/.html'],
199199
]
200200

201+
describe('should normalize clean paths correctly', () => {
202+
testCases.forEach(([[path, current], expected]) =>
203+
it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => {
204+
expect(normalizeRoutePath(path, current)).toBe(expected)
205+
}),
206+
)
207+
})
201208
describe('should normalize clean paths correctly', () => {
202209
testCases.forEach(([[path, current], expected]) =>
203210
it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { resolveRoutePathInfo } from '../../src/index.js'
3+
4+
const testCases: [string, [string, string]][] = [
5+
['/a/b/c/', ['/a/b/c/', '']],
6+
['/a/b/c/?a=1', ['/a/b/c/', '?a=1']],
7+
['/a/b/c/#b', ['/a/b/c/', '#b']],
8+
['/a/b/c/?a=1#b', ['/a/b/c/', '?a=1#b']],
9+
['a/index.html', ['a/index.html', '']],
10+
['/a/index.html?a=1', ['/a/index.html', '?a=1']],
11+
['/a/index.html#a', ['/a/index.html', '#a']],
12+
['/a/index.html?a=1#b', ['/a/index.html', '?a=1#b']],
13+
]
14+
15+
describe('should resolve route path info correctly', () => {
16+
testCases.forEach(([source, expected]) => {
17+
it(`${source} -> ${expected}`, () => {
18+
expect(resolveRoutePathInfo(source)).toEqual(expected)
19+
})
20+
})
21+
})

0 commit comments

Comments
 (0)