Skip to content

Commit 8ce1d27

Browse files
committed
feat: support clean url and improve route resolve
1 parent 46bd7ba commit 8ce1d27

25 files changed

+335
-247
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 { resolvePathInfo } 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> {
@@ -20,20 +20,22 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
2020
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 {
29-
...routes.value['/404.html'],
31+
...routes.value['/404'],
3032
path: routeFullPath,
3133
notFound: true,
3234
} as ResolvedRoute<T>
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
@@ -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/src/setupGlobalComputed.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { clientDataSymbol } from './composables/index.js'
55
import { redirects, routes } from './internal/routes.js'
66
import { siteData } from './internal/siteData.js'
77
import { resolvers } from './resolvers.js'
8+
import { resolveRouteKey } from './router/resolveRouteKey.js'
89
import type {
910
ClientConfig,
1011
ClientData,
@@ -46,10 +47,10 @@ export const setupGlobalComputed = (
4647
// handle page data HMR
4748
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
4849
__VUE_HMR_RUNTIME__.updatePageData = async (newPageData: PageData) => {
49-
const oldPageChunk = await routes.value[newPageData.path].loader()
50+
const routeKey = resolveRouteKey(newPageData.path)
51+
const oldPageChunk = await routes.value[routeKey].loader()
5052
const newPageChunk = { comp: oldPageChunk.comp, data: newPageData }
51-
routes.value[newPageData.path].loader = () =>
52-
Promise.resolve(newPageChunk)
53+
routes.value[routeKey].loader = () => Promise.resolve(newPageChunk)
5354
if (
5455
newPageData.path ===
5556
router.currentRoute.value.meta._pageChunk?.data.path

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/prepare/prepareRoutes.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ export const redirects = JSON.parse(${JSON.stringify(
5151
JSON.stringify(
5252
Object.fromEntries(
5353
app.pages.flatMap((page) =>
54-
resolvePageRedirects(page).map((redirect) => [redirect, page.path]),
54+
resolvePageRedirects(page).map((redirect) => [
55+
redirect,
56+
page.routeKey,
57+
]),
5558
),
5659
),
5760
),
@@ -60,8 +63,8 @@ export const redirects = JSON.parse(${JSON.stringify(
6063
export const routes = Object.fromEntries([
6164
${app.pages
6265
.map(
63-
({ chunkFilePath, chunkName, path, routeMeta }) =>
64-
` [${JSON.stringify(path)}, { loader: () => import(${chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''}${JSON.stringify(chunkFilePath)}), meta: ${JSON.stringify(routeMeta)} }],`,
66+
({ chunkFilePath, chunkName, routeKey, routeMeta }) =>
67+
` [${JSON.stringify(routeKey)}, { loader: () => import(${chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''}${JSON.stringify(chunkFilePath)}), meta: ${JSON.stringify(routeMeta)} }],`,
6568
)
6669
.join('\n')}
6770
]);

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

+2-2
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.routeKey === '/404')
2626

2727
// if there is a 404 page, set the default layout to NotFound
2828
if (notFoundPage) {

packages/core/src/page/createPage.ts

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { resolvePageHtmlInfo } from './resolvePageHtmlInfo.js'
1010
import { resolvePageLang } from './resolvePageLang.js'
1111
import { resolvePagePath } from './resolvePagePath.js'
1212
import { resolvePagePermalink } from './resolvePagePermalink.js'
13+
import { resolvePageRouteKey } from './resolvePageRouteKey.js'
1314
import { resolvePageRouteMeta } from './resolvePageRouteMeta.js'
1415
import { resolvePageSlug } from './resolvePageSlug.js'
1516

@@ -75,6 +76,9 @@ export const createPage = async (
7576
// resolve page path
7677
const path = resolvePagePath({ permalink, pathInferred, options })
7778

79+
// resolve page routeKey
80+
const routeKey = resolvePageRouteKey(path)
81+
7882
// resolve page rendered html file path
7983
const { htmlFilePath, htmlFilePathRelative } = resolvePageHtmlInfo({
8084
app,
@@ -118,6 +122,7 @@ export const createPage = async (
118122
pathInferred,
119123
pathLocale,
120124
permalink,
125+
routeKey,
121126
routeMeta,
122127
sfcBlocks,
123128
slug,

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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Resolve the final route path of a page
3+
*/
4+
export const resolvePageRouteKey = (path: string): string =>
5+
// convert to the clean format
6+
path.replace(/\.html$/, '').replace(/\/index$/i, '/')

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/src/types/page.ts

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ export type Page<
7272
*/
7373
permalink: string | null
7474

75+
/**
76+
* Key in routes record
77+
*/
78+
routeKey: string
79+
7580
/**
7681
* Custom data to be attached to route record
7782
*/

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
),

0 commit comments

Comments
 (0)