Skip to content

Commit 8a0c158

Browse files
committed
feat: support clean url and improve route resolve
1 parent 31ac4c9 commit 8a0c158

26 files changed

+336
-258
lines changed

.eslintrc.cjs

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
module.exports = {
22
root: true,
33
extends: 'vuepress',
4+
5+
// FIXME: This should be added to `eslint-config-vuepress`
6+
globals: {
7+
__VUEPRESS_CLEAN_URL__: 'readonly',
8+
},
9+
410
overrides: [
511
{
612
files: ['*.ts', '*.vue', '*.cts'],

packages/bundler-vite/src/plugins/vuepressMainPlugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ const resolveDefine = async ({
205205
const define: UserConfig['define'] = {
206206
__VUEPRESS_VERSION__: JSON.stringify(app.version),
207207
__VUEPRESS_BASE__: JSON.stringify(app.options.base),
208+
__VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl),
208209
__VUEPRESS_DEV__: JSON.stringify(!isBuild),
209210
__VUEPRESS_SSR__: JSON.stringify(isServer),
210211
// @see http://link.vuejs.org/feature-flags

packages/bundler-webpack/src/config/handlePluginDefine.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const handlePluginDefine = async ({
2121
{
2222
__VUEPRESS_VERSION__: JSON.stringify(app.version),
2323
__VUEPRESS_BASE__: JSON.stringify(app.options.base),
24+
__VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl),
2425
__VUEPRESS_DEV__: JSON.stringify(!isBuild),
2526
__VUEPRESS_SSR__: JSON.stringify(isServer),
2627
// @see http://link.vuejs.org/feature-flags

packages/cli/src/commands/dev/watchPageFiles.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const watchPageFiles = (app: App): FSWatcher[] => {
4141
app.pages.forEach((page) => addDeps(page))
4242

4343
// watch page files
44-
const pagesWatcher = chokidar.watch(app.options.pagePatterns, {
44+
const pagesWatcher = chokidar.watch(app.options.route.pagePatterns, {
4545
cwd: app.dir.source(),
4646
ignoreInitial: true,
4747
})

packages/client/src/router/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export { useRoute, useRouter } from 'vue-router'
33

44
export * from './resolveRoute.js'
55
export * from './resolveRouteFullPath.js'
6+
export * from './resolveRouteKey.js'
67
export * from './resolveRoutePath.js'

packages/client/src/router/resolveRoute.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { resolveRoutePathInfo } from '@vuepress/shared'
1+
import { resolvePathInfo, resolveRoutePathWithExt } from '@vuepress/shared'
22
import { routes } from '../internal/routes.js'
33
import type { Route, RouteMeta } from '../types/index.js'
4-
import { resolveRoutePath } from './resolveRoutePath.js'
4+
import { resolveRouteKey } from './resolveRouteKey.js'
55

66
export interface ResolvedRoute<T extends RouteMeta = RouteMeta>
77
extends Route<T> {
@@ -17,14 +17,16 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
1717
currentPath?: string,
1818
): ResolvedRoute<T> => {
1919
// get only the pathname from the path
20-
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)
20+
const [pathname, hashAndQueries] = resolvePathInfo(path)
2121

2222
// resolve the route path
23-
const routePath = resolveRoutePath(pathname, currentPath)
24-
const routeFullPath = routePath + hashAndQueries
23+
const routeKey = resolveRouteKey(pathname, currentPath)
24+
const routeFullPath = __VUEPRESS_CLEAN_URL__
25+
? routeKey
26+
: resolveRoutePathWithExt(routeKey) + hashAndQueries
2527

2628
// the route not found
27-
if (!routes.value[routePath]) {
29+
if (!routes.value[routeKey]) {
2830
return {
2931
...routes.value['/404.html'],
3032
path: routeFullPath,
@@ -33,7 +35,7 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
3335
}
3436

3537
return {
36-
...routes.value[routePath],
38+
...routes.value[routeKey],
3739
path: routeFullPath,
3840
notFound: false,
3941
} as ResolvedRoute<T>
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resolveRoutePathInfo } from '@vuepress/shared'
1+
import { resolvePathInfo } from '@vuepress/shared'
22
import { resolveRoutePath } from './resolveRoutePath.js'
33

44
/**
@@ -8,7 +8,7 @@ export const resolveRouteFullPath = (
88
path: string,
99
currentPath?: string,
1010
): string => {
11-
const [pathname, hashAndQueries] = resolveRoutePathInfo(path)
11+
const [pathname, hashAndQueries] = resolvePathInfo(path)
1212

1313
return resolveRoutePath(pathname, currentPath) + hashAndQueries
1414
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { normalizeRoutePath } from '@vuepress/shared'
2+
import { redirects, routes } from '../internal/routes.js'
3+
4+
/**
5+
* Resolve route path with given raw path
6+
*/
7+
export const resolveRouteKey = (
8+
pathname: string,
9+
currentPath?: string,
10+
): string => {
11+
// normalized path
12+
const routePath = normalizeRoutePath(pathname, currentPath)
13+
14+
// check if the normalized path is in routes
15+
if (routes.value[routePath]) return routePath
16+
17+
// check encoded path
18+
const encodedRoutePath = encodeURI(routePath)
19+
20+
if (routes.value[encodedRoutePath]) {
21+
return encodedRoutePath
22+
}
23+
24+
// check redirected path with normalized path and encoded path
25+
const redirectedRoutePath =
26+
redirects.value[routePath] || redirects.value[encodedRoutePath]
27+
28+
if (redirectedRoutePath) {
29+
return redirectedRoutePath
30+
}
31+
32+
// default to normalized route path
33+
return routePath
34+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { normalizeRoutePath } from '@vuepress/shared'
2-
import { redirects, routes } from '../internal/routes.js'
1+
import { resolveRoutePathWithExt } from '@vuepress/shared'
2+
import { resolveRouteKey } from './resolveRouteKey.js'
33

44
/**
55
* Resolve route path with given raw path
@@ -8,27 +8,8 @@ export const resolveRoutePath = (
88
pathname: string,
99
currentPath?: string,
1010
): string => {
11-
// normalized path
12-
const normalizedRoutePath = normalizeRoutePath(pathname, currentPath)
11+
// clean route path format used as key in routes
12+
const routeKey = resolveRouteKey(pathname, currentPath)
1313

14-
// check if the normalized path is in routes
15-
if (routes.value[normalizedRoutePath]) return normalizedRoutePath
16-
17-
// check encoded path
18-
const encodedRoutePath = encodeURI(normalizedRoutePath)
19-
20-
if (routes.value[encodedRoutePath]) {
21-
return encodedRoutePath
22-
}
23-
24-
// check redirected path with normalized path and encoded path
25-
const redirectedRoutePath =
26-
redirects.value[normalizedRoutePath] || redirects.value[encodedRoutePath]
27-
28-
if (redirectedRoutePath) {
29-
return redirectedRoutePath
30-
}
31-
32-
// default to normalized route path
33-
return normalizedRoutePath
14+
return __VUEPRESS_CLEAN_URL__ ? routeKey : resolveRoutePathWithExt(routeKey)
3415
}

packages/client/types.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
declare const __VUEPRESS_VERSION__: string
22
declare const __VUEPRESS_BASE__: string
33
declare const __VUEPRESS_DEV__: boolean
4+
declare const __VUEPRESS_CLEAN_URL__: boolean
45
declare const __VUEPRESS_SSR__: boolean
56
declare const __VUE_HMR_RUNTIME__: Record<string, any>
67
declare const __VUE_OPTIONS_API__: boolean

packages/core/src/app/resolveAppOptions.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ export const resolveAppOptions = ({
3838
bundler,
3939
debug = false,
4040
markdown = {},
41-
pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'],
42-
permalinkPattern = null,
41+
pagePatterns: _pagePatterns,
42+
permalinkPattern: _permalinkPattern,
43+
route: {
44+
cleanUrl = false,
45+
pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'],
46+
permalinkPattern = null,
47+
} = {
48+
pagePatterns: _pagePatterns,
49+
permalinkPattern: _permalinkPattern,
50+
},
4351
plugins = [],
4452
theme,
4553
}: AppConfig): AppOptions => ({
@@ -65,8 +73,11 @@ export const resolveAppOptions = ({
6573
bundler,
6674
debug,
6775
markdown,
68-
pagePatterns,
69-
permalinkPattern,
76+
route: {
77+
cleanUrl,
78+
pagePatterns,
79+
permalinkPattern,
80+
},
7081
plugins,
7182
theme,
7283
})

packages/core/src/app/resolveAppPages.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => {
1111
log('resolveAppPages start')
1212

1313
// resolve page absolute file paths according to the page patterns
14-
const pageFilePaths = await globby(app.options.pagePatterns, {
14+
const pageFilePaths = await globby(app.options.route.pagePatterns, {
1515
absolute: true,
1616
cwd: app.dir.source(),
1717
})
@@ -22,7 +22,7 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => {
2222
)
2323

2424
// find the 404 page
25-
const notFoundPage = pages.find((page) => page.path === '/404.html')
25+
const notFoundPage = pages.find((page) => page.path === '/404')
2626

2727
// if there is a 404 page, set the default layout to NotFound
2828
if (notFoundPage) {
@@ -32,7 +32,7 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => {
3232
else {
3333
pages.push(
3434
await createPage(app, {
35-
path: '/404.html',
35+
path: '/404',
3636
frontmatter: { layout: 'NotFound' },
3737
content: '404 Not Found',
3838
}),

packages/core/src/page/resolvePagePath.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,15 @@ export const resolvePagePath = ({
2121
)
2222
}
2323

24-
return encodeURI(pagePath.split('/').map(sanitizeFileName).join('/'))
24+
return encodeURI(
25+
pagePath
26+
// clean page path
27+
.replace(/\.html$/, '')
28+
.replace(/\/index$/i, '/')
29+
30+
// sanitize page path
31+
.split('/')
32+
.map(sanitizeFileName)
33+
.join('/'),
34+
)
2535
}

packages/core/src/page/resolvePagePermalink.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const resolvePagePermalink = ({
3434
}
3535

3636
const permalinkPattern =
37-
frontmatter.permalinkPattern || app.options.permalinkPattern
37+
frontmatter.permalinkPattern || app.options.route.permalinkPattern
3838

3939
if (!isString(permalinkPattern)) {
4040
return null

packages/core/src/types/app/options.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import type { Bundler } from '../bundler.js'
55
import type { PluginConfig } from '../plugin.js'
66
import type { Theme } from '../theme.js'
77

8+
export interface RouteOptions {
9+
cleanUrl?: boolean
10+
pagePatterns?: string[]
11+
permalinkPattern?: string | null
12+
}
13+
814
/**
915
* Vuepress app common config that shared between dev and build
1016
*/
@@ -14,11 +20,9 @@ export interface AppConfigCommon extends Partial<SiteData> {
1420
temp?: string
1521
cache?: string
1622
public?: string
17-
1823
debug?: boolean
1924
markdown?: MarkdownOptions
20-
pagePatterns?: string[]
21-
permalinkPattern?: string | null
25+
route?: RouteOptions
2226
bundler: Bundler
2327
theme: Theme
2428
plugins?: PluginConfig
@@ -95,9 +99,20 @@ export interface AppConfigBuild {
9599
/**
96100
* Vuepress app config
97101
*/
98-
export type AppConfig = AppConfigCommon & AppConfigDev & AppConfigBuild
102+
export type AppConfig = AppConfigCommon &
103+
AppConfigDev &
104+
AppConfigBuild & {
105+
/** @deprecated use route.pagePatterns instead */
106+
pagePatterns?: string[]
107+
/** @deprecated use route.permalinkPattern instead */
108+
permalinkPattern?: string | null
109+
}
99110

100111
/**
101112
* Vuepress app options
102113
*/
103-
export type AppOptions = Required<AppConfig>
114+
export type AppOptions = Required<
115+
AppConfigCommon & AppConfigDev & AppConfigBuild
116+
> & {
117+
route: Required<RouteOptions>
118+
}

packages/core/tests/app/resolveAppOptions.spec.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ describe('core > app > resolveAppOptions', () => {
3030
host: '0.0.0.0',
3131
port: 8080,
3232
open: false,
33-
pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'],
34-
permalinkPattern: null,
33+
route: {
34+
cleanUrl: false,
35+
pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'],
36+
permalinkPattern: null,
37+
},
3538
templateDev: path.normalize(
3639
require.resolve('@vuepress/client/templates/dev.html'),
3740
),

packages/core/tests/app/resolveAppPages.spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ describe('core > app > resolveAppPages', () => {
1313
app.markdown = createMarkdown()
1414

1515
const pages = await resolveAppPages(app)
16-
const fooPage = pages.find((page) => page.path === '/foo.html')
17-
const barPage = pages.find((page) => page.path === '/bar.html')
18-
const notFoundPage = pages.find((page) => page.path === '/404.html')
16+
const fooPage = pages.find((page) => page.path === '/foo')
17+
const barPage = pages.find((page) => page.path === '/bar')
18+
const notFoundPage = pages.find((page) => page.path === '/404')
1919

2020
expect(pages).toHaveLength(3)
2121
expect(fooPage?.filePathRelative).toEqual('foo.md')
@@ -33,9 +33,9 @@ describe('core > app > resolveAppPages', () => {
3333
app.markdown = createMarkdown()
3434

3535
const pages = await resolveAppPages(app)
36-
const fooPage = pages.find((page) => page.path === '/foo.html')
37-
const barPage = pages.find((page) => page.path === '/bar.html')
38-
const notFoundPage = pages.find((page) => page.path === '/404.html')
36+
const fooPage = pages.find((page) => page.path === '/foo')
37+
const barPage = pages.find((page) => page.path === '/bar')
38+
const notFoundPage = pages.find((page) => page.path === '/404')
3939

4040
expect(pages).toHaveLength(3)
4141
expect(fooPage?.filePathRelative).toEqual('foo.md')

packages/core/tests/page/resolvePagePath.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const testCases: [
4040
},
4141
},
4242
],
43-
'/options.html',
43+
'/options',
4444
],
4545
// use permalink
4646
[
@@ -92,7 +92,7 @@ const testCases: [
9292
options: {},
9393
},
9494
],
95-
'/inferred.html',
95+
'/inferred',
9696
],
9797
]
9898

0 commit comments

Comments
 (0)