trailing slash option

pull/869/head
Georges Gomes 3 years ago
parent a745d1bab9
commit dc49b6fcf1

@ -8,7 +8,7 @@ export default defineConfig({
description: 'Vite & Vue powered static site generator.',
lastUpdated: true,
cleanUrls: true,
cleanUrls: 'with-trailing-slash',
themeConfig: {
nav: nav(),

@ -175,18 +175,26 @@ export default {
## cleanUrls
- Type: `boolean`
- Default: `false`
- Type: `"off" | "with-trailing-slash" | "without-trailing-slash"`
- Default: `"off"`
When set to `true`, page `foo/bar.md` is generated into `foo/bar/index.html` instead of `foo/bar.html`. This gives URL location look like `foo/bar` instead of `foo/bar.html`.
When set to `"off"`, page `foo/bar.md` is generated into `foo/bar.html`.
Also work in MPA mode.
When set to `"with-trailing-slash"` or `"without-trailing-slash"`, page `foo/bar.md` is generated into `foo/bar/index.html`.
Note: `404.md` page is kept transforming to `404.html` for hosting services.
When set to `"with-trailing-slash"`, URLs will be `foo/bar/`.
When set to `"without-trailing-slash"`, URLs will be `foo/bar`.
Notes:
- `404.md` page is kept transforming to `404.html` for hosting services.
- Also work in MPA mode.
```ts
export default {
cleanUrls: true
cleanUrls: "without-trailing-slash"
}
```
### Hosting on Netlify
Always use `"off"` or `"with-trailing-slash"` when hosted on Netlify.

@ -42,11 +42,23 @@ export function createRouter(
function go(href: string = inBrowser ? location.href : '/') {
// ensure correct deep link so page refresh lands on correct files.
const url = new URL(href, fakeHost)
// ensure correct deep link so page refresh lands on correct files.
if (siteDataRef.value.cleanUrls === 'off') {
// No clean URLs
// Let's add ".html" if missing
if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
url.pathname += siteDataRef.value.cleanUrls ? '' : '.html'
url.pathname += '.html'
href = url.pathname + url.search + url.hash
}
} else if (
siteDataRef.value.cleanUrls === 'with-trailing-slash' &&
!url.pathname.endsWith('/')
) {
// Clean URLs with trailing slash
// Let's add missing slashes
url.pathname += '/'
href = url.pathname + url.search + url.hash
}
if (inBrowser) {
// save scroll position before changing url
history.replaceState({ scrollPosition: window.scrollY }, document.title)

@ -1,5 +1,6 @@
import { ref } from 'vue'
import { withBase } from 'vitepress'
import { cleanUrlsOptions } from '../../../../types/shared'
export const HASH_RE = /#.*$/
export const EXT_RE = /(index)?\.(md|html)$/
@ -69,7 +70,10 @@ export function normalize(path: string): string {
return decodeURI(path).replace(HASH_RE, '').replace(EXT_RE, '')
}
export function normalizeLink(url: string, cleanUrls: boolean = false): string {
export function normalizeLink(
url: string,
cleanUrls: cleanUrlsOptions = 'off'
): string {
if (isExternal(url)) {
return url
}
@ -81,7 +85,11 @@ export function normalizeLink(url: string, cleanUrls: boolean = false): string {
? url
: `${pathname.replace(
/(\.md)?$/,
cleanUrls ? '' : '.html'
cleanUrls === 'off'
? '.html'
: cleanUrls === 'with-trailing-slash'
? '/'
: ''
)}${search}${hash}`
return withBase(normalizedPath)

@ -7,6 +7,7 @@ import { RollupOutput, OutputChunk, OutputAsset } from 'rollup'
import { HeadConfig, PageData, createTitle, notFoundPageData } from '../shared'
import { slash } from '../utils/slash'
import { SiteConfig, resolveSiteDataByRoute } from '../config'
import { cleanUrlsOptions } from '../../../types/shared'
export async function renderPage(
config: SiteConfig,
@ -158,12 +159,18 @@ export async function renderPage(
await fs.writeFile(htmlFileName, html)
}
function transformHTMLFileName(page: string, shouldCleanUrls: boolean): string {
function transformHTMLFileName(
page: string,
shouldCleanUrls: cleanUrlsOptions
): string {
if (page === 'index.md' || page.endsWith('/index.md') || page === '404.md') {
return page.replace(/\.md$/, '.html')
}
return page.replace(/\.md$/, shouldCleanUrls ? '/index.html' : '.html')
return page.replace(
/\.md$/,
shouldCleanUrls !== 'off' ? '/index.html' : '.html'
)
}
function resolvePageImports(

@ -21,6 +21,7 @@ import {
import { resolveAliases, DEFAULT_THEME_PATH } from './alias'
import { MarkdownOptions } from './markdown/markdown'
import _debug from 'debug'
import { cleanUrlsOptions } from '../../types/shared'
export { resolveSiteDataByRoute } from './shared'
@ -77,7 +78,7 @@ export interface UserConfig<ThemeConfig = any> {
* Also generate static files as `foo/index.html` insted of `foo.html`.
* @default false
*/
cleanUrls?: boolean
cleanUrls?: cleanUrlsOptions
}
export type RawConfigExports<ThemeConfig = any> =
@ -105,7 +106,7 @@ export interface SiteConfig<ThemeConfig = any>
tempDir: string
alias: AliasOptions
pages: string[]
cleanUrls: boolean
cleanUrls: cleanUrlsOptions
}
const resolve = (root: string, file: string) =>
@ -175,7 +176,7 @@ export async function resolveConfig(
shouldPreload: userConfig.shouldPreload,
mpa: !!userConfig.mpa,
ignoreDeadLinks: userConfig.ignoreDeadLinks,
cleanUrls: !!userConfig.cleanUrls
cleanUrls: userConfig.cleanUrls || 'off'
}
return config
@ -280,7 +281,7 @@ export async function resolveSiteData(
locales: userConfig.locales || {},
langs: createLangDictionary(userConfig),
scrollOffset: userConfig.scrollOffset || 90,
cleanUrls: userConfig.cleanUrls || false
cleanUrls: userConfig.cleanUrls || 'off'
}
}

@ -18,6 +18,7 @@ import anchor from 'markdown-it-anchor'
import attrs from 'markdown-it-attrs'
import emoji from 'markdown-it-emoji'
import toc from 'markdown-it-toc-done-right'
import { cleanUrlsOptions } from '../../../types/shared'
export type ThemeOptions = Theme | { light: Theme; dark: Theme }
@ -57,7 +58,7 @@ export const createMarkdownRenderer = async (
srcDir: string,
options: MarkdownOptions = {},
base = '/',
cleanUrls = false
cleanUrls: cleanUrlsOptions = 'off'
): Promise<MarkdownRenderer> => {
const md = MarkdownIt({
html: true,

@ -6,6 +6,7 @@ import MarkdownIt from 'markdown-it'
import { MarkdownRenderer } from '../markdown'
import { URL } from 'url'
import { EXTERNAL_URL_RE } from '../../shared'
import { cleanUrlsOptions } from '../../../../types/shared'
const indexRE = /(^|.*\/)index.md(#?.*)$/i
@ -13,7 +14,7 @@ export const linkPlugin = (
md: MarkdownIt,
externalAttrs: Record<string, string>,
base: string,
shouldCleanUrls: boolean
shouldCleanUrls: cleanUrlsOptions
) => {
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const token = tokens[idx]
@ -51,7 +52,10 @@ export const linkPlugin = (
return self.renderToken(tokens, idx, options)
}
function normalizeHref(hrefAttr: [string, string], shouldCleanUrls: boolean) {
function normalizeHref(
hrefAttr: [string, string],
shouldCleanUrls: cleanUrlsOptions
) {
let url = hrefAttr[1]
const indexMatch = url.match(indexRE)
@ -62,11 +66,18 @@ export const linkPlugin = (
let cleanUrl = url.replace(/[?#].*$/, '').replace(/\?.*$/, '')
// transform foo.md -> foo[.html]
if (cleanUrl.endsWith('.md')) {
cleanUrl = cleanUrl.replace(/\.md$/, shouldCleanUrls ? '' : '.html')
cleanUrl = cleanUrl.replace(
/\.md$/,
shouldCleanUrls === 'off'
? '.html'
: shouldCleanUrls === 'with-trailing-slash'
? '/'
: ''
)
}
// transform ./foo -> ./foo[.html]
if (
!shouldCleanUrls &&
shouldCleanUrls === 'off' &&
!cleanUrl.endsWith('.html') &&
!cleanUrl.endsWith('/')
) {

@ -9,6 +9,7 @@ import { deeplyParseHeader } from './utils/parseHeader'
import { getGitTimestamp } from './utils/getGitTimestamp'
import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown'
import _debug from 'debug'
import { cleanUrlsOptions } from '../../types/shared'
const debug = _debug('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
@ -29,7 +30,7 @@ export async function createMarkdownToVueRenderFn(
isBuild = false,
base = '/',
includeLastUpdatedData = false,
cleanUrls: boolean = false
cleanUrls: cleanUrlsOptions = 'off'
) {
const md = await createMarkdownRenderer(srcDir, options, base, cleanUrls)

7
types/shared.d.ts vendored

@ -18,6 +18,11 @@ export interface Header {
slug: string
}
export type cleanUrlsOptions =
| 'off'
| 'with-trailing-slash'
| 'without-trailing-slash'
export interface SiteData<ThemeConfig = any> {
base: string
@ -57,7 +62,7 @@ export interface SiteData<ThemeConfig = any> {
label: string
}
>
cleanUrls: boolean
cleanUrls: cleanUrlsOptions
}
export type HeadConfig =

Loading…
Cancel
Save