From f6ed49f0ac3c56953350c5f89000e2586d6f465b Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 16:12:45 +0800 Subject: [PATCH 01/17] fix: router beforeResolve `fullPath` error --- packages/client/src/router/createVueRouter.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index a9dd605096..32b211749c 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -40,9 +40,10 @@ export const createVueRouter = (): Router => { // and save page data to route meta router.beforeResolve(async (to, from): Promise => { if (to.path !== from.path || from === START_LOCATION) { - const route = resolveRoute(to.fullPath) + const fullPath = to.fullPath.split('#')[0] + const route = resolveRoute(fullPath) - if (route.path !== to.fullPath) { + if (route.path !== fullPath) { return route.path } const pageChunk = await route.loader() From de2fa24bf587c2bf43a32a9b25c383e06c36c3b6 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 16:31:45 +0800 Subject: [PATCH 02/17] fix: route resolve error --- packages/client/src/router/createVueRouter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index 32b211749c..475cca99ba 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -40,7 +40,8 @@ export const createVueRouter = (): Router => { // and save page data to route meta router.beforeResolve(async (to, from): Promise => { if (to.path !== from.path || from === START_LOCATION) { - const fullPath = to.fullPath.split('#')[0] + console.log(to) + const fullPath = to.fullPath.split(/#|\?/)[0] const route = resolveRoute(fullPath) if (route.path !== fullPath) { From da2436d423352655917b4d67056afdda78a10284 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 16:33:51 +0800 Subject: [PATCH 03/17] chore: tweak --- packages/client/src/router/createVueRouter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index 475cca99ba..69f6b810cd 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -40,7 +40,6 @@ export const createVueRouter = (): Router => { // and save page data to route meta router.beforeResolve(async (to, from): Promise => { if (to.path !== from.path || from === START_LOCATION) { - console.log(to) const fullPath = to.fullPath.split(/#|\?/)[0] const route = resolveRoute(fullPath) From 2b77a53b691de930164e72b860e83b6f7d78cf78 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 16:47:07 +0800 Subject: [PATCH 04/17] chore: tweak --- packages/client/src/router/createVueRouter.ts | 6 ++---- packages/client/src/router/resolveRoute.ts | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index 69f6b810cd..6cbb8fa8e7 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -40,10 +40,8 @@ export const createVueRouter = (): Router => { // and save page data to route meta router.beforeResolve(async (to, from): Promise => { if (to.path !== from.path || from === START_LOCATION) { - const fullPath = to.fullPath.split(/#|\?/)[0] - const route = resolveRoute(fullPath) - - if (route.path !== fullPath) { + const route = resolveRoute(to.fullPath) + if (route.path !== to.fullPath) { return route.path } const pageChunk = await route.loader() diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index e4345c9a9b..bd7a6e8b06 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -16,7 +16,8 @@ export const resolveRoute = ( currentPath?: string, ): ResolvedRoute => { const routePath = resolveRoutePath(path, currentPath) - const route = routes.value[routePath] ?? { + const pathname = routePath.split(/#|\?/)[0] + const route = routes.value[pathname] ?? { ...routes.value['/404.html'], notFound: true, } From 1995208b6b95a18962176796ce507d3ca5cad388 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 17:01:46 +0800 Subject: [PATCH 05/17] chore: tweak --- packages/client/src/router/createVueRouter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/router/createVueRouter.ts b/packages/client/src/router/createVueRouter.ts index 6cbb8fa8e7..a9dd605096 100644 --- a/packages/client/src/router/createVueRouter.ts +++ b/packages/client/src/router/createVueRouter.ts @@ -41,6 +41,7 @@ export const createVueRouter = (): Router => { router.beforeResolve(async (to, from): Promise => { if (to.path !== from.path || from === START_LOCATION) { const route = resolveRoute(to.fullPath) + if (route.path !== to.fullPath) { return route.path } From 6a9c40fdaa1f46175c89fad72d1a26140699bb27 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 18:49:00 +0800 Subject: [PATCH 06/17] chore: tweak --- packages/client/src/router/resolveRoute.ts | 3 ++- .../client/src/router/resolveRoutePath.ts | 14 ++++++------- packages/shared/src/utils/index.ts | 1 + .../shared/src/utils/resolveRoutePathname.ts | 8 +++++++ .../shared/tests/resolveRoutePathname.spec.ts | 21 +++++++++++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 packages/shared/src/utils/resolveRoutePathname.ts create mode 100644 packages/shared/tests/resolveRoutePathname.spec.ts diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index bd7a6e8b06..2a35c223c8 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,3 +1,4 @@ +import { resolveRoutePathname } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' import { resolveRoutePath } from './resolveRoutePath.js' @@ -16,7 +17,7 @@ export const resolveRoute = ( currentPath?: string, ): ResolvedRoute => { const routePath = resolveRoutePath(path, currentPath) - const pathname = routePath.split(/#|\?/)[0] + const pathname = resolveRoutePathname(routePath) const route = routes.value[pathname] ?? { ...routes.value['/404.html'], notFound: true, diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index f5aece8f9b..db1bd15f7b 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -1,4 +1,4 @@ -import { normalizeRoutePath } from '@vuepress/shared' +import { normalizeRoutePath, resolveRoutePathname } from '@vuepress/shared' import { redirects, routes } from '../internal/routes.js' /** @@ -10,16 +10,16 @@ export const resolveRoutePath = ( ): string => { // normalized path const normalizedPath = normalizeRoutePath(path, currentPath) - if (routes.value[normalizedPath]) return normalizedPath - + const normalizedPathname = resolveRoutePathname(normalizedPath) + if (routes.value[normalizedPathname]) return normalizedPath // encoded path - const encodedPath = encodeURI(normalizedPath) - if (routes.value[encodedPath]) return encodedPath + const encodedPathname = encodeURI(normalizedPathname) + if (routes.value[encodedPathname]) return encodeURI(normalizedPath) // redirected path or fallback to the normalized path return ( - redirects.value[normalizedPath] || - redirects.value[encodedPath] || + redirects.value[normalizedPathname] || + redirects.value[encodedPathname] || normalizedPath ) } diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 2d329ba2c3..cbe29c4f2c 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -15,4 +15,5 @@ export * from './removeLeadingSlash.js' export * from './resolveHeadIdentifier.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' +export * from './resolveRoutePathname.js' export * from './typeGuards.js' diff --git a/packages/shared/src/utils/resolveRoutePathname.ts b/packages/shared/src/utils/resolveRoutePathname.ts new file mode 100644 index 0000000000..6dd10d2803 --- /dev/null +++ b/packages/shared/src/utils/resolveRoutePathname.ts @@ -0,0 +1,8 @@ +const SPLIT_CHAR_RE = /#|\?/ + +/** + * Resolve the route pathname + */ +export const resolveRoutePathname = (path: string): string => { + return path.split(SPLIT_CHAR_RE)[0] +} diff --git a/packages/shared/tests/resolveRoutePathname.spec.ts b/packages/shared/tests/resolveRoutePathname.spec.ts new file mode 100644 index 0000000000..9f1f0ef848 --- /dev/null +++ b/packages/shared/tests/resolveRoutePathname.spec.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { resolveRoutePathname } from '../src/index.js' + +const testCases = [ + ['/a/b/c/', '/a/b/c/'], + ['/a/b/c/?a=1', '/a/b/c/'], + ['/a/b/c/#b', '/a/b/c/'], + ['/a/b/c/?a=1#b', '/a/b/c/'], + ['a/index.html', 'a/index.html'], + ['/a/index.html?a=1', '/a/index.html'], + ['/a/index.html#a', '/a/index.html'], + ['/a/index.html?a=1#b', '/a/index.html'], +] + +describe('should resolve route pathname correctly', () => { + testCases.forEach(([source, expected]) => { + it(`${source} -> ${expected}`, () => { + expect(resolveRoutePathname(source)).toBe(expected) + }) + }) +}) From 7648075720e07a262cfde559d619d05306cdc537 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 23:20:23 +0800 Subject: [PATCH 07/17] fix: permalink redirect error --- packages/client/src/router/resolveRoute.ts | 4 ++-- .../client/src/router/resolveRoutePath.ts | 20 +++++++++++------- packages/shared/src/utils/index.ts | 2 +- .../shared/src/utils/normalizeRoutePath.ts | 5 +++-- .../shared/src/utils/resolveRouteFullPath.ts | 10 +++++++++ .../shared/src/utils/resolveRoutePathname.ts | 8 ------- .../shared/tests/resolveRouteFullPath.spec.ts | 21 +++++++++++++++++++ .../shared/tests/resolveRoutePathname.spec.ts | 21 ------------------- 8 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 packages/shared/src/utils/resolveRouteFullPath.ts delete mode 100644 packages/shared/src/utils/resolveRoutePathname.ts create mode 100644 packages/shared/tests/resolveRouteFullPath.spec.ts delete mode 100644 packages/shared/tests/resolveRoutePathname.spec.ts diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index 2a35c223c8..b0d9580a25 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,4 +1,4 @@ -import { resolveRoutePathname } from '@vuepress/shared' +import { resolveRouteFullPath } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' import { resolveRoutePath } from './resolveRoutePath.js' @@ -17,7 +17,7 @@ export const resolveRoute = ( currentPath?: string, ): ResolvedRoute => { const routePath = resolveRoutePath(path, currentPath) - const pathname = resolveRoutePathname(routePath) + const [pathname] = resolveRouteFullPath(routePath) const route = routes.value[pathname] ?? { ...routes.value['/404.html'], notFound: true, diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index db1bd15f7b..85dfbd1f09 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -1,4 +1,4 @@ -import { normalizeRoutePath, resolveRoutePathname } from '@vuepress/shared' +import { normalizeRoutePath, resolveRouteFullPath } from '@vuepress/shared' import { redirects, routes } from '../internal/routes.js' /** @@ -10,16 +10,20 @@ export const resolveRoutePath = ( ): string => { // normalized path const normalizedPath = normalizeRoutePath(path, currentPath) - const normalizedPathname = resolveRoutePathname(normalizedPath) + const [normalizedPathname, queryAndHash] = + resolveRouteFullPath(normalizedPath) + if (routes.value[normalizedPathname]) return normalizedPath + // encoded path const encodedPathname = encodeURI(normalizedPathname) if (routes.value[encodedPathname]) return encodeURI(normalizedPath) - // redirected path or fallback to the normalized path - return ( - redirects.value[normalizedPathname] || - redirects.value[encodedPathname] || - normalizedPath - ) + // redirected path + const redirectPath = + redirects.value[normalizedPathname] || redirects.value[encodedPathname] + if (redirectPath) return redirectPath + queryAndHash + + // fallback to the normalized path + return normalizedPath } diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index cbe29c4f2c..4af399a350 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -15,5 +15,5 @@ export * from './removeLeadingSlash.js' export * from './resolveHeadIdentifier.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' -export * from './resolveRoutePathname.js' +export * from './resolveRouteFullPath.js' export * from './typeGuards.js' diff --git a/packages/shared/src/utils/normalizeRoutePath.ts b/packages/shared/src/utils/normalizeRoutePath.ts index 334fc308c1..cd50bd42cf 100644 --- a/packages/shared/src/utils/normalizeRoutePath.ts +++ b/packages/shared/src/utils/normalizeRoutePath.ts @@ -1,4 +1,5 @@ import { inferRoutePath } from './inferRoutePath.js' +import { resolveRouteFullPath } from './resolveRouteFullPath.js' const FAKE_HOST = 'http://.' @@ -15,7 +16,7 @@ export const normalizeRoutePath = (path: string, current?: string): string => { return inferRoutePath(pathname) + search + hash } - const [pathname, ...queryAndHash] = path.split(/(\?|#)/) + const [pathname, queryAndHash] = resolveRouteFullPath(path) - return inferRoutePath(pathname) + queryAndHash.join('') + return inferRoutePath(pathname) + queryAndHash } diff --git a/packages/shared/src/utils/resolveRouteFullPath.ts b/packages/shared/src/utils/resolveRouteFullPath.ts new file mode 100644 index 0000000000..bc92534677 --- /dev/null +++ b/packages/shared/src/utils/resolveRouteFullPath.ts @@ -0,0 +1,10 @@ +const SPLIT_CHAR_RE = /(#|\?)/ + +/** + * Resolve the route.fullPath to [pathname, queryAndHash] + */ +export const resolveRouteFullPath = (path: string): [string, string] => { + const [pathname, ...queryAndHash] = path.split(SPLIT_CHAR_RE) + + return [pathname, queryAndHash.join('')] +} diff --git a/packages/shared/src/utils/resolveRoutePathname.ts b/packages/shared/src/utils/resolveRoutePathname.ts deleted file mode 100644 index 6dd10d2803..0000000000 --- a/packages/shared/src/utils/resolveRoutePathname.ts +++ /dev/null @@ -1,8 +0,0 @@ -const SPLIT_CHAR_RE = /#|\?/ - -/** - * Resolve the route pathname - */ -export const resolveRoutePathname = (path: string): string => { - return path.split(SPLIT_CHAR_RE)[0] -} diff --git a/packages/shared/tests/resolveRouteFullPath.spec.ts b/packages/shared/tests/resolveRouteFullPath.spec.ts new file mode 100644 index 0000000000..983aafa33e --- /dev/null +++ b/packages/shared/tests/resolveRouteFullPath.spec.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { resolveRouteFullPath } 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 pathname correctly', () => { + testCases.forEach(([source, expected]) => { + it(`${source} -> ${expected}`, () => { + expect(resolveRouteFullPath(source)).toEqual(expected) + }) + }) +}) diff --git a/packages/shared/tests/resolveRoutePathname.spec.ts b/packages/shared/tests/resolveRoutePathname.spec.ts deleted file mode 100644 index 9f1f0ef848..0000000000 --- a/packages/shared/tests/resolveRoutePathname.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { resolveRoutePathname } from '../src/index.js' - -const testCases = [ - ['/a/b/c/', '/a/b/c/'], - ['/a/b/c/?a=1', '/a/b/c/'], - ['/a/b/c/#b', '/a/b/c/'], - ['/a/b/c/?a=1#b', '/a/b/c/'], - ['a/index.html', 'a/index.html'], - ['/a/index.html?a=1', '/a/index.html'], - ['/a/index.html#a', '/a/index.html'], - ['/a/index.html?a=1#b', '/a/index.html'], -] - -describe('should resolve route pathname correctly', () => { - testCases.forEach(([source, expected]) => { - it(`${source} -> ${expected}`, () => { - expect(resolveRoutePathname(source)).toBe(expected) - }) - }) -}) From 24dd4ac1fa59d6dcfa3b6450dd16a61844304faf Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 19 May 2024 23:20:43 +0800 Subject: [PATCH 08/17] test: add e2e test --- e2e/docs/router/resolve-route-query-hash.md | 12 +++++++ .../router/resolve-route-query-hash.spec.ts | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 e2e/docs/router/resolve-route-query-hash.md create mode 100644 e2e/tests/router/resolve-route-query-hash.spec.ts diff --git a/e2e/docs/router/resolve-route-query-hash.md b/e2e/docs/router/resolve-route-query-hash.md new file mode 100644 index 0000000000..a21266220f --- /dev/null +++ b/e2e/docs/router/resolve-route-query-hash.md @@ -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')) }} + + diff --git a/e2e/tests/router/resolve-route-query-hash.spec.ts b/e2e/tests/router/resolve-route-query-hash.spec.ts new file mode 100644 index 0000000000..d7554c6425 --- /dev/null +++ b/e2e/tests/router/resolve-route-query-hash.spec.ts @@ -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) + } +}) From edd2fc1f91cde796a2fa5f3efdffdf4dc7f406f7 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Mon, 20 May 2024 13:00:10 +0800 Subject: [PATCH 09/17] feat: unify api name and avoid unnecessary recalculation --- packages/client/src/components/RouteLink.ts | 4 +- packages/client/src/router/index.ts | 1 + packages/client/src/router/resolveRoute.ts | 25 ++++++++---- .../client/src/router/resolveRouteFullPath.ts | 14 +++++++ .../client/src/router/resolveRoutePath.ts | 35 ++++++++++------- packages/shared/src/utils/index.ts | 2 +- .../shared/src/utils/normalizeRoutePath.ts | 18 ++++----- .../shared/src/utils/resolveRouteFullPath.ts | 10 ----- .../shared/src/utils/resolveRoutePathInfo.ts | 12 ++++++ .../shared/tests/normalizeRoutePath.spec.ts | 39 ------------------- ...h.spec.ts => resolveRoutePathInfo.spec.ts} | 6 +-- 11 files changed, 78 insertions(+), 88 deletions(-) create mode 100644 packages/client/src/router/resolveRouteFullPath.ts delete mode 100644 packages/shared/src/utils/resolveRouteFullPath.ts create mode 100644 packages/shared/src/utils/resolveRoutePathInfo.ts rename packages/shared/tests/{resolveRouteFullPath.spec.ts => resolveRoutePathInfo.spec.ts} (75%) diff --git a/packages/client/src/components/RouteLink.ts b/packages/client/src/components/RouteLink.ts index 99c4875533..1b1e62b157 100644 --- a/packages/client/src/components/RouteLink.ts +++ b/packages/client/src/components/RouteLink.ts @@ -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 @@ -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 () => diff --git a/packages/client/src/router/index.ts b/packages/client/src/router/index.ts index dae3d58e2c..206f6f2f2b 100644 --- a/packages/client/src/router/index.ts +++ b/packages/client/src/router/index.ts @@ -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' diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index b0d9580a25..8cacd9aac5 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,4 +1,4 @@ -import { resolveRouteFullPath } from '@vuepress/shared' +import { resolveRoutePathInfo } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' import { resolveRoutePath } from './resolveRoutePath.js' @@ -16,16 +16,25 @@ export const resolveRoute = ( path: string, currentPath?: string, ): ResolvedRoute => { - const routePath = resolveRoutePath(path, currentPath) - const [pathname] = resolveRouteFullPath(routePath) - const route = routes.value[pathname] ?? { - ...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 } return { - path: routePath, + ...routes.value[routePath], + path: routeFullPath, notFound: false, - ...route, } as ResolvedRoute } diff --git a/packages/client/src/router/resolveRouteFullPath.ts b/packages/client/src/router/resolveRouteFullPath.ts new file mode 100644 index 0000000000..a07441899a --- /dev/null +++ b/packages/client/src/router/resolveRouteFullPath.ts @@ -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 +} diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index 85dfbd1f09..4f815e4c17 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -1,29 +1,34 @@ -import { normalizeRoutePath, resolveRouteFullPath } from '@vuepress/shared' +import { normalizeRoutePath } from '@vuepress/shared' import { redirects, routes } from '../internal/routes.js' /** * Resolve route path with given raw path */ export const resolveRoutePath = ( - path: string, + pathname: string, currentPath?: string, ): string => { // normalized path - const normalizedPath = normalizeRoutePath(path, currentPath) - const [normalizedPathname, queryAndHash] = - resolveRouteFullPath(normalizedPath) + const normalizedRoutePath = normalizeRoutePath(pathname, currentPath) - if (routes.value[normalizedPathname]) return normalizedPath + // check if the normalized path is in routes + if (routes.value[normalizedRoutePath]) return normalizedRoutePath - // encoded path - const encodedPathname = encodeURI(normalizedPathname) - if (routes.value[encodedPathname]) return encodeURI(normalizedPath) + // check encoded path + const encodedRoutePath = encodeURI(normalizedRoutePath) - // redirected path - const redirectPath = - redirects.value[normalizedPathname] || redirects.value[encodedPathname] - if (redirectPath) return redirectPath + queryAndHash + if (routes.value[encodedRoutePath]) { + return encodedRoutePath + } - // fallback to the normalized path - return normalizedPath + // 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 } diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 4af399a350..fb1db555ae 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -15,5 +15,5 @@ export * from './removeLeadingSlash.js' export * from './resolveHeadIdentifier.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' -export * from './resolveRouteFullPath.js' +export * from './resolveRoutePathInfo.js' export * from './typeGuards.js' diff --git a/packages/shared/src/utils/normalizeRoutePath.ts b/packages/shared/src/utils/normalizeRoutePath.ts index cd50bd42cf..6c73ef172a 100644 --- a/packages/shared/src/utils/normalizeRoutePath.ts +++ b/packages/shared/src/utils/normalizeRoutePath.ts @@ -1,22 +1,20 @@ import { inferRoutePath } from './inferRoutePath.js' -import { resolveRouteFullPath } from './resolveRouteFullPath.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, current?: string): string => { - if (!path.startsWith('/') && current) { +export const normalizeRoutePath = ( + pathname: string, + current?: string, +): string => { + 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) + search + hash + return inferRoutePath(new URL(`${loc}/${pathname}`, FAKE_HOST).pathname) } - const [pathname, queryAndHash] = resolveRouteFullPath(path) - - return inferRoutePath(pathname) + queryAndHash + return inferRoutePath(pathname) } diff --git a/packages/shared/src/utils/resolveRouteFullPath.ts b/packages/shared/src/utils/resolveRouteFullPath.ts deleted file mode 100644 index bc92534677..0000000000 --- a/packages/shared/src/utils/resolveRouteFullPath.ts +++ /dev/null @@ -1,10 +0,0 @@ -const SPLIT_CHAR_RE = /(#|\?)/ - -/** - * Resolve the route.fullPath to [pathname, queryAndHash] - */ -export const resolveRouteFullPath = (path: string): [string, string] => { - const [pathname, ...queryAndHash] = path.split(SPLIT_CHAR_RE) - - return [pathname, queryAndHash.join('')] -} diff --git a/packages/shared/src/utils/resolveRoutePathInfo.ts b/packages/shared/src/utils/resolveRoutePathInfo.ts new file mode 100644 index 0000000000..65b0b68bad --- /dev/null +++ b/packages/shared/src/utils/resolveRoutePathInfo.ts @@ -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('')] +} diff --git a/packages/shared/tests/normalizeRoutePath.spec.ts b/packages/shared/tests/normalizeRoutePath.spec.ts index 6543ab24e2..7644945e14 100644 --- a/packages/shared/tests/normalizeRoutePath.spec.ts +++ b/packages/shared/tests/normalizeRoutePath.spec.ts @@ -204,42 +204,3 @@ describe('should normalize clean paths correctly', () => { }), ) }) - -describe('should normalize paths with query correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}?foo=bar`, current], - `${expected}?foo=bar`, - ]) - .forEach(([[path, current], expected]) => - it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) - }), - ) -}) - -describe('should normalize paths with hash correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}#foobar`, current], - `${expected}#foobar`, - ]) - .map(([[path, current], expected]) => - it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) - }), - ) -}) - -describe('should normalize paths with query and hash correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}?foo=1&bar=2#foobar`, current], - `${expected}?foo=1&bar=2#foobar`, - ]) - .map(([[path, current], expected]) => - it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) - }), - ) -}) diff --git a/packages/shared/tests/resolveRouteFullPath.spec.ts b/packages/shared/tests/resolveRoutePathInfo.spec.ts similarity index 75% rename from packages/shared/tests/resolveRouteFullPath.spec.ts rename to packages/shared/tests/resolveRoutePathInfo.spec.ts index 983aafa33e..3c704198ba 100644 --- a/packages/shared/tests/resolveRouteFullPath.spec.ts +++ b/packages/shared/tests/resolveRoutePathInfo.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { resolveRouteFullPath } from '../src/index.js' +import { resolveRoutePathInfo } from '../src/index.js' const testCases: [string, [string, string]][] = [ ['/a/b/c/', ['/a/b/c/', '']], @@ -12,10 +12,10 @@ const testCases: [string, [string, string]][] = [ ['/a/index.html?a=1#b', ['/a/index.html', '?a=1#b']], ] -describe('should resolve route pathname correctly', () => { +describe('should resolve route path info correctly', () => { testCases.forEach(([source, expected]) => { it(`${source} -> ${expected}`, () => { - expect(resolveRouteFullPath(source)).toEqual(expected) + expect(resolveRoutePathInfo(source)).toEqual(expected) }) }) }) From 46bd7ba3bfbc315204d50f795bad724d84d0e872 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Wed, 22 May 2024 16:29:52 +0800 Subject: [PATCH 10/17] chore: rename new methods --- packages/client/src/router/resolveRoute.ts | 4 ++-- packages/client/src/router/resolveRouteFullPath.ts | 4 ++-- packages/shared/src/utils/routes/index.ts | 2 +- .../routes/{resolveRoutePathInfo.ts => resolvePathInfo.ts} | 4 ++-- ...resolveRoutePathInfo.spec.ts => resolvePathInfo.spec.ts} | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) rename packages/shared/src/utils/routes/{resolveRoutePathInfo.ts => resolvePathInfo.ts} (69%) rename packages/shared/tests/routes/{resolveRoutePathInfo.spec.ts => resolvePathInfo.spec.ts} (75%) diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index 8cacd9aac5..38e3313e22 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,4 +1,4 @@ -import { resolveRoutePathInfo } from '@vuepress/shared' +import { resolvePathInfo } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' import { resolveRoutePath } from './resolveRoutePath.js' @@ -17,7 +17,7 @@ export const resolveRoute = ( currentPath?: string, ): ResolvedRoute => { // get only the pathname from the path - const [pathname, hashAndQueries] = resolveRoutePathInfo(path) + const [pathname, hashAndQueries] = resolvePathInfo(path) // resolve the route path const routePath = resolveRoutePath(pathname, currentPath) diff --git a/packages/client/src/router/resolveRouteFullPath.ts b/packages/client/src/router/resolveRouteFullPath.ts index a07441899a..a1ab501de6 100644 --- a/packages/client/src/router/resolveRouteFullPath.ts +++ b/packages/client/src/router/resolveRouteFullPath.ts @@ -1,4 +1,4 @@ -import { resolveRoutePathInfo } from '@vuepress/shared' +import { resolvePathInfo } from '@vuepress/shared' import { resolveRoutePath } from './resolveRoutePath.js' /** @@ -8,7 +8,7 @@ export const resolveRouteFullPath = ( path: string, currentPath?: string, ): string => { - const [pathname, hashAndQueries] = resolveRoutePathInfo(path) + const [pathname, hashAndQueries] = resolvePathInfo(path) return resolveRoutePath(pathname, currentPath) + hashAndQueries } diff --git a/packages/shared/src/utils/routes/index.ts b/packages/shared/src/utils/routes/index.ts index 4ec0e91b53..dfa2112b2f 100644 --- a/packages/shared/src/utils/routes/index.ts +++ b/packages/shared/src/utils/routes/index.ts @@ -2,4 +2,4 @@ export * from './inferRoutePath' export * from './normalizeRoutePath.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' -export * from './resolveRoutePathInfo.js' +export * from './resolvePathInfo.js' diff --git a/packages/shared/src/utils/routes/resolveRoutePathInfo.ts b/packages/shared/src/utils/routes/resolvePathInfo.ts similarity index 69% rename from packages/shared/src/utils/routes/resolveRoutePathInfo.ts rename to packages/shared/src/utils/routes/resolvePathInfo.ts index 65b0b68bad..99ab36c9c7 100644 --- a/packages/shared/src/utils/routes/resolveRoutePathInfo.ts +++ b/packages/shared/src/utils/routes/resolvePathInfo.ts @@ -1,9 +1,9 @@ const SPLIT_CHAR_REGEXP = /(#|\?)/ /** - * Extract pathname / hash and queries from a full route path + * Extract pathname / hash and queries from a relative URL */ -export const resolveRoutePathInfo = ( +export const resolvePathInfo = ( path: string, ): [pathname: string, hashAndQueries: string] => { const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP) diff --git a/packages/shared/tests/routes/resolveRoutePathInfo.spec.ts b/packages/shared/tests/routes/resolvePathInfo.spec.ts similarity index 75% rename from packages/shared/tests/routes/resolveRoutePathInfo.spec.ts rename to packages/shared/tests/routes/resolvePathInfo.spec.ts index 5577bd8c09..a71f71b5e0 100644 --- a/packages/shared/tests/routes/resolveRoutePathInfo.spec.ts +++ b/packages/shared/tests/routes/resolvePathInfo.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { resolveRoutePathInfo } from '../../src/index.js' +import { resolvePathInfo } from '../../src/index.js' const testCases: [string, [string, string]][] = [ ['/a/b/c/', ['/a/b/c/', '']], @@ -12,10 +12,10 @@ const testCases: [string, [string, string]][] = [ ['/a/index.html?a=1#b', ['/a/index.html', '?a=1#b']], ] -describe('should resolve route path info correctly', () => { +describe('should resolve path info correctly', () => { testCases.forEach(([source, expected]) => { it(`${source} -> ${expected}`, () => { - expect(resolveRoutePathInfo(source)).toEqual(expected) + expect(resolvePathInfo(source)).toEqual(expected) }) }) }) From 8ce1d273c17387d27a91f94b453029686e26acf4 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Wed, 22 May 2024 16:38:56 +0800 Subject: [PATCH 11/17] feat: support clean url and improve route resolve --- .eslintrc.cjs | 6 + .../src/plugins/vuepressMainPlugin.ts | 1 + .../src/config/handlePluginDefine.ts | 1 + .../cli/src/commands/dev/watchPageFiles.ts | 2 +- packages/client/src/router/index.ts | 1 + packages/client/src/router/resolveRoute.ts | 16 +- packages/client/src/router/resolveRouteKey.ts | 34 ++ .../client/src/router/resolveRoutePath.ts | 29 +- packages/client/src/setupGlobalComputed.ts | 7 +- packages/client/types.d.ts | 1 + .../core/src/app/prepare/prepareRoutes.ts | 9 +- packages/core/src/app/resolveAppOptions.ts | 19 +- packages/core/src/app/resolveAppPages.ts | 4 +- packages/core/src/page/createPage.ts | 5 + .../core/src/page/resolvePagePermalink.ts | 2 +- packages/core/src/page/resolvePageRouteKey.ts | 6 + packages/core/src/types/app/options.ts | 25 +- packages/core/src/types/page.ts | 5 + .../core/tests/app/resolveAppOptions.spec.ts | 7 +- .../src/plugins/linksPlugin/linksPlugin.ts | 21 +- packages/shared/src/utils/routes/index.ts | 1 + .../shared/src/utils/routes/inferRoutePath.ts | 18 +- .../utils/routes/resolveRoutePathWithExt.ts | 2 + .../tests/routes/inferRoutePath.spec.ts | 42 +-- .../tests/routes/normalizeRoutePath.spec.ts | 318 +++++++++--------- 25 files changed, 335 insertions(+), 247 deletions(-) create mode 100644 packages/client/src/router/resolveRouteKey.ts create mode 100644 packages/core/src/page/resolvePageRouteKey.ts create mode 100644 packages/shared/src/utils/routes/resolveRoutePathWithExt.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7f763acda8..44c2c7dc3a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,6 +1,12 @@ module.exports = { root: true, extends: 'vuepress', + + // FIXME: This should be added to `eslint-config-vuepress` + globals: { + __VUEPRESS_CLEAN_URL__: 'readonly', + }, + overrides: [ { files: ['*.ts', '*.vue', '*.cts'], diff --git a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts index 05a53d3b0d..9c73ea36a6 100644 --- a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts @@ -205,6 +205,7 @@ const resolveDefine = async ({ const define: UserConfig['define'] = { __VUEPRESS_VERSION__: JSON.stringify(app.version), __VUEPRESS_BASE__: JSON.stringify(app.options.base), + __VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl), __VUEPRESS_DEV__: JSON.stringify(!isBuild), __VUEPRESS_SSR__: JSON.stringify(isServer), // @see http://link.vuejs.org/feature-flags diff --git a/packages/bundler-webpack/src/config/handlePluginDefine.ts b/packages/bundler-webpack/src/config/handlePluginDefine.ts index d118844d48..7681c70f5a 100644 --- a/packages/bundler-webpack/src/config/handlePluginDefine.ts +++ b/packages/bundler-webpack/src/config/handlePluginDefine.ts @@ -21,6 +21,7 @@ export const handlePluginDefine = async ({ { __VUEPRESS_VERSION__: JSON.stringify(app.version), __VUEPRESS_BASE__: JSON.stringify(app.options.base), + __VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl), __VUEPRESS_DEV__: JSON.stringify(!isBuild), __VUEPRESS_SSR__: JSON.stringify(isServer), // @see http://link.vuejs.org/feature-flags diff --git a/packages/cli/src/commands/dev/watchPageFiles.ts b/packages/cli/src/commands/dev/watchPageFiles.ts index 48dcee2060..10c1bf7ad7 100644 --- a/packages/cli/src/commands/dev/watchPageFiles.ts +++ b/packages/cli/src/commands/dev/watchPageFiles.ts @@ -41,7 +41,7 @@ export const watchPageFiles = (app: App): FSWatcher[] => { app.pages.forEach((page) => addDeps(page)) // watch page files - const pagesWatcher = chokidar.watch(app.options.pagePatterns, { + const pagesWatcher = chokidar.watch(app.options.route.pagePatterns, { cwd: app.dir.source(), ignoreInitial: true, }) diff --git a/packages/client/src/router/index.ts b/packages/client/src/router/index.ts index 206f6f2f2b..b7801c6fd7 100644 --- a/packages/client/src/router/index.ts +++ b/packages/client/src/router/index.ts @@ -3,4 +3,5 @@ export { useRoute, useRouter } from 'vue-router' export * from './resolveRoute.js' export * from './resolveRouteFullPath.js' +export * from './resolveRouteKey.js' export * from './resolveRoutePath.js' diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index 38e3313e22..d25f396785 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,7 +1,7 @@ -import { resolvePathInfo } from '@vuepress/shared' +import { resolvePathInfo, resolveRoutePathWithExt } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' -import { resolveRoutePath } from './resolveRoutePath.js' +import { resolveRouteKey } from './resolveRouteKey.js' export interface ResolvedRoute extends Route { @@ -20,20 +20,22 @@ export const resolveRoute = ( const [pathname, hashAndQueries] = resolvePathInfo(path) // resolve the route path - const routePath = resolveRoutePath(pathname, currentPath) - const routeFullPath = routePath + hashAndQueries + const routeKey = resolveRouteKey(pathname, currentPath) + const routeFullPath = __VUEPRESS_CLEAN_URL__ + ? routeKey + : resolveRoutePathWithExt(routeKey) + hashAndQueries // the route not found - if (!routes.value[routePath]) { + if (!routes.value[routeKey]) { return { - ...routes.value['/404.html'], + ...routes.value['/404'], path: routeFullPath, notFound: true, } as ResolvedRoute } return { - ...routes.value[routePath], + ...routes.value[routeKey], path: routeFullPath, notFound: false, } as ResolvedRoute diff --git a/packages/client/src/router/resolveRouteKey.ts b/packages/client/src/router/resolveRouteKey.ts new file mode 100644 index 0000000000..8c2e43d371 --- /dev/null +++ b/packages/client/src/router/resolveRouteKey.ts @@ -0,0 +1,34 @@ +import { normalizeRoutePath } from '@vuepress/shared' +import { redirects, routes } from '../internal/routes.js' + +/** + * Resolve route path with given raw path + */ +export const resolveRouteKey = ( + pathname: string, + currentPath?: string, +): string => { + // normalized path + const routePath = normalizeRoutePath(pathname, currentPath) + + // check if the normalized path is in routes + if (routes.value[routePath]) return routePath + + // check encoded path + const encodedRoutePath = encodeURI(routePath) + + if (routes.value[encodedRoutePath]) { + return encodedRoutePath + } + + // check redirected path with normalized path and encoded path + const redirectedRoutePath = + redirects.value[routePath] || redirects.value[encodedRoutePath] + + if (redirectedRoutePath) { + return redirectedRoutePath + } + + // default to normalized route path + return routePath +} diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index 4f815e4c17..964b7d8399 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -1,5 +1,5 @@ -import { normalizeRoutePath } from '@vuepress/shared' -import { redirects, routes } from '../internal/routes.js' +import { resolveRoutePathWithExt } from '@vuepress/shared' +import { resolveRouteKey } from './resolveRouteKey.js' /** * Resolve route path with given raw path @@ -8,27 +8,8 @@ export const resolveRoutePath = ( pathname: string, currentPath?: string, ): string => { - // normalized path - const normalizedRoutePath = normalizeRoutePath(pathname, currentPath) + // clean route path format used as key in routes + const routeKey = resolveRouteKey(pathname, currentPath) - // check if the normalized path is in routes - if (routes.value[normalizedRoutePath]) return normalizedRoutePath - - // 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 + return __VUEPRESS_CLEAN_URL__ ? routeKey : resolveRoutePathWithExt(routeKey) } diff --git a/packages/client/src/setupGlobalComputed.ts b/packages/client/src/setupGlobalComputed.ts index 57197148a6..f33b8ee3fd 100644 --- a/packages/client/src/setupGlobalComputed.ts +++ b/packages/client/src/setupGlobalComputed.ts @@ -5,6 +5,7 @@ import { clientDataSymbol } from './composables/index.js' import { redirects, routes } from './internal/routes.js' import { siteData } from './internal/siteData.js' import { resolvers } from './resolvers.js' +import { resolveRouteKey } from './router/resolveRouteKey.js' import type { ClientConfig, ClientData, @@ -46,10 +47,10 @@ export const setupGlobalComputed = ( // handle page data HMR if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) { __VUE_HMR_RUNTIME__.updatePageData = async (newPageData: PageData) => { - const oldPageChunk = await routes.value[newPageData.path].loader() + const routeKey = resolveRouteKey(newPageData.path) + const oldPageChunk = await routes.value[routeKey].loader() const newPageChunk = { comp: oldPageChunk.comp, data: newPageData } - routes.value[newPageData.path].loader = () => - Promise.resolve(newPageChunk) + routes.value[routeKey].loader = () => Promise.resolve(newPageChunk) if ( newPageData.path === router.currentRoute.value.meta._pageChunk?.data.path diff --git a/packages/client/types.d.ts b/packages/client/types.d.ts index 2d55050eb3..fdaa55b854 100644 --- a/packages/client/types.d.ts +++ b/packages/client/types.d.ts @@ -1,6 +1,7 @@ declare const __VUEPRESS_VERSION__: string declare const __VUEPRESS_BASE__: string declare const __VUEPRESS_DEV__: boolean +declare const __VUEPRESS_CLEAN_URL__: boolean declare const __VUEPRESS_SSR__: boolean declare const __VUE_HMR_RUNTIME__: Record declare const __VUE_OPTIONS_API__: boolean diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index b3556e8041..780a0d4721 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -51,7 +51,10 @@ export const redirects = JSON.parse(${JSON.stringify( JSON.stringify( Object.fromEntries( app.pages.flatMap((page) => - resolvePageRedirects(page).map((redirect) => [redirect, page.path]), + resolvePageRedirects(page).map((redirect) => [ + redirect, + page.routeKey, + ]), ), ), ), @@ -60,8 +63,8 @@ export const redirects = JSON.parse(${JSON.stringify( export const routes = Object.fromEntries([ ${app.pages .map( - ({ chunkFilePath, chunkName, path, routeMeta }) => - ` [${JSON.stringify(path)}, { loader: () => import(${chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''}${JSON.stringify(chunkFilePath)}), meta: ${JSON.stringify(routeMeta)} }],`, + ({ chunkFilePath, chunkName, routeKey, routeMeta }) => + ` [${JSON.stringify(routeKey)}, { loader: () => import(${chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''}${JSON.stringify(chunkFilePath)}), meta: ${JSON.stringify(routeMeta)} }],`, ) .join('\n')} ]); diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index d851c3d969..04538847b1 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -38,8 +38,16 @@ export const resolveAppOptions = ({ bundler, debug = false, markdown = {}, - pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern = null, + pagePatterns: _pagePatterns, + permalinkPattern: _permalinkPattern, + route: { + cleanUrl = false, + pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern = null, + } = { + pagePatterns: _pagePatterns, + permalinkPattern: _permalinkPattern, + }, plugins = [], theme, }: AppConfig): AppOptions => ({ @@ -65,8 +73,11 @@ export const resolveAppOptions = ({ bundler, debug, markdown, - pagePatterns, - permalinkPattern, + route: { + cleanUrl, + pagePatterns, + permalinkPattern, + }, plugins, theme, }) diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index 2f721fa7fd..039c18c276 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -11,7 +11,7 @@ export const resolveAppPages = async (app: App): Promise => { log('resolveAppPages start') // resolve page absolute file paths according to the page patterns - const pageFilePaths = await globby(app.options.pagePatterns, { + const pageFilePaths = await globby(app.options.route.pagePatterns, { absolute: true, cwd: app.dir.source(), }) @@ -22,7 +22,7 @@ export const resolveAppPages = async (app: App): Promise => { ) // find the 404 page - const notFoundPage = pages.find((page) => page.path === '/404.html') + const notFoundPage = pages.find((page) => page.routeKey === '/404') // if there is a 404 page, set the default layout to NotFound if (notFoundPage) { diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index ab9b05deb6..c5147133f3 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -10,6 +10,7 @@ import { resolvePageHtmlInfo } from './resolvePageHtmlInfo.js' import { resolvePageLang } from './resolvePageLang.js' import { resolvePagePath } from './resolvePagePath.js' import { resolvePagePermalink } from './resolvePagePermalink.js' +import { resolvePageRouteKey } from './resolvePageRouteKey.js' import { resolvePageRouteMeta } from './resolvePageRouteMeta.js' import { resolvePageSlug } from './resolvePageSlug.js' @@ -75,6 +76,9 @@ export const createPage = async ( // resolve page path const path = resolvePagePath({ permalink, pathInferred, options }) + // resolve page routeKey + const routeKey = resolvePageRouteKey(path) + // resolve page rendered html file path const { htmlFilePath, htmlFilePathRelative } = resolvePageHtmlInfo({ app, @@ -118,6 +122,7 @@ export const createPage = async ( pathInferred, pathLocale, permalink, + routeKey, routeMeta, sfcBlocks, slug, diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 6a549085f2..14ca26bb13 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -34,7 +34,7 @@ export const resolvePagePermalink = ({ } const permalinkPattern = - frontmatter.permalinkPattern || app.options.permalinkPattern + frontmatter.permalinkPattern || app.options.route.permalinkPattern if (!isString(permalinkPattern)) { return null diff --git a/packages/core/src/page/resolvePageRouteKey.ts b/packages/core/src/page/resolvePageRouteKey.ts new file mode 100644 index 0000000000..33d523832d --- /dev/null +++ b/packages/core/src/page/resolvePageRouteKey.ts @@ -0,0 +1,6 @@ +/** + * Resolve the final route path of a page + */ +export const resolvePageRouteKey = (path: string): string => + // convert to the clean format + path.replace(/\.html$/, '').replace(/\/index$/i, '/') diff --git a/packages/core/src/types/app/options.ts b/packages/core/src/types/app/options.ts index 93271fd4bf..6721a3b930 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -5,6 +5,12 @@ import type { Bundler } from '../bundler.js' import type { PluginConfig } from '../plugin.js' import type { Theme } from '../theme.js' +export interface RouteOptions { + cleanUrl?: boolean + pagePatterns?: string[] + permalinkPattern?: string | null +} + /** * Vuepress app common config that shared between dev and build */ @@ -14,11 +20,9 @@ export interface AppConfigCommon extends Partial { temp?: string cache?: string public?: string - debug?: boolean markdown?: MarkdownOptions - pagePatterns?: string[] - permalinkPattern?: string | null + route?: RouteOptions bundler: Bundler theme: Theme plugins?: PluginConfig @@ -95,9 +99,20 @@ export interface AppConfigBuild { /** * Vuepress app config */ -export type AppConfig = AppConfigCommon & AppConfigDev & AppConfigBuild +export type AppConfig = AppConfigCommon & + AppConfigDev & + AppConfigBuild & { + /** @deprecated use route.pagePatterns instead */ + pagePatterns?: string[] + /** @deprecated use route.permalinkPattern instead */ + permalinkPattern?: string | null + } /** * Vuepress app options */ -export type AppOptions = Required +export type AppOptions = Required< + AppConfigCommon & AppConfigDev & AppConfigBuild +> & { + route: Required +} diff --git a/packages/core/src/types/page.ts b/packages/core/src/types/page.ts index 5aadd8d7ae..ca8c7b03af 100644 --- a/packages/core/src/types/page.ts +++ b/packages/core/src/types/page.ts @@ -72,6 +72,11 @@ export type Page< */ permalink: string | null + /** + * Key in routes record + */ + routeKey: string + /** * Custom data to be attached to route record */ diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index a7f3ff125c..1a269dfac6 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -30,8 +30,11 @@ describe('core > app > resolveAppOptions', () => { host: '0.0.0.0', port: 8080, open: false, - pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern: null, + route: { + cleanUrl: false, + pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern: null, + }, templateDev: path.normalize( require.resolve('@vuepress/client/templates/dev.html'), ), diff --git a/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts b/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts index 5fb407656a..d2d0e05b58 100644 --- a/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts +++ b/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts @@ -1,4 +1,8 @@ -import { inferRoutePath, isLinkExternal } from '@vuepress/shared' +import { + inferRoutePath, + isLinkExternal, + resolveRoutePathWithExt, +} from '@vuepress/shared' import type { PluginWithOptions } from 'markdown-it' import type Token from 'markdown-it/lib/token.mjs' import type { MarkdownEnv } from '../../types.js' @@ -114,17 +118,22 @@ export const linksPlugin: PluginWithOptions = ( // normalize markdown file path to route path // we are removing the `base` from absolute path because it should not be // passed to `` or `` - const normalizedPath = inferRoutePath( - absolutePath - ? absolutePath.replace(new RegExp(`^${base}`), '/') - : relativePath, + const normalizedPath = resolveRoutePathWithExt( + inferRoutePath( + absolutePath + ? absolutePath.replace(new RegExp(`^${base}`), '/') + : relativePath, + ), ) // replace the original href link with the normalized path hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}` // set `hasOpenInternalLink` to modify the ending tag hasOpenInternalLink = true } else { - const normalizedPath = inferRoutePath(absolutePath ?? relativePath) + // ext is added here + const normalizedPath = resolveRoutePathWithExt( + inferRoutePath(absolutePath ?? relativePath), + ) // replace the original href link with the normalized path hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}` } diff --git a/packages/shared/src/utils/routes/index.ts b/packages/shared/src/utils/routes/index.ts index dfa2112b2f..0a6f4a5596 100644 --- a/packages/shared/src/utils/routes/index.ts +++ b/packages/shared/src/utils/routes/index.ts @@ -1,5 +1,6 @@ export * from './inferRoutePath' export * from './normalizeRoutePath.js' +export * from './resolveRoutePathWithExt.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' export * from './resolvePathInfo.js' diff --git a/packages/shared/src/utils/routes/inferRoutePath.ts b/packages/shared/src/utils/routes/inferRoutePath.ts index 0a1ba2e4aa..863b394852 100644 --- a/packages/shared/src/utils/routes/inferRoutePath.ts +++ b/packages/shared/src/utils/routes/inferRoutePath.ts @@ -6,20 +6,20 @@ export const inferRoutePath = (path: string): string => { if (!path || path.endsWith('/')) return path // convert README.md to index.html - let routePath = path.replace(/(^|\/)README.md$/i, '$1index.html') + let routePath = path.replace(/(^|\/)README.md$/i, '$1index') - // convert /foo/bar.md to /foo/bar.html + // convert /foo/bar.md to /foo/bar if (routePath.endsWith('.md')) { - routePath = routePath.substring(0, routePath.length - 3) + '.html' + routePath = routePath.substring(0, routePath.length - 3) } - // convert /foo/bar to /foo/bar.html - else if (!routePath.endsWith('.html')) { - routePath = routePath + '.html' + // convert /foo/bar.html to /foo/bar + else if (routePath.endsWith('.html')) { + routePath = routePath.substring(0, routePath.length - 5) } - // convert /foo/index.html to /foo/ - if (routePath.endsWith('/index.html')) { - routePath = routePath.substring(0, routePath.length - 10) + // convert /foo/index to /foo/ + if (routePath.endsWith('/index')) { + routePath = routePath.substring(0, routePath.length - 5) } return routePath diff --git a/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts b/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts new file mode 100644 index 0000000000..a3d9924af8 --- /dev/null +++ b/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts @@ -0,0 +1,2 @@ +export const resolveRoutePathWithExt = (routePath: string): string => + routePath.endsWith('/') ? routePath : routePath + '.html' diff --git a/packages/shared/tests/routes/inferRoutePath.spec.ts b/packages/shared/tests/routes/inferRoutePath.spec.ts index 053fa22a89..71960e6edf 100644 --- a/packages/shared/tests/routes/inferRoutePath.spec.ts +++ b/packages/shared/tests/routes/inferRoutePath.spec.ts @@ -15,19 +15,19 @@ const testCases = [ ['/foo/index.md', '/foo/'], ['/foo/index.html', '/foo/'], ['/foo/index', '/foo/'], - ['README.md', 'index.html'], - ['readme.md', 'index.html'], - ['index.md', 'index.html'], - ['index.html', 'index.html'], - ['index', 'index.html'], + ['README.md', 'index'], + ['readme.md', 'index'], + ['index.md', 'index'], + ['index.html', 'index'], + ['index', 'index'], // absolute non-index - ['/foo', '/foo.html'], - ['/foo.md', '/foo.html'], - ['/foo.html', '/foo.html'], - ['/foo/bar', '/foo/bar.html'], - ['/foo/bar.md', '/foo/bar.html'], - ['/foo/bar.html', '/foo/bar.html'], + ['/foo', '/foo'], + ['/foo.md', '/foo'], + ['/foo.html', '/foo'], + ['/foo/bar', '/foo/bar'], + ['/foo/bar.md', '/foo/bar'], + ['/foo/bar.html', '/foo/bar'], // relative index without current ['foo/', 'foo/'], @@ -38,19 +38,19 @@ const testCases = [ ['foo/index', 'foo/'], // relative non index without current - ['foo', 'foo.html'], - ['foo.md', 'foo.html'], - ['foo.html', 'foo.html'], - ['foo/bar', 'foo/bar.html'], - ['foo/bar.md', 'foo/bar.html'], - ['foo/bar.html', 'foo/bar.html'], + ['foo', 'foo'], + ['foo.md', 'foo'], + ['foo.html', 'foo'], + ['foo/bar', 'foo/bar'], + ['foo/bar.md', 'foo/bar'], + ['foo/bar.html', 'foo/bar'], // unexpected corner cases ['', ''], - ['.md', '.html'], - ['foo/.md', 'foo/.html'], - ['/.md', '/.html'], - ['/foo/.md', '/foo/.html'], + ['.md', ''], + ['foo/.md', 'foo/'], + ['/.md', '/'], + ['/foo/.md', '/foo/'], ] describe('should normalize clean paths correctly', () => { diff --git a/packages/shared/tests/routes/normalizeRoutePath.spec.ts b/packages/shared/tests/routes/normalizeRoutePath.spec.ts index 1732c6d2f6..85fec82d27 100644 --- a/packages/shared/tests/routes/normalizeRoutePath.spec.ts +++ b/packages/shared/tests/routes/normalizeRoutePath.spec.ts @@ -15,19 +15,19 @@ const testCases = [ [['/foo/index.md'], '/foo/'], [['/foo/index.html'], '/foo/'], [['/foo/index'], '/foo/'], - [['README.md'], 'index.html'], - [['readme.md'], 'index.html'], - [['index.md'], 'index.html'], - [['index.html'], 'index.html'], - [['index'], 'index.html'], + [['README.md'], 'index'], + [['readme.md'], 'index'], + [['index.md'], 'index'], + [['index.html'], 'index'], + [['index'], 'index'], // absolute non-index - [['/foo'], '/foo.html'], - [['/foo.md'], '/foo.html'], - [['/foo.html'], '/foo.html'], - [['/foo/bar'], '/foo/bar.html'], - [['/foo/bar.md'], '/foo/bar.html'], - [['/foo/bar.html'], '/foo/bar.html'], + [['/foo'], '/foo'], + [['/foo.md'], '/foo'], + [['/foo.html'], '/foo'], + [['/foo/bar'], '/foo/bar'], + [['/foo/bar.md'], '/foo/bar'], + [['/foo/bar.html'], '/foo/bar'], // relative index without current [['foo/'], 'foo/'], @@ -38,163 +38,163 @@ const testCases = [ [['foo/index'], 'foo/'], // relative non index without current - [['foo'], 'foo.html'], - [['foo.md'], 'foo.html'], - [['foo.html'], 'foo.html'], - [['foo/bar'], 'foo/bar.html'], - [['foo/bar.md'], 'foo/bar.html'], - [['foo/bar.html'], 'foo/bar.html'], + [['foo'], 'foo'], + [['foo.md'], 'foo'], + [['foo.html'], 'foo'], + [['foo/bar'], 'foo/bar'], + [['foo/bar.md'], 'foo/bar'], + [['foo/bar.html'], 'foo/bar'], // relative non index with current - [['foo', '/'], '/foo.html'], - [['foo', '/a.html'], '/foo.html'], - [['foo', '/index.html'], '/foo.html'], - [['foo', '/a/'], '/a/foo.html'], - [['foo', '/a/index.html'], '/a/foo.html'], - [['foo', '/a/b.html'], '/a/foo.html'], - [['foo.md', '/'], '/foo.html'], - [['foo.md', '/a.html'], '/foo.html'], - [['foo.md', '/index.html'], '/foo.html'], - [['foo.md', '/a/'], '/a/foo.html'], - [['foo.md', '/a/index.html'], '/a/foo.html'], - [['foo.md', '/a/b.html'], '/a/foo.html'], - [['foo.html', '/'], '/foo.html'], - [['foo.html', '/a.html'], '/foo.html'], - [['foo.html', '/index.html'], '/foo.html'], - [['foo.html', '/a/'], '/a/foo.html'], - [['foo.html', '/a/index.html'], '/a/foo.html'], - [['foo.html', '/a/b.html'], '/a/foo.html'], - [['foo/bar', '/'], '/foo/bar.html'], - [['foo/bar', '/a.html'], '/foo/bar.html'], - [['foo/bar', '/index.html'], '/foo/bar.html'], - [['foo/bar', '/a/'], '/a/foo/bar.html'], - [['foo/bar', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar', '/a/b.html'], '/a/foo/bar.html'], - [['foo/bar.md', '/'], '/foo/bar.html'], - [['foo/bar.md', '/a.html'], '/foo/bar.html'], - [['foo/bar.md', '/index.html'], '/foo/bar.html'], - [['foo/bar.md', '/a/'], '/a/foo/bar.html'], - [['foo/bar.md', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar.md', '/a/b.html'], '/a/foo/bar.html'], - [['foo/bar.html', '/'], '/foo/bar.html'], - [['foo/bar.html', '/a.html'], '/foo/bar.html'], - [['foo/bar.html', '/index.html'], '/foo/bar.html'], - [['foo/bar.html', '/a/'], '/a/foo/bar.html'], - [['foo/bar.html', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar.html', '/a/b.html'], '/a/foo/bar.html'], - [['./foo', '/'], '/foo.html'], - [['./foo', '/a.html'], '/foo.html'], - [['./foo', '/index.html'], '/foo.html'], - [['./foo', '/a/'], '/a/foo.html'], - [['./foo', '/a/index.html'], '/a/foo.html'], - [['./foo', '/a/b.html'], '/a/foo.html'], - [['./foo.md', '/'], '/foo.html'], - [['./foo.md', '/a.html'], '/foo.html'], - [['./foo.md', '/index.html'], '/foo.html'], - [['./foo.md', '/a/'], '/a/foo.html'], - [['./foo.md', '/a/index.html'], '/a/foo.html'], - [['./foo.md', '/a/b.html'], '/a/foo.html'], - [['./foo.html', '/'], '/foo.html'], - [['./foo.html', '/a.html'], '/foo.html'], - [['./foo.html', '/index.html'], '/foo.html'], - [['./foo.html', '/a/'], '/a/foo.html'], - [['./foo.html', '/a/index.html'], '/a/foo.html'], - [['./foo.html', '/a/b.html'], '/a/foo.html'], - [['./foo/bar', '/'], '/foo/bar.html'], - [['./foo/bar', '/a.html'], '/foo/bar.html'], - [['./foo/bar', '/index.html'], '/foo/bar.html'], - [['./foo/bar', '/a/'], '/a/foo/bar.html'], - [['./foo/bar', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar', '/a/b.html'], '/a/foo/bar.html'], - [['./foo/bar.md', '/'], '/foo/bar.html'], - [['./foo/bar.md', '/a.html'], '/foo/bar.html'], - [['./foo/bar.md', '/index.html'], '/foo/bar.html'], - [['./foo/bar.md', '/a/'], '/a/foo/bar.html'], - [['./foo/bar.md', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar.md', '/a/b.html'], '/a/foo/bar.html'], - [['./foo/bar.html', '/'], '/foo/bar.html'], - [['./foo/bar.html', '/a.html'], '/foo/bar.html'], - [['./foo/bar.html', '/index.html'], '/foo/bar.html'], - [['./foo/bar.html', '/a/'], '/a/foo/bar.html'], - [['./foo/bar.html', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar.html', '/a/b.html'], '/a/foo/bar.html'], - [['../foo', '/a/'], '/foo.html'], - [['../foo', '/a/index.html'], '/foo.html'], - [['../foo', '/a/b.html'], '/foo.html'], - [['../foo.md', '/a/'], '/foo.html'], - [['../foo.md', '/a/index.html'], '/foo.html'], - [['../foo.md', '/a/b.html'], '/foo.html'], - [['../foo.html', '/a/'], '/foo.html'], - [['../foo.html', '/a/index.html'], '/foo.html'], - [['../foo.html', '/a/b.html'], '/foo.html'], - [['../foo/bar', '/a/'], '/foo/bar.html'], - [['../foo/bar', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar', '/a/b.html'], '/foo/bar.html'], - [['../foo/bar.md', '/a/'], '/foo/bar.html'], - [['../foo/bar.md', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar.md', '/a/b.html'], '/foo/bar.html'], - [['../foo/bar.html', '/a/'], '/foo/bar.html'], - [['../foo/bar.html', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar.html', '/a/b.html'], '/foo/bar.html'], + [['foo', '/'], '/foo'], + [['foo', '/a.html'], '/foo'], + [['foo', '/index.html'], '/foo'], + [['foo', '/a/'], '/a/foo'], + [['foo', '/a/index.html'], '/a/foo'], + [['foo', '/a/b.html'], '/a/foo'], + [['foo.md', '/'], '/foo'], + [['foo.md', '/a.html'], '/foo'], + [['foo.md', '/index.html'], '/foo'], + [['foo.md', '/a/'], '/a/foo'], + [['foo.md', '/a/index.html'], '/a/foo'], + [['foo.md', '/a/b.html'], '/a/foo'], + [['foo.html', '/'], '/foo'], + [['foo.html', '/a.html'], '/foo'], + [['foo.html', '/index.html'], '/foo'], + [['foo.html', '/a/'], '/a/foo'], + [['foo.html', '/a/index.html'], '/a/foo'], + [['foo.html', '/a/b.html'], '/a/foo'], + [['foo/bar', '/'], '/foo/bar'], + [['foo/bar', '/a.html'], '/foo/bar'], + [['foo/bar', '/index.html'], '/foo/bar'], + [['foo/bar', '/a/'], '/a/foo/bar'], + [['foo/bar', '/a/index.html'], '/a/foo/bar'], + [['foo/bar', '/a/b.html'], '/a/foo/bar'], + [['foo/bar.md', '/'], '/foo/bar'], + [['foo/bar.md', '/a.html'], '/foo/bar'], + [['foo/bar.md', '/index.html'], '/foo/bar'], + [['foo/bar.md', '/a/'], '/a/foo/bar'], + [['foo/bar.md', '/a/index.html'], '/a/foo/bar'], + [['foo/bar.md', '/a/b.html'], '/a/foo/bar'], + [['foo/bar.html', '/'], '/foo/bar'], + [['foo/bar.html', '/a.html'], '/foo/bar'], + [['foo/bar.html', '/index.html'], '/foo/bar'], + [['foo/bar.html', '/a/'], '/a/foo/bar'], + [['foo/bar.html', '/a/index.html'], '/a/foo/bar'], + [['foo/bar.html', '/a/b.html'], '/a/foo/bar'], + [['./foo', '/'], '/foo'], + [['./foo', '/a.html'], '/foo'], + [['./foo', '/index.html'], '/foo'], + [['./foo', '/a/'], '/a/foo'], + [['./foo', '/a/index.html'], '/a/foo'], + [['./foo', '/a/b.html'], '/a/foo'], + [['./foo.md', '/'], '/foo'], + [['./foo.md', '/a.html'], '/foo'], + [['./foo.md', '/index.html'], '/foo'], + [['./foo.md', '/a/'], '/a/foo'], + [['./foo.md', '/a/index.html'], '/a/foo'], + [['./foo.md', '/a/b.html'], '/a/foo'], + [['./foo.html', '/'], '/foo'], + [['./foo.html', '/a.html'], '/foo'], + [['./foo.html', '/index.html'], '/foo'], + [['./foo.html', '/a/'], '/a/foo'], + [['./foo.html', '/a/index.html'], '/a/foo'], + [['./foo.html', '/a/b.html'], '/a/foo'], + [['./foo/bar', '/'], '/foo/bar'], + [['./foo/bar', '/a.html'], '/foo/bar'], + [['./foo/bar', '/index.html'], '/foo/bar'], + [['./foo/bar', '/a/'], '/a/foo/bar'], + [['./foo/bar', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar', '/a/b.html'], '/a/foo/bar'], + [['./foo/bar.md', '/'], '/foo/bar'], + [['./foo/bar.md', '/a.html'], '/foo/bar'], + [['./foo/bar.md', '/index.html'], '/foo/bar'], + [['./foo/bar.md', '/a/'], '/a/foo/bar'], + [['./foo/bar.md', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar.md', '/a/b.html'], '/a/foo/bar'], + [['./foo/bar.html', '/'], '/foo/bar'], + [['./foo/bar.html', '/a.html'], '/foo/bar'], + [['./foo/bar.html', '/index.html'], '/foo/bar'], + [['./foo/bar.html', '/a/'], '/a/foo/bar'], + [['./foo/bar.html', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar.html', '/a/b.html'], '/a/foo/bar'], + [['../foo', '/a/'], '/foo'], + [['../foo', '/a/index.html'], '/foo'], + [['../foo', '/a/b.html'], '/foo'], + [['../foo.md', '/a/'], '/foo'], + [['../foo.md', '/a/index.html'], '/foo'], + [['../foo.md', '/a/b.html'], '/foo'], + [['../foo.html', '/a/'], '/foo'], + [['../foo.html', '/a/index.html'], '/foo'], + [['../foo.html', '/a/b.html'], '/foo'], + [['../foo/bar', '/a/'], '/foo/bar'], + [['../foo/bar', '/a/index.html'], '/foo/bar'], + [['../foo/bar', '/a/b.html'], '/foo/bar'], + [['../foo/bar.md', '/a/'], '/foo/bar'], + [['../foo/bar.md', '/a/index.html'], '/foo/bar'], + [['../foo/bar.md', '/a/b.html'], '/foo/bar'], + [['../foo/bar.html', '/a/'], '/foo/bar'], + [['../foo/bar.html', '/a/index.html'], '/foo/bar'], + [['../foo/bar.html', '/a/b.html'], '/foo/bar'], // absolute non index with current - [['/foo', '/'], '/foo.html'], - [['/foo', '/a.html'], '/foo.html'], - [['/foo', '/index.html'], '/foo.html'], - [['/foo', '/a/'], '/foo.html'], - [['/foo', '/a/index.html'], '/foo.html'], - [['/foo', '/a/b.html'], '/foo.html'], - [['/foo.md', '/'], '/foo.html'], - [['/foo.md', '/a.html'], '/foo.html'], - [['/foo.md', '/index.html'], '/foo.html'], - [['/foo.md', '/a/'], '/foo.html'], - [['/foo.md', '/a/index.html'], '/foo.html'], - [['/foo.md', '/a/b.html'], '/foo.html'], - [['/foo.html', '/'], '/foo.html'], - [['/foo.html', '/a.html'], '/foo.html'], - [['/foo.html', '/index.html'], '/foo.html'], - [['/foo.html', '/a/'], '/foo.html'], - [['/foo.html', '/a/index.html'], '/foo.html'], - [['/foo.html', '/a/b.html'], '/foo.html'], - [['/foo/bar', '/'], '/foo/bar.html'], - [['/foo/bar', '/a.html'], '/foo/bar.html'], - [['/foo/bar', '/index.html'], '/foo/bar.html'], - [['/foo/bar', '/a/'], '/foo/bar.html'], - [['/foo/bar', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar', '/a/b.html'], '/foo/bar.html'], - [['/foo/bar.md', '/'], '/foo/bar.html'], - [['/foo/bar.md', '/a.html'], '/foo/bar.html'], - [['/foo/bar.md', '/index.html'], '/foo/bar.html'], - [['/foo/bar.md', '/a/'], '/foo/bar.html'], - [['/foo/bar.md', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar.md', '/a/b.html'], '/foo/bar.html'], - [['/foo/bar.html', '/'], '/foo/bar.html'], - [['/foo/bar.html', '/a.html'], '/foo/bar.html'], - [['/foo/bar.html', '/index.html'], '/foo/bar.html'], - [['/foo/bar.html', '/a/'], '/foo/bar.html'], - [['/foo/bar.html', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar.html', '/a/b.html'], '/foo/bar.html'], + [['/foo', '/'], '/foo'], + [['/foo', '/a.html'], '/foo'], + [['/foo', '/index.html'], '/foo'], + [['/foo', '/a/'], '/foo'], + [['/foo', '/a/index.html'], '/foo'], + [['/foo', '/a/b.html'], '/foo'], + [['/foo.md', '/'], '/foo'], + [['/foo.md', '/a.html'], '/foo'], + [['/foo.md', '/index.html'], '/foo'], + [['/foo.md', '/a/'], '/foo'], + [['/foo.md', '/a/index.html'], '/foo'], + [['/foo.md', '/a/b.html'], '/foo'], + [['/foo.html', '/'], '/foo'], + [['/foo.html', '/a.html'], '/foo'], + [['/foo.html', '/index.html'], '/foo'], + [['/foo.html', '/a/'], '/foo'], + [['/foo.html', '/a/index.html'], '/foo'], + [['/foo.html', '/a/b.html'], '/foo'], + [['/foo/bar', '/'], '/foo/bar'], + [['/foo/bar', '/a.html'], '/foo/bar'], + [['/foo/bar', '/index.html'], '/foo/bar'], + [['/foo/bar', '/a/'], '/foo/bar'], + [['/foo/bar', '/a/index.html'], '/foo/bar'], + [['/foo/bar', '/a/b.html'], '/foo/bar'], + [['/foo/bar.md', '/'], '/foo/bar'], + [['/foo/bar.md', '/a.html'], '/foo/bar'], + [['/foo/bar.md', '/index.html'], '/foo/bar'], + [['/foo/bar.md', '/a/'], '/foo/bar'], + [['/foo/bar.md', '/a/index.html'], '/foo/bar'], + [['/foo/bar.md', '/a/b.html'], '/foo/bar'], + [['/foo/bar.html', '/'], '/foo/bar'], + [['/foo/bar.html', '/a.html'], '/foo/bar'], + [['/foo/bar.html', '/index.html'], '/foo/bar'], + [['/foo/bar.html', '/a/'], '/foo/bar'], + [['/foo/bar.html', '/a/index.html'], '/foo/bar'], + [['/foo/bar.html', '/a/b.html'], '/foo/bar'], // only hash and query [[''], ''], // unexpected corner cases - [['.md'], '.html'], - [['foo/.md'], 'foo/.html'], - [['/.md'], '/.html'], - [['/foo/.md'], '/foo/.html'], - [['.md', '/a/'], '/a/.html'], - [['foo/.md', '/a/'], '/a/foo/.html'], - [['/.md', '/a/'], '/.html'], - [['/foo/.md', '/a/'], '/foo/.html'], - [['.md', '/a/index.html'], '/a/.html'], - [['foo/.md', '/a/index.html'], '/a/foo/.html'], - [['/.md', '/a/index.html'], '/.html'], - [['/foo/.md', '/a/index.html'], '/foo/.html'], - [['.md', '/a/b.html'], '/a/.html'], - [['foo/.md', '/a/b.html'], '/a/foo/.html'], - [['/.md', '/a/b.html'], '/.html'], - [['/foo/.md', '/a/b.html'], '/foo/.html'], + [['.md'], ''], + [['foo/.md'], 'foo/'], + [['/.md'], '/'], + [['/foo/.md'], '/foo/'], + [['.md', '/a/'], '/a/'], + [['foo/.md', '/a/'], '/a/foo/'], + [['/.md', '/a/'], '/'], + [['/foo/.md', '/a/'], '/foo/'], + [['.md', '/a/index.html'], '/a/'], + [['foo/.md', '/a/index.html'], '/a/foo/'], + [['/.md', '/a/index.html'], '/'], + [['/foo/.md', '/a/index.html'], '/foo/'], + [['.md', '/a/b.html'], '/a/'], + [['foo/.md', '/a/b.html'], '/a/foo/'], + [['/.md', '/a/b.html'], '/'], + [['/foo/.md', '/a/b.html'], '/foo/'], ] describe('should normalize clean paths correctly', () => { From fa7b93b92dd61d8a5f7c0b7f3d410d8c623eac44 Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 24 May 2024 00:00:40 +0800 Subject: [PATCH 12/17] chore: tweaks --- packages/client/src/router/resolveRoute.ts | 4 ++-- .../client/src/router/resolveRouteFullPath.ts | 5 ++--- packages/shared/src/utils/routes/index.ts | 2 +- .../src/utils/routes/resolvePathInfo.ts | 12 ---------- packages/shared/src/utils/routes/splitPath.ts | 17 ++++++++++++++ .../tests/routes/resolvePathInfo.spec.ts | 21 ------------------ .../shared/tests/routes/splitPath.spec.ts | 22 +++++++++++++++++++ 7 files changed, 44 insertions(+), 39 deletions(-) delete mode 100644 packages/shared/src/utils/routes/resolvePathInfo.ts create mode 100644 packages/shared/src/utils/routes/splitPath.ts delete mode 100644 packages/shared/tests/routes/resolvePathInfo.spec.ts create mode 100644 packages/shared/tests/routes/splitPath.spec.ts diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index 38e3313e22..c8a7f4b8a8 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,4 +1,4 @@ -import { resolvePathInfo } from '@vuepress/shared' +import { splitPath } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' import { resolveRoutePath } from './resolveRoutePath.js' @@ -17,7 +17,7 @@ export const resolveRoute = ( currentPath?: string, ): ResolvedRoute => { // get only the pathname from the path - const [pathname, hashAndQueries] = resolvePathInfo(path) + const { pathname, hashAndQueries } = splitPath(path) // resolve the route path const routePath = resolveRoutePath(pathname, currentPath) diff --git a/packages/client/src/router/resolveRouteFullPath.ts b/packages/client/src/router/resolveRouteFullPath.ts index a1ab501de6..694386d74b 100644 --- a/packages/client/src/router/resolveRouteFullPath.ts +++ b/packages/client/src/router/resolveRouteFullPath.ts @@ -1,4 +1,4 @@ -import { resolvePathInfo } from '@vuepress/shared' +import { splitPath } from '@vuepress/shared' import { resolveRoutePath } from './resolveRoutePath.js' /** @@ -8,7 +8,6 @@ export const resolveRouteFullPath = ( path: string, currentPath?: string, ): string => { - const [pathname, hashAndQueries] = resolvePathInfo(path) - + const { pathname, hashAndQueries } = splitPath(path) return resolveRoutePath(pathname, currentPath) + hashAndQueries } diff --git a/packages/shared/src/utils/routes/index.ts b/packages/shared/src/utils/routes/index.ts index dfa2112b2f..5e67f4b001 100644 --- a/packages/shared/src/utils/routes/index.ts +++ b/packages/shared/src/utils/routes/index.ts @@ -2,4 +2,4 @@ export * from './inferRoutePath' export * from './normalizeRoutePath.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' -export * from './resolvePathInfo.js' +export * from './splitPath.js' diff --git a/packages/shared/src/utils/routes/resolvePathInfo.ts b/packages/shared/src/utils/routes/resolvePathInfo.ts deleted file mode 100644 index 99ab36c9c7..0000000000 --- a/packages/shared/src/utils/routes/resolvePathInfo.ts +++ /dev/null @@ -1,12 +0,0 @@ -const SPLIT_CHAR_REGEXP = /(#|\?)/ - -/** - * Extract pathname / hash and queries from a relative URL - */ -export const resolvePathInfo = ( - path: string, -): [pathname: string, hashAndQueries: string] => { - const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP) - - return [pathname, hashAndQueries.join('')] -} diff --git a/packages/shared/src/utils/routes/splitPath.ts b/packages/shared/src/utils/routes/splitPath.ts new file mode 100644 index 0000000000..2aa3906dc6 --- /dev/null +++ b/packages/shared/src/utils/routes/splitPath.ts @@ -0,0 +1,17 @@ +const SPLIT_CHAR_REGEXP = /(#|\?)/ + +/** + * Split a path into pathname and hashAndQueries + */ +export const splitPath = ( + path: string, +): { + pathname: string + hashAndQueries: string +} => { + const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP) + return { + pathname, + hashAndQueries: hashAndQueries.join(''), + } +} diff --git a/packages/shared/tests/routes/resolvePathInfo.spec.ts b/packages/shared/tests/routes/resolvePathInfo.spec.ts deleted file mode 100644 index a71f71b5e0..0000000000 --- a/packages/shared/tests/routes/resolvePathInfo.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { resolvePathInfo } 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 path info correctly', () => { - testCases.forEach(([source, expected]) => { - it(`${source} -> ${expected}`, () => { - expect(resolvePathInfo(source)).toEqual(expected) - }) - }) -}) diff --git a/packages/shared/tests/routes/splitPath.spec.ts b/packages/shared/tests/routes/splitPath.spec.ts new file mode 100644 index 0000000000..6dfd2452fb --- /dev/null +++ b/packages/shared/tests/routes/splitPath.spec.ts @@ -0,0 +1,22 @@ +import { expect, it } from 'vitest' +import { splitPath } from '../../src/index.js' + +const testCases: [string, ReturnType][] = [ + ['/a/b/c/', { pathname: '/a/b/c/', hashAndQueries: '' }], + ['/a/b/c/?a=1', { pathname: '/a/b/c/', hashAndQueries: '?a=1' }], + ['/a/b/c/#b', { pathname: '/a/b/c/', hashAndQueries: '#b' }], + ['/a/b/c/?a=1#b', { pathname: '/a/b/c/', hashAndQueries: '?a=1#b' }], + ['a/index.html', { pathname: 'a/index.html', hashAndQueries: '' }], + ['/a/index.html?a=1', { pathname: '/a/index.html', hashAndQueries: '?a=1' }], + ['/a/index.html#a', { pathname: '/a/index.html', hashAndQueries: '#a' }], + [ + '/a/index.html?a=1#b', + { pathname: '/a/index.html', hashAndQueries: '?a=1#b' }, + ], +] + +testCases.forEach(([source, expected]) => { + it(`${source} -> ${expected}`, () => { + expect(splitPath(source)).toEqual(expected) + }) +}) From 26c28ed81ab0c0e244bcdb660c9c6783e883efb0 Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 24 May 2024 00:15:26 +0800 Subject: [PATCH 13/17] chore: updates --- .../theme/client/layouts/NotFound.vue | 1 + e2e/docs/404.md | 2 ++ e2e/docs/README.md | 2 ++ e2e/docs/router/navigate-by-link.md | 1 + e2e/docs/router/navigate-by-router.md | 26 ++++++++++++++++ e2e/docs/router/navigation.md | 16 ---------- e2e/tests/router/navigate-by-link.spec.ts | 11 +++++++ e2e/tests/router/navigate-by-router.spec.ts | 30 +++++++++++++++++++ e2e/tests/router/navigation.spec.ts | 18 ----------- 9 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 e2e/docs/router/navigate-by-link.md create mode 100644 e2e/docs/router/navigate-by-router.md delete mode 100644 e2e/docs/router/navigation.md create mode 100644 e2e/tests/router/navigate-by-link.spec.ts create mode 100644 e2e/tests/router/navigate-by-router.spec.ts delete mode 100644 e2e/tests/router/navigation.spec.ts diff --git a/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue b/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue index 75e27d4c0e..1e98d1e27f 100644 --- a/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue +++ b/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue @@ -1,3 +1,4 @@ diff --git a/e2e/docs/404.md b/e2e/docs/404.md index fac3cec274..937c74d960 100644 --- a/e2e/docs/404.md +++ b/e2e/docs/404.md @@ -2,3 +2,5 @@ routeMeta: foo: bar --- + +## NotFound H2 diff --git a/e2e/docs/README.md b/e2e/docs/README.md index 257cc5642c..eb63f0ccbf 100644 --- a/e2e/docs/README.md +++ b/e2e/docs/README.md @@ -1 +1,3 @@ foo + +## Home H2 diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md new file mode 100644 index 0000000000..cf873b4c07 --- /dev/null +++ b/e2e/docs/router/navigate-by-link.md @@ -0,0 +1 @@ + diff --git a/e2e/docs/router/navigate-by-router.md b/e2e/docs/router/navigate-by-router.md new file mode 100644 index 0000000000..444c5ae29f --- /dev/null +++ b/e2e/docs/router/navigate-by-router.md @@ -0,0 +1,26 @@ + + + + + + diff --git a/e2e/docs/router/navigation.md b/e2e/docs/router/navigation.md deleted file mode 100644 index 624df7c8f1..0000000000 --- a/e2e/docs/router/navigation.md +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts new file mode 100644 index 0000000000..dd3436b404 --- /dev/null +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test' +import { BASE } from '../../utils/env' + +test.beforeEach(async ({ page }) => { + await page.goto('router/navigate-by-link.html') +}) + +test('TODO', async ({ page }) => { + // TODO + await expect(page).toHaveURL(`${BASE}router/navigate-by-link.html`) +}) diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts new file mode 100644 index 0000000000..9ae3c7b325 --- /dev/null +++ b/e2e/tests/router/navigate-by-router.spec.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test' +import { BASE } from '../../utils/env' + +test.beforeEach(async ({ page }) => { + await page.goto('router/navigate-by-router.html') +}) + +test('should preserve query', async ({ page }) => { + await page.locator('#home-with-query').click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') +}) + +test('should preserve query and hash', async ({ page }) => { + await page.locator('#home-with-query-and-hash').click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') +}) + +test('should preserve hash', async ({ page }) => { + await page.locator('#not-found-with-hash').click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') +}) + +test('should preserve hash and query', async ({ page }) => { + await page.locator('#not-found-with-hash-and-query').click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') +}) diff --git a/e2e/tests/router/navigation.spec.ts b/e2e/tests/router/navigation.spec.ts deleted file mode 100644 index 76573acf6c..0000000000 --- a/e2e/tests/router/navigation.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect, test } from '@playwright/test' -import { BASE } from '../../utils/env' - -test('should preserve query', async ({ page }) => { - await page.goto('router/navigation.html') - - await page.locator('#home').click() - - await expect(page).toHaveURL(`${BASE}?home=true`) -}) - -test('should preserve hash', async ({ page }) => { - await page.goto('router/navigation.html') - - await page.locator('#not-found').click() - - await expect(page).toHaveURL(`${BASE}404.html#404`) -}) From c72ad207f9c764ffd406d5dfcbc2c5e3dcdd8694 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 24 May 2024 12:47:27 +0800 Subject: [PATCH 14/17] test: add e2e tests --- e2e/docs/router/navigate-by-link.md | 21 +++++- e2e/tests/router/navigate-by-link.spec.ts | 81 ++++++++++++++++++++++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md index cf873b4c07..6d28173095 100644 --- a/e2e/docs/router/navigate-by-link.md +++ b/e2e/docs/router/navigate-by-link.md @@ -1 +1,20 @@ - +## Markdown Links with html + +- [Home with query](/?home=true) +- [Home with query and hash](/?home=true#home) +- [404 with hash](/404.html#404) +- [404 with hash and query](/404.html#404?notFound=true) + +## Markdown Links with md + +- [Home with query](/README.md?home=true) +- [Home with query and hash](/README.md?home=true#home) +- [404 with hash](/404.md#404) +- [404 with hash and query](/404.md#404?notFound=true) + +## HTML Links + +Home +Home +404 +404 diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index dd3436b404..a2076b0dd2 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -5,7 +5,82 @@ test.beforeEach(async ({ page }) => { await page.goto('router/navigate-by-link.html') }) -test('TODO', async ({ page }) => { - // TODO - await expect(page).toHaveURL(`${BASE}router/navigate-by-link.html`) +test.describe('should preserve query', () => { + test('markdown links with html suffix', async ({ page }) => { + await page.locator('#markdown-links-with-html + ul > li > a').nth(0).click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('markdown links with md suffix', async ({ page }) => { + await page.locator('#markdown-links-with-md + ul > li > a').nth(0).click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html links', async ({ page }) => { + await page.locator('#html-links + p > a').nth(0).click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) +}) + +test.describe('should preserve query and hash', () => { + test('markdown links with html suffix', async ({ page }) => { + await page.locator('#markdown-links-with-html + ul > li > a').nth(1).click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('markdown links with md suffix', async ({ page }) => { + await page.locator('#markdown-links-with-md + ul > li > a').nth(1).click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html links', async ({ page }) => { + await page.locator('#html-links + p > a').nth(1).click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) +}) + +test.describe('should preserve hash', () => { + test('markdown links with html suffix', async ({ page }) => { + await page.locator('#markdown-links-with-html + ul > li > a').nth(2).click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('markdown links with md suffix', async ({ page }) => { + await page.locator('#markdown-links-with-md + ul > li > a').nth(2).click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html links', async ({ page }) => { + await page.locator('#html-links + p > a').nth(2).click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) +}) + +test.describe('should preserve hash and query', () => { + test('markdown links with html suffix', async ({ page }) => { + await page.locator('#markdown-links-with-html + ul > li > a').nth(3).click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('markdown links with md suffix', async ({ page }) => { + await page.locator('#markdown-links-with-md + ul > li > a').nth(3).click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html links', async ({ page }) => { + await page.locator('#html-links + p > a').nth(3).click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) }) From 2fa1f5ffcb743651663b4a91dfb43cb824c7573a Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 24 May 2024 12:49:25 +0800 Subject: [PATCH 15/17] chore: tweaks --- packages/client/src/router/resolveRoute.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index c63be5763f..0bbb627016 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -16,14 +16,13 @@ export const resolveRoute = ( path: string, currentPath?: string, ): ResolvedRoute => { - // get only the pathname from the path const { pathname, hashAndQueries } = splitPath(path) - // resolve the route path + // calculate the route key and full path const routeKey = resolveRouteKey(pathname, currentPath) - const routeFullPath = __VUEPRESS_CLEAN_URL__ - ? routeKey - : resolveRoutePathWithExt(routeKey) + hashAndQueries + const routeFullPath = + (__VUEPRESS_CLEAN_URL__ ? routeKey : resolveRoutePathWithExt(routeKey)) + + hashAndQueries // the route not found if (!routes.value[routeKey]) { From d26921ee294e514c7f7130a19e35cbe2f1ba3bff Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 24 May 2024 13:00:16 +0800 Subject: [PATCH 16/17] test: add e2e tests --- e2e/docs/router/navigate-by-link.md | 16 +++++- e2e/docs/router/navigate-by-router.md | 49 ++++++++++++---- e2e/tests/router/navigate-by-link.spec.ts | 64 ++++++++++++++++++--- e2e/tests/router/navigate-by-router.spec.ts | 64 +++++++++++++++------ 4 files changed, 156 insertions(+), 37 deletions(-) diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md index 6d28173095..7426f6843f 100644 --- a/e2e/docs/router/navigate-by-link.md +++ b/e2e/docs/router/navigate-by-link.md @@ -12,9 +12,23 @@ - [404 with hash](/404.md#404) - [404 with hash and query](/404.md#404?notFound=true) -## HTML Links +## Markdown Clean Links + +- [Home with query](/?home=true) +- [Home with query and hash](/?home=true#home) +- [404 with hash](/404#404) +- [404 with hash and query](/404#404?notFound=true) + +## HTML Full Links Home Home 404 404 + +## HTML Clean Links + +Home +Home +404 +404 diff --git a/e2e/docs/router/navigate-by-router.md b/e2e/docs/router/navigate-by-router.md index 444c5ae29f..ec7fcb997c 100644 --- a/e2e/docs/router/navigate-by-router.md +++ b/e2e/docs/router/navigate-by-router.md @@ -1,26 +1,51 @@ - - - - +
+ + + + +
+ +
+ + + + +
diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index a2076b0dd2..d878a26146 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -18,8 +18,20 @@ test.describe('should preserve query', () => { await expect(page.locator('#home-h2')).toHaveText('Home H2') }) - test('html links', async ({ page }) => { - await page.locator('#html-links + p > a').nth(0).click() + test('markdown clean links', async ({ page }) => { + await page.locator('#markdown-clean-links + ul > li > a').nth(0).click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html full links', async ({ page }) => { + await page.locator('#html-full-links + p > a').nth(0).click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html clean links', async ({ page }) => { + await page.locator('#html-clean-links + p > a').nth(0).click() await expect(page).toHaveURL(`${BASE}?home=true`) await expect(page.locator('#home-h2')).toHaveText('Home H2') }) @@ -38,8 +50,20 @@ test.describe('should preserve query and hash', () => { await expect(page.locator('#home-h2')).toHaveText('Home H2') }) - test('html links', async ({ page }) => { - await page.locator('#html-links + p > a').nth(1).click() + test('markdown clean links', async ({ page }) => { + await page.locator('#markdown-clean-links + ul > li > a').nth(1).click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html full links', async ({ page }) => { + await page.locator('#html-full-links + p > a').nth(1).click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('html clean links', async ({ page }) => { + await page.locator('#html-clean-links + p > a').nth(1).click() await expect(page).toHaveURL(`${BASE}?home=true#home`) await expect(page.locator('#home-h2')).toHaveText('Home H2') }) @@ -58,8 +82,20 @@ test.describe('should preserve hash', () => { await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) - test('html links', async ({ page }) => { - await page.locator('#html-links + p > a').nth(2).click() + test('markdown clean links', async ({ page }) => { + await page.locator('#markdown-clean-links + ul > li > a').nth(2).click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html full links', async ({ page }) => { + await page.locator('#html-full-links + p > a').nth(2).click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html clean links', async ({ page }) => { + await page.locator('#html-clean-links + p > a').nth(2).click() await expect(page).toHaveURL(`${BASE}404.html#404`) await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) @@ -78,8 +114,20 @@ test.describe('should preserve hash and query', () => { await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) - test('html links', async ({ page }) => { - await page.locator('#html-links + p > a').nth(3).click() + test('markdown clean links', async ({ page }) => { + await page.locator('#markdown-clean-links + ul > li > a').nth(3).click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html full links', async ({ page }) => { + await page.locator('#html-full-links + p > a').nth(3).click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('html clean links', async ({ page }) => { + await page.locator('#html-clean-links + p > a').nth(3).click() await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts index 9ae3c7b325..9f2115e139 100644 --- a/e2e/tests/router/navigate-by-router.spec.ts +++ b/e2e/tests/router/navigate-by-router.spec.ts @@ -5,26 +5,58 @@ test.beforeEach(async ({ page }) => { await page.goto('router/navigate-by-router.html') }) -test('should preserve query', async ({ page }) => { - await page.locator('#home-with-query').click() - await expect(page).toHaveURL(`${BASE}?home=true`) - await expect(page.locator('#home-h2')).toHaveText('Home H2') +test.describe('should preserve query', () => { + test('full', async ({ page }) => { + await page.locator('#full .home-with-query').click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .home-with-query').click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) }) -test('should preserve query and hash', async ({ page }) => { - await page.locator('#home-with-query-and-hash').click() - await expect(page).toHaveURL(`${BASE}?home=true#home`) - await expect(page.locator('#home-h2')).toHaveText('Home H2') +test.describe('should preserve query and hash', () => { + test('full', async ({ page }) => { + await page.locator('#full .home-with-query-and-hash').click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .home-with-query-and-hash').click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) }) -test('should preserve hash', async ({ page }) => { - await page.locator('#not-found-with-hash').click() - await expect(page).toHaveURL(`${BASE}404.html#404`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') +test.describe('should preserve hash', () => { + test('full', async ({ page }) => { + await page.locator('#full .not-found-with-hash').click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .not-found-with-hash').click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) }) -test('should preserve hash and query', async ({ page }) => { - await page.locator('#not-found-with-hash-and-query').click() - await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') +test.describe('should preserve hash and query', () => { + test('full', async ({ page }) => { + await page.locator('#full .not-found-with-hash-and-query').click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .not-found-with-hash-and-query').click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) }) From 1a28db6532666962ba6c27c42f48233d440edd43 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 24 May 2024 14:15:04 +0800 Subject: [PATCH 17/17] test(e2e): fix tests --- e2e/docs/router/navigate-by-link.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md index 6d28173095..397fd82857 100644 --- a/e2e/docs/router/navigate-by-link.md +++ b/e2e/docs/router/navigate-by-link.md @@ -14,7 +14,7 @@ ## HTML Links -Home -Home -404 -404 +Home +Home +404 +404