Skip to content

Commit bccce98

Browse files
committed
fix: update route configs on file add / delete
1 parent 34b149b commit bccce98

File tree

4 files changed

+122
-99
lines changed

4 files changed

+122
-99
lines changed

src/node/config.ts

+40-45
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import _debug from 'debug'
33
import fg from 'fast-glob'
44
import fs from 'fs-extra'
55
import path from 'path'
6-
import { compile, match } from 'path-to-regexp'
76
import c from 'picocolors'
87
import {
98
createLogger,
@@ -17,9 +16,10 @@ import { DEFAULT_THEME_PATH } from './alias'
1716
import type { MarkdownOptions } from './markdown/markdown'
1817
import {
1918
dynamicRouteRE,
20-
resolveRoutes,
19+
resolveDynamicRoutes,
2120
type ResolvedRouteConfig
2221
} from './plugins/dynamicRoutesPlugin'
22+
import { resolveRewrites } from './plugins/rewritesPlugin'
2323
import {
2424
APPEARANCE_KEY,
2525
type Awaitable,
@@ -183,12 +183,16 @@ export interface SiteConfig<ThemeConfig = any>
183183
cacheDir: string
184184
tempDir: string
185185
pages: string[]
186-
dynamicRoutes: readonly [ResolvedRouteConfig[], Record<string, string[]>]
186+
dynamicRoutes: {
187+
routes: ResolvedRouteConfig[]
188+
fileToModulesMap: Record<string, string[]>
189+
}
187190
rewrites: {
188191
map: Record<string, string | undefined>
189192
inv: Record<string, string | undefined>
190193
}
191194
logger: Logger
195+
userConfig: UserConfig
192196
}
193197

194198
const resolve = (root: string, file: string) =>
@@ -242,39 +246,10 @@ export async function resolveConfig(
242246
? userThemeDir
243247
: DEFAULT_THEME_PATH
244248

245-
// Important: fast-glob doesn't guarantee order of the returned files.
246-
// We must sort the pages so the input list to rollup is stable across
247-
// builds - otherwise different input order could result in different exports
248-
// order in shared chunks which in turns invalidates the hash of every chunk!
249-
// JavaScript built-in sort() is mandated to be stable as of ES2019 and
250-
// supported in Node 12+, which is required by Vite.
251-
const allMarkdownFiles = (
252-
await fg(['**.md'], {
253-
cwd: srcDir,
254-
ignore: ['**/node_modules', ...(userConfig.srcExclude || [])]
255-
})
256-
).sort()
257-
258-
const pages = allMarkdownFiles.filter((p) => !dynamicRouteRE.test(p))
259-
const dynamicRouteFiles = allMarkdownFiles.filter((p) =>
260-
dynamicRouteRE.test(p)
249+
const { pages, dynamicRoutes, rewrites } = await resolvePages(
250+
srcDir,
251+
userConfig
261252
)
262-
const dynamicRoutes = await resolveRoutes(dynamicRouteFiles)
263-
pages.push(...dynamicRoutes[0].map((r) => r.path))
264-
265-
const rewriteEntries = Object.entries(userConfig.rewrites || {})
266-
const rewrites = rewriteEntries.length
267-
? Object.fromEntries(
268-
pages
269-
.map((src) => {
270-
for (const [from, to] of rewriteEntries) {
271-
const dest = rewrite(src, from, to)
272-
if (dest) return [src, dest]
273-
}
274-
})
275-
.filter((e) => e != null) as [string, string][]
276-
)
277-
: {}
278253

279254
const config: SiteConfig = {
280255
root,
@@ -305,10 +280,8 @@ export async function resolveConfig(
305280
transformHead: userConfig.transformHead,
306281
transformHtml: userConfig.transformHtml,
307282
transformPageData: userConfig.transformPageData,
308-
rewrites: {
309-
map: rewrites,
310-
inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse()))
311-
}
283+
rewrites,
284+
userConfig
312285
}
313286

314287
return config
@@ -444,10 +417,32 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] {
444417
return head
445418
}
446419

447-
function rewrite(src: string, from: string, to: string) {
448-
const urlMatch = match(from)
449-
const res = urlMatch(src)
450-
if (!res) return false
451-
const toPath = compile(to)
452-
return toPath(res.params)
420+
export async function resolvePages(srcDir: string, userConfig: UserConfig) {
421+
// Important: fast-glob doesn't guarantee order of the returned files.
422+
// We must sort the pages so the input list to rollup is stable across
423+
// builds - otherwise different input order could result in different exports
424+
// order in shared chunks which in turns invalidates the hash of every chunk!
425+
// JavaScript built-in sort() is mandated to be stable as of ES2019 and
426+
// supported in Node 12+, which is required by Vite.
427+
const allMarkdownFiles = (
428+
await fg(['**.md'], {
429+
cwd: srcDir,
430+
ignore: ['**/node_modules', ...(userConfig.srcExclude || [])]
431+
})
432+
).sort()
433+
434+
const pages = allMarkdownFiles.filter((p) => !dynamicRouteRE.test(p))
435+
const dynamicRouteFiles = allMarkdownFiles.filter((p) =>
436+
dynamicRouteRE.test(p)
437+
)
438+
const dynamicRoutes = await resolveDynamicRoutes(dynamicRouteFiles)
439+
pages.push(...dynamicRoutes.routes.map((r) => r.path))
440+
441+
const rewrites = resolveRewrites(pages, userConfig.rewrites)
442+
443+
return {
444+
pages,
445+
dynamicRoutes,
446+
rewrites
447+
}
453448
}

src/node/plugin.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ import {
1515
resolveAliases,
1616
SITE_DATA_REQUEST_PATH
1717
} from './alias'
18-
import type { SiteConfig } from './config'
18+
import { resolvePages, type SiteConfig } from './config'
1919
import { clearCache, createMarkdownToVueRenderFn } from './markdownToVue'
2020
import type { PageDataPayload } from './shared'
2121
import { staticDataPlugin } from './plugins/staticDataPlugin'
2222
import { webFontsPlugin } from './plugins/webFontsPlugin'
2323
import { dynamicRoutesPlugin } from './plugins/dynamicRoutesPlugin'
24+
import { rewritesPlugin } from './plugins/rewritesPlugin'
2425

2526
declare module 'vite' {
2627
interface UserConfig {
@@ -70,8 +71,7 @@ export async function createVitePressPlugin(
7071
pages,
7172
ignoreDeadLinks,
7273
lastUpdated,
73-
cleanUrls,
74-
rewrites
74+
cleanUrls
7575
} = siteConfig
7676

7777
let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>>
@@ -198,15 +198,16 @@ export async function createVitePressPlugin(
198198
configDeps.forEach((file) => server.watcher.add(file))
199199
}
200200

201-
server.middlewares.use((req, res, next) => {
202-
if (req.url) {
203-
const page = req.url.replace(/[?#].*$/, '').slice(site.base.length)
204-
if (rewrites.inv[page]) {
205-
req.url = req.url.replace(page, rewrites.inv[page]!)
206-
}
201+
// update pages, dynamicRoutes and rewrites on md file add / deletion
202+
const onFileAddDelete = async (file: string) => {
203+
if (file.endsWith('.md')) {
204+
Object.assign(
205+
siteConfig,
206+
await resolvePages(siteConfig.srcDir, siteConfig.userConfig)
207+
)
207208
}
208-
next()
209-
})
209+
}
210+
server.watcher.on('add', onFileAddDelete).on('unlink', onFileAddDelete)
210211

211212
// serve our index.html after vite history fallback
212213
return () => {
@@ -347,6 +348,7 @@ export async function createVitePressPlugin(
347348

348349
return [
349350
vitePressPlugin,
351+
rewritesPlugin(siteConfig),
350352
vuePlugin,
351353
webFontsPlugin(siteConfig.useWebFonts),
352354
...(userViteConfig?.plugins || []),

src/node/plugins/dynamicRoutesPlugin.ts

+15-43
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import fs from 'fs-extra'
88
import c from 'picocolors'
99
import path from 'path'
10-
import type { SiteConfig } from '../config'
10+
import { resolvePages, type SiteConfig } from '../config'
1111

1212
export const dynamicRouteRE = /\[(\w+?)\]/g
1313

@@ -41,63 +41,33 @@ export const dynamicRoutesPlugin = async (
4141
config: SiteConfig
4242
): Promise<Plugin> => {
4343
let server: ViteDevServer
44-
let routes = config.dynamicRoutes[0].map(r => r.route)
45-
let [resolvedRoutes, routeFileToModulesMap] = config.dynamicRoutes
46-
47-
// TODO: make this more efficient by only reloading the invalidated route
48-
// TODO: invlidate modules for paths that are no longer present
49-
async function invlidateRoutes() {
50-
;[resolvedRoutes, routeFileToModulesMap] = await resolveRoutes(routes)
51-
}
5244

5345
return {
5446
name: 'vitepress:dynamic-routes',
5547

5648
configureServer(_server) {
5749
server = _server
58-
59-
const onFileAddDelete = (
60-
file: string,
61-
updateRoutes: (route: string) => void
62-
) => {
63-
if (dynamicRouteRE.test(file) && /\.(md|paths\.[jt]s)$/.test(file)) {
64-
if (file.endsWith('.md')) {
65-
updateRoutes(normalizePath(path.relative(config.root, file)))
66-
}
67-
invlidateRoutes().then(() => {
68-
server.ws.send({ type: 'full-reload' })
69-
})
70-
}
71-
}
72-
73-
server.watcher
74-
.on('add', (file) => {
75-
onFileAddDelete(file, (route) => routes.push(route))
76-
})
77-
.on('unlink', (file) => {
78-
onFileAddDelete(file, (route) => {
79-
routes = routes.filter((r) => r !== route)
80-
})
81-
})
8250
},
8351

8452
resolveId(id) {
8553
if (!id.endsWith('.md')) return
8654
const normalizedId = id.startsWith(config.root)
8755
? normalizePath(path.relative(config.root, id))
8856
: id.replace(/^\//, '')
89-
const matched = resolvedRoutes.find((r) => r.path === normalizedId)
57+
const matched = config.dynamicRoutes.routes.find(
58+
(r) => r.path === normalizedId
59+
)
9060
if (matched) {
9161
return normalizedId
9262
}
9363
},
9464

9565
load(id) {
96-
const matched = resolvedRoutes.find((r) => r.path === id)
66+
const matched = config.dynamicRoutes.routes.find((r) => r.path === id)
9767
if (matched) {
9868
const { route, params, content } = matched
9969
const routeFile = normalizePath(path.resolve(config.root, route))
100-
routeFileToModulesMap[routeFile].push(id)
70+
config.dynamicRoutes.fileToModulesMap[routeFile].push(id)
10171

10272
let baseContent = fs.readFileSync(routeFile, 'utf-8')
10373

@@ -118,11 +88,11 @@ export const dynamicRoutesPlugin = async (
11888
},
11989

12090
async handleHotUpdate(ctx) {
121-
const mods = routeFileToModulesMap[ctx.file]
91+
const mods = config.dynamicRoutes.fileToModulesMap[ctx.file]
12292
if (mods) {
12393
// path loader module updated, reset loaded routes
12494
if (/\.paths\.[jt]s$/.test(ctx.file)) {
125-
await invlidateRoutes()
95+
await resolvePages(config.srcDir, config.userConfig)
12696
}
12797
for (const id of mods) {
12898
ctx.modules.push(server.moduleGraph.getModuleById(id)!)
@@ -132,7 +102,9 @@ export const dynamicRoutesPlugin = async (
132102
}
133103
}
134104

135-
export async function resolveRoutes(routes: string[]) {
105+
export async function resolveDynamicRoutes(
106+
routes: string[]
107+
): Promise<SiteConfig['dynamicRoutes']> {
136108
const pendingResolveRoutes: Promise<ResolvedRouteConfig[]>[] = []
137109
const routeFileToModulesMap: Record<string, string[]> = {}
138110

@@ -189,8 +161,8 @@ export async function resolveRoutes(routes: string[]) {
189161
}
190162
}
191163

192-
return [
193-
(await Promise.all(pendingResolveRoutes)).flat(),
194-
routeFileToModulesMap
195-
] as const
164+
return {
165+
routes: (await Promise.all(pendingResolveRoutes)).flat(),
166+
fileToModulesMap: routeFileToModulesMap
167+
}
196168
}

src/node/plugins/rewritesPlugin.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Plugin } from 'vite'
2+
import { compile, match } from 'path-to-regexp'
3+
import type { SiteConfig, UserConfig } from '../config'
4+
5+
export function resolveRewrites(
6+
pages: string[],
7+
userRewrites: UserConfig['rewrites']
8+
) {
9+
const rewriteEntries = Object.entries(userRewrites || {})
10+
const rewrites = rewriteEntries.length
11+
? Object.fromEntries(
12+
pages
13+
.map((src) => {
14+
for (const [from, to] of rewriteEntries) {
15+
const dest = rewrite(src, from, to)
16+
if (dest) return [src, dest]
17+
}
18+
})
19+
.filter((e) => e != null) as [string, string][]
20+
)
21+
: {}
22+
return {
23+
map: rewrites,
24+
inv: Object.fromEntries(Object.entries(rewrites).map((a) => a.reverse()))
25+
}
26+
}
27+
28+
function rewrite(src: string, from: string, to: string) {
29+
const urlMatch = match(from)
30+
const res = urlMatch(src)
31+
if (!res) return false
32+
const toPath = compile(to)
33+
return toPath(res.params)
34+
}
35+
36+
export const rewritesPlugin = (config: SiteConfig): Plugin => {
37+
return {
38+
name: 'vitepress:rewrites',
39+
configureServer(server) {
40+
// dev rewrite
41+
server.middlewares.use((req, _res, next) => {
42+
if (req.url) {
43+
const page = req.url
44+
.replace(/[?#].*$/, '')
45+
.slice(config.site.base.length)
46+
if (config.rewrites.inv[page]) {
47+
req.url = req.url.replace(page, config.rewrites.inv[page]!)
48+
}
49+
}
50+
next()
51+
})
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)