Skip to content

Commit

Permalink
feat: support clean url and improve route resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed May 22, 2024
1 parent 46bd7ba commit 8ce1d27
Show file tree
Hide file tree
Showing 25 changed files with 335 additions and 247 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -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'],
Expand Down
1 change: 1 addition & 0 deletions packages/bundler-vite/src/plugins/vuepressMainPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/bundler-webpack/src/config/handlePluginDefine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/dev/watchPageFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
16 changes: 9 additions & 7 deletions packages/client/src/router/resolveRoute.ts
Original file line number Diff line number Diff line change
@@ -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<T extends RouteMeta = RouteMeta>
extends Route<T> {
Expand All @@ -20,20 +20,22 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
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<T>
}

return {
...routes.value[routePath],
...routes.value[routeKey],
path: routeFullPath,
notFound: false,
} as ResolvedRoute<T>
Expand Down
34 changes: 34 additions & 0 deletions packages/client/src/router/resolveRouteKey.ts
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 5 additions & 24 deletions packages/client/src/router/resolveRoutePath.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
7 changes: 4 additions & 3 deletions packages/client/src/setupGlobalComputed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/client/types.d.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>
declare const __VUE_OPTIONS_API__: boolean
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/app/prepare/prepareRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]),
),
),
),
Expand All @@ -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')}
]);
Expand Down
19 changes: 15 additions & 4 deletions packages/core/src/app/resolveAppOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand All @@ -65,8 +73,11 @@ export const resolveAppOptions = ({
bundler,
debug,
markdown,
pagePatterns,
permalinkPattern,
route: {
cleanUrl,
pagePatterns,
permalinkPattern,
},
plugins,
theme,
})
4 changes: 2 additions & 2 deletions packages/core/src/app/resolveAppPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => {
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(),
})
Expand All @@ -22,7 +22,7 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => {
)

// 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) {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/page/createPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -118,6 +122,7 @@ export const createPage = async (
pathInferred,
pathLocale,
permalink,
routeKey,
routeMeta,
sfcBlocks,
slug,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/page/resolvePagePermalink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/page/resolvePageRouteKey.ts
Original file line number Diff line number Diff line change
@@ -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, '/')
25 changes: 20 additions & 5 deletions packages/core/src/types/app/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -14,11 +20,9 @@ export interface AppConfigCommon extends Partial<SiteData> {
temp?: string
cache?: string
public?: string

debug?: boolean
markdown?: MarkdownOptions
pagePatterns?: string[]
permalinkPattern?: string | null
route?: RouteOptions
bundler: Bundler
theme: Theme
plugins?: PluginConfig
Expand Down Expand Up @@ -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<AppConfig>
export type AppOptions = Required<
AppConfigCommon & AppConfigDev & AppConfigBuild
> & {
route: Required<RouteOptions>
}
5 changes: 5 additions & 0 deletions packages/core/src/types/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ export type Page<
*/
permalink: string | null

/**
* Key in routes record
*/
routeKey: string

/**
* Custom data to be attached to route record
*/
Expand Down
7 changes: 5 additions & 2 deletions packages/core/tests/app/resolveAppOptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
),
Expand Down
Loading

0 comments on commit 8ce1d27

Please sign in to comment.