Skip to content

Commit

Permalink
Merge branch 'resolve-route' into route-option
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed May 22, 2024
2 parents 5cda79f + 31ac4c9 commit 3c0428c
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 30 deletions.
12 changes: 12 additions & 0 deletions e2e/docs/router/resolve-route-query-hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Resolve Route FullPath

## Includes Query And Hash

- Search Query: {{ JSON.stringify(resolveRoute('/?query=1')) }}
- Hash: {{ JSON.stringify(resolveRoute('/#hash')) }}
- Search Query And Hash: {{ JSON.stringify(resolveRoute('/?query=1#hash')) }}
- Permalink And Search Query: {{ JSON.stringify(resolveRoute('/routes/permalinks/ascii-non-ascii.md?query=1')) }}

<script setup>
import { resolveRoute } from 'vuepress/client'
</script>
35 changes: 35 additions & 0 deletions e2e/tests/router/resolve-route-query-hash.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from '@playwright/test'

const testCases = [
{
path: '/?query=1',
notFound: false,
},
{
path: '/#hash',
notFound: false,
},
{
path: '/?query=1#hash',
notFound: false,
},
{
path: encodeURI('/永久链接-ascii-中文/?query=1'),
notFound: false,
},
]

test('should resolve routes when including both the query and hash', async ({
page,
}) => {
const listItemsLocator = await page
.locator('.e2e-theme-content #includes-query-and-hash + ul > li')
.all()

for (const [index, li] of listItemsLocator.entries()) {
const textContent = await li.textContent()
const resolvedRoute = JSON.parse(/: (\{.*\})\s*$/.exec(textContent!)![1])
expect(resolvedRoute.path).toEqual(testCases[index].path)
expect(resolvedRoute.notFound).toEqual(testCases[index].notFound)
}
})
4 changes: 2 additions & 2 deletions packages/client/src/components/RouteLink.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { computed, defineComponent, h } from 'vue'
import type { SlotsType, VNode } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { resolveRoutePath } from '../router/index.js'
import { resolveRouteFullPath } from '../router/index.js'

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

return () =>
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
export { useRoute, useRouter } from 'vue-router'

export * from './resolveRoute.js'
export * from './resolveRouteFullPath.js'
export * from './resolveRoutePath.js'
23 changes: 17 additions & 6 deletions packages/client/src/router/resolveRoute.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { resolveRoutePathInfo } from '@vuepress/shared'
import { routes } from '../internal/routes.js'
import type { Route, RouteMeta } from '../types/index.js'
import { resolveRoutePath } from './resolveRoutePath.js'
Expand All @@ -15,15 +16,25 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
path: string,
currentPath?: string,
): ResolvedRoute<T> => {
const routePath = resolveRoutePath(path, currentPath)
const route = routes.value[routePath] ?? {
...routes.value['/404.html'],
notFound: true,
// get only the pathname from the path
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)

// resolve the route path
const routePath = resolveRoutePath(pathname, currentPath)
const routeFullPath = routePath + hashAndQueries

// the route not found
if (!routes.value[routePath]) {
return {
...routes.value['/404.html'],
path: routeFullPath,
notFound: true,
} as ResolvedRoute<T>
}

return {
path: routePath,
...routes.value[routePath],
path: routeFullPath,
notFound: false,
...route,
} as ResolvedRoute<T>
}
14 changes: 14 additions & 0 deletions packages/client/src/router/resolveRouteFullPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { resolveRoutePathInfo } from '@vuepress/shared'
import { resolveRoutePath } from './resolveRoutePath.js'

/**
* Resolve route full path with given raw path
*/
export const resolveRouteFullPath = (
path: string,
currentPath?: string,
): string => {
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)

return resolveRoutePath(pathname, currentPath) + hashAndQueries
}
35 changes: 22 additions & 13 deletions packages/client/src/router/resolveRoutePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,34 @@ declare const __VUEPRESS_CLEAN_URL__: boolean
* Resolve route path with given raw path
*/
export const resolveRoutePath = (
path: string,
pathname: string,
currentPath?: string,
): string => {
// normalized path
const normalizedPath = normalizeRoutePath(
path,
const normalizedRoutePath = normalizeRoutePath(
pathname,
currentPath,
__VUEPRESS_CLEAN_URL__,
)
if (routes.value[normalizedPath]) return normalizedPath

// encoded path
const encodedPath = encodeURI(normalizedPath)
if (routes.value[encodedPath]) return encodedPath
// check if the normalized path is in routes
if (routes.value[normalizedRoutePath]) return normalizedRoutePath

// redirected path or fallback to the normalized path
return (
redirects.value[normalizedPath] ||
redirects.value[encodedPath] ||
normalizedPath
)
// check encoded path
const encodedRoutePath = encodeURI(normalizedRoutePath)

if (routes.value[encodedRoutePath]) {
return encodedRoutePath
}

// check redirected path with normalized path and encoded path
const redirectedRoutePath =
redirects.value[normalizedRoutePath] || redirects.value[encodedRoutePath]

if (redirectedRoutePath) {
return redirectedRoutePath
}

// default to normalized route path
return normalizedRoutePath
}
1 change: 1 addition & 0 deletions packages/shared/src/utils/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './inferRoutePath'
export * from './normalizeRoutePath.js'
export * from './resolveLocalePath.js'
export * from './resolveRoutePathFromUrl.js'
export * from './resolveRoutePathInfo.js'
17 changes: 8 additions & 9 deletions packages/shared/src/utils/routes/normalizeRoutePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ import { inferRoutePath } from './inferRoutePath.js'
const FAKE_HOST = 'http://.'

/**
* Normalize the given path to the final route path
* Normalize the given pathname path to the final route path
*/
export const normalizeRoutePath = (
path: string,
pathname: string,
current?: string,
cleanUrl = false,
): string => {
if (!path.startsWith('/') && current) {
if (!pathname.startsWith('/') && current) {
// the relative path should be resolved against the current path
const loc = current.slice(0, current.lastIndexOf('/'))

const { pathname, search, hash } = new URL(`${loc}/${path}`, FAKE_HOST)

return inferRoutePath(pathname, cleanUrl) + search + hash
return inferRoutePath(
new URL(`${loc}/${pathname}`, FAKE_HOST).pathname,
cleanUrl,
)
}

const [pathname, ...queryAndHash] = path.split(/(\?|#)/)

return inferRoutePath(pathname, cleanUrl) + queryAndHash.join('')
return inferRoutePath(pathname, cleanUrl)
}
12 changes: 12 additions & 0 deletions packages/shared/src/utils/routes/resolveRoutePathInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const SPLIT_CHAR_REGEXP = /(#|\?)/

/**
* Extract pathname / hash and queries from a full route path
*/
export const resolveRoutePathInfo = (
path: string,
): [pathname: string, hashAndQueries: string] => {
const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP)

return [pathname, hashAndQueries.join('')]
}
7 changes: 7 additions & 0 deletions packages/shared/tests/routes/normalizeRoutePath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ describe('default', () => {
[['/foo/.md', '/a/b.html'], '/foo/.html'],
]

describe('should normalize clean paths correctly', () => {
testCases.forEach(([[path, current], expected]) =>
it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => {
expect(normalizeRoutePath(path, current)).toBe(expected)
}),
)
})
describe('should normalize clean paths correctly', () => {
testCases.forEach(([[path, current], expected]) =>
it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => {
Expand Down
21 changes: 21 additions & 0 deletions packages/shared/tests/routes/resolveRoutePathInfo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { resolveRoutePathInfo } from '../../src/index.js'

const testCases: [string, [string, string]][] = [
['/a/b/c/', ['/a/b/c/', '']],
['/a/b/c/?a=1', ['/a/b/c/', '?a=1']],
['/a/b/c/#b', ['/a/b/c/', '#b']],
['/a/b/c/?a=1#b', ['/a/b/c/', '?a=1#b']],
['a/index.html', ['a/index.html', '']],
['/a/index.html?a=1', ['/a/index.html', '?a=1']],
['/a/index.html#a', ['/a/index.html', '#a']],
['/a/index.html?a=1#b', ['/a/index.html', '?a=1#b']],
]

describe('should resolve route path info correctly', () => {
testCases.forEach(([source, expected]) => {
it(`${source} -> ${expected}`, () => {
expect(resolveRoutePathInfo(source)).toEqual(expected)
})
})
})

0 comments on commit 3c0428c

Please sign in to comment.