From 06a3c0deb9259b86de6cc7c47f312fa0bb2395ee Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sat, 28 Jan 2023 14:38:21 +0530 Subject: [PATCH 1/3] feat: stable `cleanUrls` --- docs/.vitepress/config.ts | 2 +- docs/config/app-configs.md | 20 ++++++------------- docs/guide/routing.md | 16 ++------------- src/client/app/router.ts | 2 +- .../components/VPAlgoliaSearchBox.vue | 2 +- src/client/theme-default/composables/langs.ts | 2 +- src/client/theme-default/support/utils.ts | 2 +- src/node/build/render.ts | 9 +-------- src/node/config.ts | 17 +++++----------- src/node/markdown/env.ts | 4 ++-- src/node/markdown/plugins/link.ts | 7 ++----- src/node/markdownToVue.ts | 9 ++------- src/shared/shared.ts | 1 - types/shared.d.ts | 7 +------ 14 files changed, 26 insertions(+), 74 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index a87403697cb0..76bbe32820fe 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -10,7 +10,7 @@ export default defineConfig({ description: 'Vite & Vue powered static site generator.', lastUpdated: true, - cleanUrls: 'without-subfolders', + cleanUrls: true, head: [['meta', { name: 'theme-color', content: '#3c8772' }]], diff --git a/docs/config/app-configs.md b/docs/config/app-configs.md index de69006c3b92..c8c65775c08d 100644 --- a/docs/config/app-configs.md +++ b/docs/config/app-configs.md @@ -266,29 +266,21 @@ export default { } ``` -## cleanUrls (Experimental) +## cleanUrls -- Type: `'disabled' | 'without-subfolders' | 'with-subfolders'` -- Default: `'disabled'` +- Type: `boolean` +- Default: `false` -Allows removing trailing `.html` from URLs and, optionally, generating clean directory structure. +Allows removing trailing `.html` from URLs. ```ts export default { - cleanUrls: 'with-subfolders' + cleanUrls: true } ``` -This option has several modes you can choose. Here is the list of all modes available. - -| Mode | Page | Generated Page | URL | -| :--------------------- | :-------- | :---------------- | :---------- | -| `'disabled'` | `/foo.md` | `/foo.html` | `/foo.html` | -| `'without-subfolders'` | `/foo.md` | `/foo.html` | `/foo` | -| `'with-subfolders'` | `/foo.md` | `/foo/index.html` | `/foo` | - ::: warning -Enabling this may require additional configuration on your hosting platform. For it to work, your server must serve the generated page on requesting the URL **without a redirect**. +Enabling this may require additional configuration on your hosting platform. For it to work, your server must serve `/foo.html` on requesting `/foo` **without a redirect**. ::: ## rewrites diff --git a/docs/guide/routing.md b/docs/guide/routing.md index 917b4eebc9a9..ccc43d8b6d7c 100644 --- a/docs/guide/routing.md +++ b/docs/guide/routing.md @@ -88,26 +88,14 @@ By default, VitePress generates the final static page files by adding `.html` ex └─ index.md ``` -However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-configs#cleanurls-experimental) option. +However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-configs#cleanurls) option. ```ts export default { - cleanUrls: 'with-subfolders' + cleanUrls: true } ``` -This option has several modes you can choose. Here is the list of all modes available. The default behavior is `disabled` mode. - -| Mode | Page | Generated Page | URL | -| :--------------------- | :-------- | :---------------- | :---------- | -| `'disabled'` | `/foo.md` | `/foo.html` | `/foo.html` | -| `'without-subfolders'` | `/foo.md` | `/foo.html` | `/foo` | -| `'with-subfolders'` | `/foo.md` | `/foo/index.html` | `/foo` | - -::: warning -Enabling this may require additional configuration on your hosting platform. For it to work, your server must serve the generated page on requesting the URL **without a redirect**. -::: - ## Customize the Mappings You may customize the mapping between directory structure and URL. It's useful when you have complex document structure. For example, let's say you have several packages and would like to place documentations along with the source files like this. diff --git a/src/client/app/router.ts b/src/client/app/router.ts index 8b0972549e71..bce917765d95 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -49,7 +49,7 @@ export function createRouter( async function go(href: string = inBrowser ? location.href : '/') { await router.onBeforeRouteChange?.(href) const url = new URL(href, fakeHost) - if (siteDataRef.value.cleanUrls === 'disabled') { + if (!siteDataRef.value.cleanUrls) { // ensure correct deep link so page refresh lands on correct files. // if cleanUrls is enabled, the server should handle this if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) { diff --git a/src/client/theme-default/components/VPAlgoliaSearchBox.vue b/src/client/theme-default/components/VPAlgoliaSearchBox.vue index 63681dbdc694..00e90b159dc3 100644 --- a/src/client/theme-default/components/VPAlgoliaSearchBox.vue +++ b/src/client/theme-default/components/VPAlgoliaSearchBox.vue @@ -90,7 +90,7 @@ function getRelativePath(absoluteUrl: string) { return ( pathname.replace( /\.html$/, - site.value.cleanUrls === 'disabled' ? '.html' : '' + site.value.cleanUrls ? '' : '.html' ) + hash ) } diff --git a/src/client/theme-default/composables/langs.ts b/src/client/theme-default/composables/langs.ts index d80ccb265325..8000e2ab9034 100644 --- a/src/client/theme-default/composables/langs.ts +++ b/src/client/theme-default/composables/langs.ts @@ -24,7 +24,7 @@ export function useLangs({ value.link || (key === 'root' ? '/' : `/${key}/`), theme.value.i18nRouting !== false && correspondingLink, page.value.relativePath.slice(currentLang.value.link.length - 1), - site.value.cleanUrls === 'disabled' + !site.value.cleanUrls ) } ) diff --git a/src/client/theme-default/support/utils.ts b/src/client/theme-default/support/utils.ts index 2d35f94f8428..0af969eab432 100644 --- a/src/client/theme-default/support/utils.ts +++ b/src/client/theme-default/support/utils.ts @@ -44,7 +44,7 @@ export function normalizeLink(url: string): string { /(?:(^\.+)\/)?.*$/, `$1${pathname.replace( /(\.md)?$/, - site.value.cleanUrls === 'disabled' ? '.html' : '' + site.value.cleanUrls ? '' : '.html' )}${search}${hash}` ) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 920a31c78e9f..c8dd9d8995cd 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -173,14 +173,7 @@ export async function renderPage( ${inlinedScript} `.trim() - const createSubDirectory = - config.cleanUrls === 'with-subfolders' && - !/(^|\/)(index|404).md$/.test(page) - - const htmlFileName = path.join( - config.outDir, - page.replace(/\.md$/, createSubDirectory ? '/index.html' : '.html') - ) + const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html')) await fs.ensureDir(path.dirname(htmlFileName)) const transformedHtml = await config.transformHtml?.(html, htmlFileName, { diff --git a/src/node/config.ts b/src/node/config.ts index 3c186ddc4fb3..32872bba7550 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -16,7 +16,6 @@ import type { MarkdownOptions } from './markdown/markdown' import { APPEARANCE_KEY, type Awaitable, - type CleanUrlsMode, type DefaultTheme, type HeadConfig, type LocaleConfig, @@ -77,17 +76,11 @@ export interface UserConfig ignoreDeadLinks?: boolean | 'localhostLinks' /** - * @experimental - * Remove '.html' from URLs and generate clean directory structure. - * - * Available Modes: - * - `disabled`: generates `/foo.html` for every `/foo.md` and shows `/foo.html` in browser - * - `without-subfolders`: generates `/foo.html` for every `/foo.md` but shows `/foo` in browser - * - `with-subfolders`: generates `/foo/index.html` for every `/foo.md` and shows `/foo` in browser + * Don't force `.html` on URLs. * - * @default 'disabled' + * @default false */ - cleanUrls?: CleanUrlsMode + cleanUrls?: boolean /** * Use web fonts instead of emitting font files to dist. @@ -279,7 +272,7 @@ export async function resolveConfig( shouldPreload: userConfig.shouldPreload, mpa: !!userConfig.mpa, ignoreDeadLinks: userConfig.ignoreDeadLinks, - cleanUrls: userConfig.cleanUrls || 'disabled', + cleanUrls: !!userConfig.cleanUrls, useWebFonts: userConfig.useWebFonts ?? typeof process.versions.webcontainer === 'string', @@ -394,7 +387,7 @@ export async function resolveSiteData( themeConfig: userConfig.themeConfig || {}, locales: userConfig.locales || {}, scrollOffset: userConfig.scrollOffset || 90, - cleanUrls: userConfig.cleanUrls || 'disabled' + cleanUrls: !!userConfig.cleanUrls } } diff --git a/src/node/markdown/env.ts b/src/node/markdown/env.ts index 3dd5fef822e0..b079c2811c2e 100644 --- a/src/node/markdown/env.ts +++ b/src/node/markdown/env.ts @@ -1,5 +1,5 @@ import type { MarkdownSfcBlocks } from '@mdit-vue/plugin-sfc' -import type { CleanUrlsMode, Header } from '../shared' +import type { Header } from '../shared' // Manually declaring all properties as rollup-plugin-dts // is unable to merge augmented module declarations @@ -34,6 +34,6 @@ export interface MarkdownEnv { title?: string path: string relativePath: string - cleanUrls: CleanUrlsMode + cleanUrls: boolean links?: string[] } diff --git a/src/node/markdown/plugins/link.ts b/src/node/markdown/plugins/link.ts index 1582cc650095..4688f4c0c939 100644 --- a/src/node/markdown/plugins/link.ts +++ b/src/node/markdown/plugins/link.ts @@ -68,14 +68,11 @@ export const linkPlugin = ( let cleanUrl = url.replace(/[?#].*$/, '') // transform foo.md -> foo[.html] if (cleanUrl.endsWith('.md')) { - cleanUrl = cleanUrl.replace( - /\.md$/, - env.cleanUrls === 'disabled' ? '.html' : '' - ) + cleanUrl = cleanUrl.replace(/\.md$/, env.cleanUrls ? '' : '.html') } // transform ./foo -> ./foo[.html] if ( - env.cleanUrls === 'disabled' && + !env.cleanUrls && !cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/') ) { diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 2cc002dc6ed2..a5e9a2f109e3 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -4,12 +4,7 @@ import c from 'picocolors' import LRUCache from 'lru-cache' import { resolveTitleFromToken } from '@mdit-vue/shared' import type { SiteConfig } from './config' -import { - type PageData, - type HeadConfig, - EXTERNAL_URL_RE, - type CleanUrlsMode -} from './shared' +import { type PageData, type HeadConfig, EXTERNAL_URL_RE } from './shared' import { slash } from './utils/slash' import { getGitTimestamp } from './utils/getGitTimestamp' import { @@ -43,7 +38,7 @@ export async function createMarkdownToVueRenderFn( isBuild = false, base = '/', includeLastUpdatedData = false, - cleanUrls: CleanUrlsMode = 'disabled', + cleanUrls = false, siteConfig: SiteConfig | null = null ) { const md = await createMarkdownRenderer(srcDir, options, base) diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 653e0a12dd40..a2a43d821f3a 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -2,7 +2,6 @@ import type { HeadConfig, PageData, SiteData } from '../../types/shared.js' export type { Awaitable, - CleanUrlsMode, DefaultTheme, HeadConfig, Header, diff --git a/types/shared.d.ts b/types/shared.d.ts index 4429b0410e8c..8bacd8137fe1 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -43,14 +43,9 @@ export interface Header { children: Header[] } -export type CleanUrlsMode = - | 'disabled' - | 'without-subfolders' - | 'with-subfolders' - export interface SiteData { base: string - cleanUrls?: CleanUrlsMode + cleanUrls?: boolean lang: string dir: string title: string From 4428e241ea8a046d900c9de54e13aa2b26ef6b92 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sat, 28 Jan 2023 19:23:22 +0530 Subject: [PATCH 2/3] fix: handle trailing slash --- src/client/app/router.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/client/app/router.ts b/src/client/app/router.ts index bce917765d95..727088ab9f25 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -89,6 +89,22 @@ export function createRouter( if (inBrowser) { nextTick(() => { + let actualPathname = `/${__pageData.relativePath.replace( + /(?:(^|\/)index)?\.md$/, + '$1' + )}` + if (!siteDataRef.value.cleanUrls && !actualPathname.endsWith('/')) { + actualPathname += '.html' + } + if (actualPathname !== targetLoc.pathname) { + targetLoc.pathname = actualPathname + history.replaceState( + null, + '', + actualPathname + targetLoc.search + targetLoc.hash + ) + } + if (targetLoc.hash && !scrollPosition) { let target: HTMLElement | null = null try { From f9c98060a681ca7f1c5be26c2e311294194a5df5 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sat, 28 Jan 2023 19:33:24 +0530 Subject: [PATCH 3/3] cleanup --- src/client/app/router.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/client/app/router.ts b/src/client/app/router.ts index 727088ab9f25..f0ab3ec3903b 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -89,20 +89,16 @@ export function createRouter( if (inBrowser) { nextTick(() => { - let actualPathname = `/${__pageData.relativePath.replace( - /(?:(^|\/)index)?\.md$/, - '$1' - )}` + let actualPathname = + '/' + + __pageData.relativePath.replace(/(?:(^|\/)index)?\.md$/, '$1') if (!siteDataRef.value.cleanUrls && !actualPathname.endsWith('/')) { actualPathname += '.html' } if (actualPathname !== targetLoc.pathname) { targetLoc.pathname = actualPathname - history.replaceState( - null, - '', - actualPathname + targetLoc.search + targetLoc.hash - ) + href = actualPathname + targetLoc.search + targetLoc.hash + history.replaceState(null, '', href) } if (targetLoc.hash && !scrollPosition) {