From 670498804e60da0979bf3b35dff6da676a1bf141 Mon Sep 17 00:00:00 2001 From: Georges Gomes Date: Tue, 28 Dec 2021 15:16:01 +0100 Subject: [PATCH] feat(cleanUrls): Generate "clean urls" in SPA and MPA mode --- docs/.vitepress/config.ts | 1 + src/client/app/router.ts | 14 ++++++++++---- src/node/build/render.ts | 10 +++++++++- src/node/config.ts | 13 ++++++++++++- src/node/markdown/markdown.ts | 7 ++++--- src/node/markdown/plugins/link.ts | 15 ++++++++------- src/node/markdownToVue.ts | 7 +++++-- src/node/plugin.ts | 2 +- types/shared.d.ts | 3 ++- 9 files changed, 52 insertions(+), 20 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8ef0d400..ad95abb7 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,3 +1,4 @@ + export default { lang: 'en-US', title: 'VitePress', diff --git a/src/client/app/router.ts b/src/client/app/router.ts index e7f1c59a..7c56d631 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -2,6 +2,7 @@ import { reactive, inject, markRaw, nextTick, readonly } from 'vue' import type { Component, InjectionKey } from 'vue' import { PageData } from '../shared' import { inBrowser } from './utils' +import { siteDataRef } from './data' export interface Route { path: string @@ -42,10 +43,15 @@ 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) - if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) { - url.pathname += '.html' - href = url.pathname + url.search + url.hash + if (siteDataRef.value.cleanUrls) { + // TODO + } + 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) { // save scroll position before changing url diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 8bfe54d5..d15dab97 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -138,11 +138,19 @@ export async function renderPage( ${inlinedScript} `.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.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( config: SiteConfig, page: string, diff --git a/src/node/config.ts b/src/node/config.ts index d3ff1c70..dbd7e88d 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -54,6 +54,14 @@ export interface UserConfig { * @experimental */ 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 = @@ -73,6 +81,7 @@ export interface SiteConfig themeDir: string outDir: string tempDir: string + cleanUrls: boolean; alias: AliasOptions pages: string[] } @@ -124,6 +133,7 @@ export async function resolveConfig( configPath, outDir: resolve(root, 'dist'), tempDir: path.resolve(APP_PATH, 'temp'), + cleanUrls: !!userConfig.cleanUrls, markdown: userConfig.markdown, alias: resolveAliases(themeDir), vue: userConfig.vue, @@ -229,6 +239,7 @@ export async function resolveSiteData( head: userConfig.head || [], themeConfig: userConfig.themeConfig || {}, locales: userConfig.locales || {}, - langs: createLangDictionary(userConfig) + langs: createLangDictionary(userConfig), + cleanUrls: userConfig.cleanUrls || false } } diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index ff8a637f..41b0c4af 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -16,6 +16,7 @@ import anchor from 'markdown-it-anchor' import attrs from 'markdown-it-attrs' import emoji from 'markdown-it-emoji' import toc from 'markdown-it-table-of-contents' +import { SiteConfig } from 'config' export interface MarkdownOptions extends MarkdownIt.Options { lineNumbers?: boolean @@ -47,7 +48,7 @@ export interface MarkdownRenderer { export type { Header } export const createMarkdownRenderer = ( - srcDir: string, + siteConfig: SiteConfig, options: MarkdownOptions = {} ): MarkdownRenderer => { const md = MarkdownIt({ @@ -61,7 +62,7 @@ export const createMarkdownRenderer = ( md.use(componentPlugin) .use(highlightLinePlugin) .use(preWrapperPlugin) - .use(snippetPlugin, srcDir) + .use(snippetPlugin, siteConfig.srcDir) .use(hoistPlugin) .use(containerPlugin) .use(extractHeaderPlugin) @@ -69,7 +70,7 @@ export const createMarkdownRenderer = ( target: '_blank', rel: 'noopener noreferrer', ...options.externalLinks - }) + }, siteConfig.cleanUrls) // 3rd party plugins .use(attrs, options.attrs) .use(anchor, { diff --git a/src/node/markdown/plugins/link.ts b/src/node/markdown/plugins/link.ts index 7d7c71f7..14120094 100644 --- a/src/node/markdown/plugins/link.ts +++ b/src/node/markdown/plugins/link.ts @@ -11,7 +11,8 @@ const indexRE = /(^|.*\/)index.md(#?.*)$/i export const linkPlugin = ( md: MarkdownIt, - externalAttrs: Record + externalAttrs: Record, + shouldCleanUrls: boolean ) => { md.renderer.rules.link_open = (tokens, idx, options, env, self) => { const token = tokens[idx] @@ -30,7 +31,7 @@ export const linkPlugin = ( // mail links !url.startsWith('mailto:') ) { - normalizeHref(hrefAttr) + normalizeHref(hrefAttr, shouldCleanUrls) } // encode vite-specific replace strings in case they appear in URLs @@ -43,7 +44,7 @@ export const linkPlugin = ( return self.renderToken(tokens, idx, options) } - function normalizeHref(hrefAttr: [string, string]) { + function normalizeHref(hrefAttr: [string, string], shouldCleanUrls: boolean) { let url = hrefAttr[1] const indexMatch = url.match(indexRE) @@ -52,12 +53,12 @@ export const linkPlugin = ( url = path + hash } else { let cleanUrl = url.replace(/\#.*$/, '').replace(/\?.*$/, '') - // .md -> .html + // transform foo.md -> foo[.html] if (cleanUrl.endsWith('.md')) { - cleanUrl = cleanUrl.replace(/\.md$/, '.html') + cleanUrl = cleanUrl.replace(/\.md$/, shouldCleanUrls ? '' : '.html') } - // ./foo -> ./foo.html - if (!cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/')) { + // transform ./foo -> ./foo[.html] + if (!shouldCleanUrls && !cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/')) { cleanUrl += '.html' } const parsed = new URL(url, 'http://a.com') diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index bc01c15d..e6993e39 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -8,6 +8,7 @@ import { PageData, HeadConfig } from './shared' import { slash } from './utils/slash' import chalk from 'chalk' import _debug from 'debug' +import { SiteConfig } from 'config' const debug = _debug('vitepress:md') const cache = new LRUCache({ max: 1024 }) @@ -21,13 +22,15 @@ export interface MarkdownCompileResult { } export function createMarkdownToVueRenderFn( - srcDir: string, + siteConfig: SiteConfig, options: MarkdownOptions = {}, pages: string[], userDefines: Record | undefined, isBuild = false ) { - const md = createMarkdownRenderer(srcDir, options) + const { srcDir } = siteConfig; + + const md = createMarkdownRenderer(siteConfig, options) pages = pages.map((p) => slash(p.replace(/\.md$/, ''))) const userDefineRegex = userDefines diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 7853d5c0..43dd31fa 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -80,7 +80,7 @@ export function createVitePressPlugin( configResolved(resolvedConfig) { config = resolvedConfig markdownToVue = createMarkdownToVueRenderFn( - srcDir, + siteConfig, markdown, pages, config.define, diff --git a/types/shared.d.ts b/types/shared.d.ts index 4f6558a6..e4dd515b 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -40,7 +40,8 @@ export interface SiteData { */ label: string } - > + >, + cleanUrls: boolean; } export type HeadConfig =