From 5225534ba7e8142be6e76b36bd9553801002591e Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 15:27:34 +0530 Subject: [PATCH 1/9] feat(build): page remap feature --- src/node/build/build.ts | 4 +++- src/node/build/bundle.ts | 3 ++- src/node/build/render.ts | 6 ++++++ src/node/config.ts | 9 ++++++++- src/node/markdownToVue.ts | 6 ++++++ src/node/plugin.ts | 13 ++++++++++++- 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 36a66483562b..919c508ab91c 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -64,7 +64,9 @@ export async function build( // as JS object literal. const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap)) - const pages = ['404.md', ...siteConfig.pages] + const pages = ['404.md', ...siteConfig.pages].map( + (page) => siteConfig.remap?.[`/${page}`]?.slice(1) || page + ) await Promise.all( pages.map((page) => diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index db8e022f37ad..be9ce1d2a37b 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -37,7 +37,8 @@ export async function bundle( config.pages.forEach((file) => { // page filename conversion // foo/bar.md -> foo_bar.md - input[slash(file).replace(/\//g, '_')] = path.resolve(config.srcDir, file) + const alias = config.remap?.[`/${file}`]?.slice(1) || file + input[slash(alias).replace(/\//g, '_')] = path.resolve(config.srcDir, file) }) // resolve options to pass to vite diff --git a/src/node/build/render.ts b/src/node/build/render.ts index e695eec609dc..66afc4817b40 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -202,6 +202,12 @@ function resolvePageImports( ) { // find the page's js chunk and inject script tags for its imports so that // they start fetching as early as possible + Object.entries(config.remap || {}).some(([key, val]) => { + if (page === val.slice(1)) { + page = key.slice(1) + return true + } + }) const srcPath = normalizePath( fs.realpathSync(path.resolve(config.srcDir, page)) ) diff --git a/src/node/config.ts b/src/node/config.ts index bda1406ea6dc..71f6b661bcc8 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -99,6 +99,11 @@ export interface UserConfig { */ useWebFonts?: boolean + /** + * @experimental + */ + remap?: Record + /** * Build end hook: called when SSG finish. * @param siteConfig The resolved configuration. @@ -165,6 +170,7 @@ export interface SiteConfig | 'transformHead' | 'transformHtml' | 'transformPageData' + | 'remap' > { root: string srcDir: string @@ -261,7 +267,8 @@ export async function resolveConfig( buildEnd: userConfig.buildEnd, transformHead: userConfig.transformHead, transformHtml: userConfig.transformHtml, - transformPageData: userConfig.transformPageData + transformPageData: userConfig.transformPageData, + remap: userConfig.remap } return config diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 70b185a5cf99..29fd1971c0f5 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -55,6 +55,12 @@ export async function createMarkdownToVueRenderFn( file: string, publicDir: string ): Promise => { + Object.entries(siteConfig?.remap || {}).some(([key, val]) => { + if (file.endsWith(key)) { + file = file.slice(0, -key.length) + val + return true + } + }) const relativePath = slash(path.relative(srcDir, file)) const dir = path.dirname(file) const cacheKey = JSON.stringify({ src, file }) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index fb68788ad18c..dad51b77a125 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -63,7 +63,8 @@ export async function createVitePressPlugin( pages, ignoreDeadLinks, lastUpdated, - cleanUrls + cleanUrls, + remap } = siteConfig let markdownToVue: Awaited> @@ -189,6 +190,16 @@ export async function createVitePressPlugin( configDeps.forEach((file) => server.watcher.add(file)) } + server.middlewares.use((req, res, next) => { + Object.entries(remap || {}).some(([key, val]) => { + if (req.url?.includes(val)) { + req.url = req.url?.replace(val, key) + return true + } + }) + next() + }) + // serve our index.html after vite history fallback return () => { server.middlewares.use(async (req, res, next) => { From 761319e7d3aecf7d7bc3fce310a202a12a7d7da0 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:04:05 +0530 Subject: [PATCH 2/9] fix: false deadlinks when using remap --- src/node/markdownToVue.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 29fd1971c0f5..2abafcffb021 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -128,6 +128,14 @@ export async function createMarkdownToVueRenderFn( url = url.replace(/[?#].*$/, '').replace(/\.(html|md)$/, '') if (url.endsWith('/')) url += `index` + + Object.entries(siteConfig?.remap || {}).some(([key, val]) => { + if (url.includes(val.slice(0, -3))) { + url = url.replace(val.slice(0, -3), key.slice(0, -3)) + return true + } + }) + const resolved = decodeURIComponent( slash( url.startsWith('/') From 0e8db7ae3350d87e629016c75fcad3f7d67ef73a Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 18:14:00 +0530 Subject: [PATCH 3/9] fix: broken proxy logic --- src/node/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index dad51b77a125..1c1b9e868e8f 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -192,7 +192,7 @@ export async function createVitePressPlugin( server.middlewares.use((req, res, next) => { Object.entries(remap || {}).some(([key, val]) => { - if (req.url?.includes(val)) { + if (req.url?.startsWith(val)) { req.url = req.url?.replace(val, key) return true } From 101098d9695d1855444bd36be6ef495708edac1d Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 20:29:09 +0530 Subject: [PATCH 4/9] chore: cleanup --- src/node/build/build.ts | 2 +- src/node/build/bundle.ts | 2 +- src/node/build/render.ts | 10 +++++----- src/node/markdownToVue.ts | 24 +++++++++--------------- src/node/plugin.ts | 4 ++-- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 919c508ab91c..3cd20896ee89 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -65,7 +65,7 @@ export async function build( const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap)) const pages = ['404.md', ...siteConfig.pages].map( - (page) => siteConfig.remap?.[`/${page}`]?.slice(1) || page + (page) => siteConfig.remap?.[page] || page ) await Promise.all( diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index be9ce1d2a37b..4cdb3ab6bb00 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -37,7 +37,7 @@ export async function bundle( config.pages.forEach((file) => { // page filename conversion // foo/bar.md -> foo_bar.md - const alias = config.remap?.[`/${file}`]?.slice(1) || file + const alias = config.remap?.[file] || file input[slash(alias).replace(/\//g, '_')] = path.resolve(config.srcDir, file) }) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 66afc4817b40..50cbc99c86e6 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -200,14 +200,14 @@ function resolvePageImports( result: RollupOutput, appChunk: OutputChunk ) { - // find the page's js chunk and inject script tags for its imports so that - // they start fetching as early as possible - Object.entries(config.remap || {}).some(([key, val]) => { - if (page === val.slice(1)) { - page = key.slice(1) + Object.entries(config.remap || {}).some(([before, after]) => { + if (page === after) { + page = before return true } }) + // find the page's js chunk and inject script tags for its imports so that + // they start fetching as early as possible const srcPath = normalizePath( fs.realpathSync(path.resolve(config.srcDir, page)) ) diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 2abafcffb021..426b8325d676 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -55,12 +55,8 @@ export async function createMarkdownToVueRenderFn( file: string, publicDir: string ): Promise => { - Object.entries(siteConfig?.remap || {}).some(([key, val]) => { - if (file.endsWith(key)) { - file = file.slice(0, -key.length) + val - return true - } - }) + const alias = siteConfig?.remap?.[file.slice(srcDir.length + 1)] + file = alias ? path.join(srcDir, alias) : file const relativePath = slash(path.relative(srcDir, file)) const dir = path.dirname(file) const cacheKey = JSON.stringify({ src, file }) @@ -128,21 +124,19 @@ export async function createMarkdownToVueRenderFn( url = url.replace(/[?#].*$/, '').replace(/\.(html|md)$/, '') if (url.endsWith('/')) url += `index` - - Object.entries(siteConfig?.remap || {}).some(([key, val]) => { - if (url.includes(val.slice(0, -3))) { - url = url.replace(val.slice(0, -3), key.slice(0, -3)) - return true - } - }) - - const resolved = decodeURIComponent( + let resolved = decodeURIComponent( slash( url.startsWith('/') ? url.slice(1) : path.relative(srcDir, path.resolve(dir, url)) ) ) + Object.entries(siteConfig?.remap || {}).some(([before, after]) => { + if (resolved === after.slice(0, -3)) { + resolved = before.slice(0, -3) + return true + } + }) if ( !pages.includes(resolved) && !fs.existsSync(path.resolve(dir, publicDir, `${resolved}.html`)) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 1c1b9e868e8f..2658b2969475 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -192,8 +192,8 @@ export async function createVitePressPlugin( server.middlewares.use((req, res, next) => { Object.entries(remap || {}).some(([key, val]) => { - if (req.url?.startsWith(val)) { - req.url = req.url?.replace(val, key) + if (req.url?.startsWith('/' + val)) { + req.url = req.url.replace(val, key) return true } }) From d23592b696c8ca897adfa534ec78666ce35da90c Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 21:12:35 +0530 Subject: [PATCH 5/9] chore: rename var --- src/node/plugin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 2658b2969475..9f1e62663490 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -191,9 +191,9 @@ export async function createVitePressPlugin( } server.middlewares.use((req, res, next) => { - Object.entries(remap || {}).some(([key, val]) => { - if (req.url?.startsWith('/' + val)) { - req.url = req.url.replace(val, key) + Object.entries(remap || {}).some(([before, after]) => { + if (req.url?.startsWith('/' + after)) { + req.url = req.url.replace(after, before) return true } }) From 59423c72047f539fb8d8a7fd7b97e785bcd8fa02 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 15 Jan 2023 21:50:22 +0530 Subject: [PATCH 6/9] perf: precompute inverse map to avoid iteration --- src/node/build/build.ts | 2 +- src/node/build/bundle.ts | 2 +- src/node/build/render.ts | 7 +------ src/node/config.ts | 8 ++++++-- src/node/markdownToVue.ts | 10 +++------- src/node/plugin.ts | 12 +++++------- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 3cd20896ee89..6bc698cb28c1 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -65,7 +65,7 @@ export async function build( const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap)) const pages = ['404.md', ...siteConfig.pages].map( - (page) => siteConfig.remap?.[page] || page + (page) => siteConfig.__map[page] || page ) await Promise.all( diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index 4cdb3ab6bb00..f9996abaf0f2 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -37,7 +37,7 @@ export async function bundle( config.pages.forEach((file) => { // page filename conversion // foo/bar.md -> foo_bar.md - const alias = config.remap?.[file] || file + const alias = config.__map[file] || file input[slash(alias).replace(/\//g, '_')] = path.resolve(config.srcDir, file) }) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 50cbc99c86e6..fb6991fd8002 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -200,12 +200,7 @@ function resolvePageImports( result: RollupOutput, appChunk: OutputChunk ) { - Object.entries(config.remap || {}).some(([before, after]) => { - if (page === after) { - page = before - return true - } - }) + page = config.__invMap[page] || page // find the page's js chunk and inject script tags for its imports so that // they start fetching as early as possible const srcPath = normalizePath( diff --git a/src/node/config.ts b/src/node/config.ts index 71f6b661bcc8..b69c2cdb130c 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -170,7 +170,6 @@ export interface SiteConfig | 'transformHead' | 'transformHtml' | 'transformPageData' - | 'remap' > { root: string srcDir: string @@ -182,6 +181,8 @@ export interface SiteConfig cacheDir: string tempDir: string pages: string[] + __map: Record + __invMap: Record } const resolve = (root: string, file: string) => @@ -268,7 +269,10 @@ export async function resolveConfig( transformHead: userConfig.transformHead, transformHtml: userConfig.transformHtml, transformPageData: userConfig.transformPageData, - remap: userConfig.remap + __map: userConfig.remap || {}, + __invMap: Object.fromEntries( + Object.entries(userConfig.remap || {}).map((a) => a.reverse()) + ) } return config diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 426b8325d676..4d4c2946ea3a 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -55,7 +55,7 @@ export async function createMarkdownToVueRenderFn( file: string, publicDir: string ): Promise => { - const alias = siteConfig?.remap?.[file.slice(srcDir.length + 1)] + const alias = siteConfig?.__map[file.slice(srcDir.length + 1)] file = alias ? path.join(srcDir, alias) : file const relativePath = slash(path.relative(srcDir, file)) const dir = path.dirname(file) @@ -131,12 +131,8 @@ export async function createMarkdownToVueRenderFn( : path.relative(srcDir, path.resolve(dir, url)) ) ) - Object.entries(siteConfig?.remap || {}).some(([before, after]) => { - if (resolved === after.slice(0, -3)) { - resolved = before.slice(0, -3) - return true - } - }) + resolved = + siteConfig?.__invMap[resolved + '.md']?.slice(0, -3) || resolved if ( !pages.includes(resolved) && !fs.existsSync(path.resolve(dir, publicDir, `${resolved}.html`)) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 9f1e62663490..7ae65f6e797a 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -64,7 +64,7 @@ export async function createVitePressPlugin( ignoreDeadLinks, lastUpdated, cleanUrls, - remap + __invMap } = siteConfig let markdownToVue: Awaited> @@ -191,12 +191,10 @@ export async function createVitePressPlugin( } server.middlewares.use((req, res, next) => { - Object.entries(remap || {}).some(([before, after]) => { - if (req.url?.startsWith('/' + after)) { - req.url = req.url.replace(after, before) - return true - } - }) + if (req.url) { + const page = req.url.replace(/[?#].*$/, '').slice(1) + req.url = req.url.replace(page, __invMap[page] || page) + } next() }) From 3da88bc33b53fada7ed74281c0e2fff660269bec Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:01:26 +0530 Subject: [PATCH 7/9] feat: support patterns in rewrite --- package.json | 1 + pnpm-lock.yaml | 6 ++++++ src/node/build/build.ts | 2 +- src/node/build/bundle.ts | 2 +- src/node/build/render.ts | 2 +- src/node/config.ts | 42 ++++++++++++++++++++++++++++++++------- src/node/markdownToVue.ts | 4 ++-- src/node/plugin.ts | 4 ++-- 8 files changed, 49 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8540044199db..e9dfb4633bcc 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "nanoid": "3.3.4", "npm-run-all": "^4.1.5", "ora": "5.4.1", + "path-to-regexp": "^6.2.1", "picocolors": "^1.0.0", "pkg-dir": "5.0.0", "playwright-chromium": "1.28.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a68374dcac09..172ad4d9183a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,7 @@ importers: nanoid: 3.3.4 npm-run-all: ^4.1.5 ora: 5.4.1 + path-to-regexp: ^6.2.1 picocolors: ^1.0.0 pkg-dir: 5.0.0 playwright-chromium: 1.28.1 @@ -148,6 +149,7 @@ importers: nanoid: 3.3.4 npm-run-all: 4.1.5 ora: 5.4.1 + path-to-regexp: 6.2.1 picocolors: 1.0.0 pkg-dir: 5.0.0 playwright-chromium: 1.28.1 @@ -3128,6 +3130,10 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-to-regexp/6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true + /path-type/3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 6bc698cb28c1..75694e4d717d 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -65,7 +65,7 @@ export async function build( const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap)) const pages = ['404.md', ...siteConfig.pages].map( - (page) => siteConfig.__map[page] || page + (page) => siteConfig.rewrites.map[page] || page ) await Promise.all( diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index f9996abaf0f2..4de2dc3a1df7 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -37,7 +37,7 @@ export async function bundle( config.pages.forEach((file) => { // page filename conversion // foo/bar.md -> foo_bar.md - const alias = config.__map[file] || file + const alias = config.rewrites.map[file] || file input[slash(alias).replace(/\//g, '_')] = path.resolve(config.srcDir, file) }) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 6b83a167f147..920a31c78e9f 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -201,7 +201,7 @@ function resolvePageImports( result: RollupOutput, appChunk: OutputChunk ) { - page = config.__invMap[page] || page + page = config.rewrites.inv[page] || page // find the page's js chunk and inject script tags for its imports so that // they start fetching as early as possible const srcPath = normalizePath( diff --git a/src/node/config.ts b/src/node/config.ts index ecbfe4c4fc49..7786163a7a5c 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -3,6 +3,7 @@ import _debug from 'debug' import fg from 'fast-glob' import fs from 'fs-extra' import path from 'path' +import { match, compile } from 'path-to-regexp' import c from 'picocolors' import { loadConfigFromFile, @@ -100,8 +101,10 @@ export interface UserConfig /** * @experimental + * + * source -> destination */ - remap?: Record + rewrites?: Record /** * Build end hook: called when SSG finish. @@ -180,8 +183,10 @@ export interface SiteConfig cacheDir: string tempDir: string pages: string[] - __map: Record - __invMap: Record + rewrites: { + map: Record + inv: Record + } } const resolve = (root: string, file: string) => @@ -241,6 +246,21 @@ export async function resolveConfig( }) ).sort() + const rewriteEntries = Object.entries(userConfig.rewrites || {}) + + const rewrites = rewriteEntries.length + ? Object.fromEntries( + pages + .map((src) => { + for (const [from, to] of rewriteEntries) { + const dest = rewrite(src, from, to) + if (dest) return [src, dest] + } + }) + .filter((e) => e != null) as [string, string][] + ) + : {} + const config: SiteConfig = { root, srcDir, @@ -268,10 +288,10 @@ export async function resolveConfig( transformHead: userConfig.transformHead, transformHtml: userConfig.transformHtml, transformPageData: userConfig.transformPageData, - __map: userConfig.remap || {}, - __invMap: Object.fromEntries( - Object.entries(userConfig.remap || {}).map((a) => a.reverse()) - ) + rewrites: { + map: rewrites, + inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse())) + } } return config @@ -406,3 +426,11 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] { return head } + +function rewrite(src: string, from: string, to: string) { + const urlMatch = match(from) + const res = urlMatch(src) + if (!res) return false + const toPath = compile(to) + return toPath(res.params) +} diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 4d4c2946ea3a..6c6f111be940 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -55,7 +55,7 @@ export async function createMarkdownToVueRenderFn( file: string, publicDir: string ): Promise => { - const alias = siteConfig?.__map[file.slice(srcDir.length + 1)] + const alias = siteConfig?.rewrites.map[file.slice(srcDir.length + 1)] file = alias ? path.join(srcDir, alias) : file const relativePath = slash(path.relative(srcDir, file)) const dir = path.dirname(file) @@ -132,7 +132,7 @@ export async function createMarkdownToVueRenderFn( ) ) resolved = - siteConfig?.__invMap[resolved + '.md']?.slice(0, -3) || resolved + siteConfig?.rewrites.inv[resolved + '.md']?.slice(0, -3) || resolved if ( !pages.includes(resolved) && !fs.existsSync(path.resolve(dir, publicDir, `${resolved}.html`)) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 7ae65f6e797a..f0673eaad3cb 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -64,7 +64,7 @@ export async function createVitePressPlugin( ignoreDeadLinks, lastUpdated, cleanUrls, - __invMap + rewrites } = siteConfig let markdownToVue: Awaited> @@ -193,7 +193,7 @@ export async function createVitePressPlugin( server.middlewares.use((req, res, next) => { if (req.url) { const page = req.url.replace(/[?#].*$/, '').slice(1) - req.url = req.url.replace(page, __invMap[page] || page) + req.url = req.url.replace(page, rewrites.inv[page] || page) } next() }) From 2907a883558ac963e7d19ff716e9dacdbdcb6b91 Mon Sep 17 00:00:00 2001 From: Kia Ishii Date: Fri, 27 Jan 2023 18:55:35 +0900 Subject: [PATCH 8/9] add docs --- docs/.vitepress/config.ts | 1 + docs/config/app-configs.md | 32 +++++-- docs/guide/routing.md | 185 +++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 docs/guide/routing.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index e6c9a964b3da..a87403697cb0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -84,6 +84,7 @@ function sidebarGuide() { { text: 'What is VitePress?', link: '/guide/what-is-vitepress' }, { text: 'Getting Started', link: '/guide/getting-started' }, { text: 'Configuration', link: '/guide/configuration' }, + { text: 'Routing', link: '/guide/routing' }, { text: 'Deploying', link: '/guide/deploying' }, { text: 'Internationalization', link: '/guide/i18n' } ] diff --git a/docs/config/app-configs.md b/docs/config/app-configs.md index 2671f6787045..de69006c3b92 100644 --- a/docs/config/app-configs.md +++ b/docs/config/app-configs.md @@ -271,23 +271,37 @@ export default { - Type: `'disabled' | 'without-subfolders' | 'with-subfolders'` - Default: `'disabled'` -Allows removing trailing `.html` from URLs and, optionally, generating clean directory structure. Available modes: +Allows removing trailing `.html` from URLs and, optionally, generating clean directory structure. -| 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` | +```ts +export default { + cleanUrls: 'with-subfolders' +} +``` -::: warning +This option has several modes you can choose. Here is the list of all modes available. -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 (see above table) **without a redirect**. +| 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**. ::: +## rewrites + +- Type: `Record` + +Defines custom directory <-> URL mappings. See [Routing: Customize the Mappings](/guide/routing#customize-the-mappings) for more details. + ```ts export default { - cleanUrls: 'with-subfolders' + rewrites: { + 'source/:page': 'destination/:page' + } } ``` diff --git a/docs/guide/routing.md b/docs/guide/routing.md new file mode 100644 index 000000000000..5772bc1ba79d --- /dev/null +++ b/docs/guide/routing.md @@ -0,0 +1,185 @@ +# Routing + +VitePress is built with file system based routing, which means the directory structure of the source file corresponds to the final URL. You may customize the mapping of the directory structure and URL too. Read through this page to learn everything about the VitePress routing system. + +## Basic Routing + +By default, VitePress assumes your page files are stored in project root. Here you may add markdown files with the name being the URL path. For example, when you have following directory structure: + +``` +. +├─ guide +│ ├─ getting-started.md +│ └─ index.md +├─ index.md +└─ prologue.md +``` + +Then you can access the pages by the below URL. + +``` +index.md -> / +prologue.md -> /prologue.html +guide/index.md -> /guide/ +getting-started.md -> /guide/getting-started.html +``` + +As you can see, the directory structure corresponds to the final URL, as same as hosting plain HTML from a typical web server. + +## Changing the Root Directory + +To change the root directory for your page files, you may pass the directory name to the `vitepress` command. For example, if you want to store your page files under `docs` directory, then you should run `vitepress dev docs` command. + +``` +. +├─ docs +│ ├─ getting-started.md +│ └─ index.md +└─ ... +``` + +``` +vitepress dev docs +``` + +This is going to map the URL as follows. + +``` +docs/index.md -> / +docs/getting-started.md -> /getting-started.html +``` + +You may also customize the root directory in config file via [`srcDir`](/config/app-configs#srcdir) option too. The following setting act same as running `vitepress dev docs` command. + +```ts +export default { + srcDir: './docs' +} +``` + +## Linking Between Pages + +When adding links in pages, omit extension from the path and use either absolute path from the root, or relative path from the page. VitePress will handle the extension according to your configuration setup. + +```md + +[Getting Started](/guide/getting-started) +[Getting Started](../guide/getting-started) + + +[Getting Started](/guide/getting-started.md) +[Getting Started](/guide/getting-started.html) +``` + +Learn more about page links and links to assets, such as link to images, at [Asset Handling](asset-handling). + +## Generate Clean URL + +A "Clean URL" is commonly known as URL without `.html` extension, for example, `example.com/path` instead of `example.com/path.html`. + +By default, VitePress generates the final static page files by adding `.html` extension to each file. If you would like to have clean URL, you may structure your directory by only using `index.html` file. + +``` +. +├─ getting-started +│ └─ index.md +├─ installation +│ └─ index.md +└─ index.md +``` + +However, you may also generate a clean URL by setting up [`cleanUrls`](/config/app-configs#cleanurls-experimental) option. + +```ts +export default { + cleanUrls: 'with-subfolders' +} +``` + +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 a several packages and would like to place documentations along with the source files like this. + +``` +. +├─ packages +│ ├─ pkg-a +│ │ └─ src +│ │ ├─ pkg-a-code.ts +│ │ └─ pkg-a-code.md +│ └─ pkg-b +│ └─ src +│ ├─ pkg-b-code.ts +│ └─ pkg-b-code.md +``` + +And you want the VitePress pages to be generated as follows. + +``` +packages/pkg-a/src/pkg-a-code.md -> /pkg-a/pkg-a-code.md +packages/pkg-b/src/pkg-b-code.md -> /pkg-b/pkg-b-code.md +``` + +You may configure the mapping via [`rewrites`](/config/app-configs#rewrites) option like this. + +```ts +export default { + rewrites: { + 'packages/pkg-a/src/pkg-a-code.md': 'pkg-a/pkg-a-code', + 'packages/pkg-b/src/pkg-b-code.md': 'pkg-b/pkg-b-code' + } +} +``` + +The `rewrites` option can also have dynamic route parameters. In this example, we have fixed path `packages` and `src` which stays the same on all pages, and it might be verbose to have to list all pages in your config as you add pages. You may configure the above mapping as below and get the same result. + +```ts +export default { + rewrites: { + 'packages/:pkg/src/:page': ':pkg/:page' + } +} +``` + +Route parameters are prefixed by `:` (e.g. `:pkg`). The name of the parameter is just a placeholder and can be anything. + +In addition, you may add `*` at the end of the parameter to map all sub directories from there on. + +```ts +export default { + rewrites: { + 'packages/:pkg/src/:page*': ':pkg/:page*' + } +} +``` + +The above will create mapping as below. + +``` +packages/pkg-a/src/pkg-a-code.md -> /pkg-a/pkg-a-code +packages/pkg-b/src/folder/file.md -> /pkg-b/folder/file +``` + +::: warning You need server restart on page addition +At the moment, VitePress doesn't detect page additions to the mapped directory. You need to restart your server when adding or removing files from the directory during the dev mode. Updating the already existing files gets updated as usual. +::: + +### Relative Link Handling in Page + +Note that when enabling rewrites, **relative links in the markdown are resolved relative to the final path**. For example, in order to create relative link from `packages/pkg-a/src/pkg-a-code.md` to `packages/pkg-b/src/pkg-b-code.md`, you should define link as below. + +```md +[Link to PKG B](../pkg-b/pkg-b-code) +``` From b9fe347297c4404f5e6e81ebd7c0c48505f73d49 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:49:13 +0530 Subject: [PATCH 9/9] Apply suggestions from code review --- docs/guide/routing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/routing.md b/docs/guide/routing.md index 5772bc1ba79d..917b4eebc9a9 100644 --- a/docs/guide/routing.md +++ b/docs/guide/routing.md @@ -49,7 +49,7 @@ docs/index.md -> / docs/getting-started.md -> /getting-started.html ``` -You may also customize the root directory in config file via [`srcDir`](/config/app-configs#srcdir) option too. The following setting act same as running `vitepress dev docs` command. +You may also customize the root directory in config file via [`srcDir`](/config/app-configs#srcdir) option too. Running `vitepress dev` with the following setting acts same as running `vitepress dev docs` command. ```ts export default { @@ -110,7 +110,7 @@ Enabling this may require additional configuration on your hosting platform. For ## 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 a several packages and would like to place documentations along with the source files like this. +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. ``` .