feat(cleanUrls): Generate "clean urls" in SPA and MPA mode

pull/488/head
Georges Gomes 4 years ago
parent 0c31bbac85
commit 3d83dea50d

@ -49,10 +49,15 @@ export function createRouter(
function go(href: string = inBrowser ? location.href : '/') { function go(href: string = inBrowser ? location.href : '/') {
// ensure correct deep link so page refresh lands on correct files. // ensure correct deep link so page refresh lands on correct files.
const url = new URL(href, fakeHost) if (siteDataRef.value.cleanUrls) {
if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) { // TODO
url.pathname += '.html' }
href = url.pathname + url.search + url.hash else {
const url = new URL(href, fakeHost)
if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
url.pathname += '.html'
href = url.pathname + url.search + url.hash
}
} }
if (inBrowser) { if (inBrowser) {
// save scroll position before changing url // save scroll position before changing url

@ -150,11 +150,19 @@ export async function renderPage(
${inlinedScript} ${inlinedScript}
</body> </body>
</html>`.trim() </html>`.trim()
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html')) const htmlFileName = path.join(config.outDir, transformHTMLFileName(page, config.cleanUrls));
await fs.ensureDir(path.dirname(htmlFileName)) await fs.ensureDir(path.dirname(htmlFileName))
await fs.writeFile(htmlFileName, html) await fs.writeFile(htmlFileName, html)
} }
function transformHTMLFileName(page: string, shouldCleanUrls: boolean): string {
if (page === 'index.md' || page.endsWith('/index.md')) {
return page.replace(/\.md$/, '.html');
}
return page.replace(/\.md$/, shouldCleanUrls ? '/index.html' : '.html');
}
function resolvePageImports( function resolvePageImports(
config: SiteConfig, config: SiteConfig,
page: string, page: string,

@ -63,6 +63,14 @@ export interface UserConfig<ThemeConfig = any> {
* @experimental * @experimental
*/ */
mpa?: boolean mpa?: boolean
/**
* Always use "clean URLs" without the `.html`.
* Also generate static files as `foo/index.html` insted of `foo.html`.
* (default: false)
* @experimental (Works better with mpa mode)
*/
cleanUrls?: boolean
} }
export type RawConfigExports<ThemeConfig = any> = export type RawConfigExports<ThemeConfig = any> =
@ -82,6 +90,7 @@ export interface SiteConfig<ThemeConfig = any>
themeDir: string themeDir: string
outDir: string outDir: string
tempDir: string tempDir: string
cleanUrls: boolean;
alias: AliasOptions alias: AliasOptions
pages: string[] pages: string[]
} }
@ -145,6 +154,7 @@ export async function resolveConfig(
configPath, configPath,
outDir, outDir,
tempDir: resolve(root, '.temp'), tempDir: resolve(root, '.temp'),
cleanUrls: !!userConfig.cleanUrls,
markdown: userConfig.markdown, markdown: userConfig.markdown,
lastUpdated: userConfig.lastUpdated, lastUpdated: userConfig.lastUpdated,
alias: resolveAliases(root, themeDir), alias: resolveAliases(root, themeDir),
@ -252,6 +262,7 @@ export async function resolveSiteData(
themeConfig: userConfig.themeConfig || {}, themeConfig: userConfig.themeConfig || {},
locales: userConfig.locales || {}, locales: userConfig.locales || {},
langs: createLangDictionary(userConfig), langs: createLangDictionary(userConfig),
scrollOffset: userConfig.scrollOffset || 90 scrollOffset: userConfig.scrollOffset || 90,
cleanUrls: userConfig.cleanUrls || false
} }
} }

@ -17,6 +17,7 @@ import anchor from 'markdown-it-anchor'
import attrs from 'markdown-it-attrs' import attrs from 'markdown-it-attrs'
import emoji from 'markdown-it-emoji' import emoji from 'markdown-it-emoji'
import toc from 'markdown-it-table-of-contents' import toc from 'markdown-it-table-of-contents'
import { SiteConfig } from 'config'
export interface MarkdownOptions extends MarkdownIt.Options { export interface MarkdownOptions extends MarkdownIt.Options {
lineNumbers?: boolean lineNumbers?: boolean
@ -51,7 +52,8 @@ export type { Header }
export const createMarkdownRenderer = ( export const createMarkdownRenderer = (
srcDir: string, srcDir: string,
options: MarkdownOptions = {}, options: MarkdownOptions = {},
base: string base: string,
cleanUrls: boolean = false,
): MarkdownRenderer => { ): MarkdownRenderer => {
const md = MarkdownIt({ const md = MarkdownIt({
html: true, html: true,
@ -76,7 +78,7 @@ export const createMarkdownRenderer = (
rel: 'noopener noreferrer', rel: 'noopener noreferrer',
...options.externalLinks ...options.externalLinks
}, },
base base, cleanUrls
) )
// 3rd party plugins // 3rd party plugins
.use(attrs, options.attrs) .use(attrs, options.attrs)

@ -13,6 +13,7 @@ export const linkPlugin = (
md: MarkdownIt, md: MarkdownIt,
externalAttrs: Record<string, string>, externalAttrs: Record<string, string>,
base: string base: string
shouldCleanUrls: boolean
) => { ) => {
md.renderer.rules.link_open = (tokens, idx, options, env, self) => { md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const token = tokens[idx] const token = tokens[idx]
@ -37,7 +38,7 @@ export const linkPlugin = (
// links to files (other than html/md) // links to files (other than html/md)
!/\.(?!html|md)\w+($|\?)/i.test(url) !/\.(?!html|md)\w+($|\?)/i.test(url)
) { ) {
normalizeHref(hrefAttr) normalizeHref(hrefAttr, shouldCleanUrls)
} }
// encode vite-specific replace strings in case they appear in URLs // encode vite-specific replace strings in case they appear in URLs
@ -50,7 +51,7 @@ export const linkPlugin = (
return self.renderToken(tokens, idx, options) return self.renderToken(tokens, idx, options)
} }
function normalizeHref(hrefAttr: [string, string]) { function normalizeHref(hrefAttr: [string, string], shouldCleanUrls: boolean) {
let url = hrefAttr[1] let url = hrefAttr[1]
const indexMatch = url.match(indexRE) const indexMatch = url.match(indexRE)
@ -58,13 +59,13 @@ export const linkPlugin = (
const [, path, hash] = indexMatch const [, path, hash] = indexMatch
url = path + hash url = path + hash
} else { } else {
let cleanUrl = url.replace(/[?#].*$/, '') let cleanUrl = url.replace(/[?#].*$/, '').replace(/\?.*$/, '')
// .md -> .html // transform foo.md -> foo[.html]
if (cleanUrl.endsWith('.md')) { if (cleanUrl.endsWith('.md')) {
cleanUrl = cleanUrl.replace(/\.md$/, '.html') cleanUrl = cleanUrl.replace(/\.md$/, shouldCleanUrls ? '' : '.html')
} }
// ./foo -> ./foo.html // transform ./foo -> ./foo[.html]
if (!cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/')) { if (!shouldCleanUrls && !cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/')) {
cleanUrl += '.html' cleanUrl += '.html'
} }
const parsed = new URL(url, 'http://a.com') const parsed = new URL(url, 'http://a.com')

@ -28,9 +28,10 @@ export function createMarkdownToVueRenderFn(
userDefines: Record<string, any> | undefined, userDefines: Record<string, any> | undefined,
isBuild = false, isBuild = false,
base: string, base: string,
includeLastUpdatedData = false includeLastUpdatedData = false,
cleanUrls: boolean = false
) { ) {
const md = createMarkdownRenderer(srcDir, options, base) const md = createMarkdownRenderer(srcDir, options, base, cleanUrls);
pages = pages.map((p) => slash(p.replace(/\.md$/, ''))) pages = pages.map((p) => slash(p.replace(/\.md$/, '')))
const userDefineRegex = userDefines const userDefineRegex = userDefines

@ -43,7 +43,8 @@ export function createVitePressPlugin(
site, site,
vue: userVuePluginOptions, vue: userVuePluginOptions,
vite: userViteConfig, vite: userViteConfig,
pages pages,
cleanUrls
} = siteConfig } = siteConfig
let markdownToVue: ReturnType<typeof createMarkdownToVueRenderFn> let markdownToVue: ReturnType<typeof createMarkdownToVueRenderFn>
@ -79,7 +80,8 @@ export function createVitePressPlugin(
config.define, config.define,
config.command === 'build', config.command === 'build',
config.base, config.base,
siteConfig.lastUpdated siteConfig.lastUpdated,
cleanUrls
) )
}, },

3
types/shared.d.ts vendored

@ -43,7 +43,8 @@ export interface SiteData<ThemeConfig = any> {
*/ */
label: string label: string
} }
> >,
cleanUrls: boolean;
} }
export type HeadConfig = export type HeadConfig =

Loading…
Cancel
Save