fix: module graph causing unnecessary route regeneration on every update

fix: preserve externally added dynamic routes and pages

chore: bump deps, lock vitest as its latest beta is broken
pull/4901/head
Divyansh Singh 4 weeks ago
parent 318c14fa7c
commit fc267ae6b7

@ -15,7 +15,7 @@
"open-cli": "^8.0.0", "open-cli": "^8.0.0",
"postcss-rtlcss": "^5.7.1", "postcss-rtlcss": "^5.7.1",
"vitepress": "workspace:*", "vitepress": "workspace:*",
"vitepress-plugin-group-icons": "^1.6.1", "vitepress-plugin-group-icons": "^1.6.2",
"vitepress-plugin-llms": "^1.7.1" "vitepress-plugin-llms": "^1.7.2"
} }
} }

@ -97,25 +97,25 @@
"dependencies": { "dependencies": {
"@docsearch/css": "^4.0.0-beta.5", "@docsearch/css": "^4.0.0-beta.5",
"@docsearch/js": "^4.0.0-beta.5", "@docsearch/js": "^4.0.0-beta.5",
"@iconify-json/simple-icons": "^1.2.44", "@iconify-json/simple-icons": "^1.2.46",
"@shikijs/core": "^3.8.1", "@shikijs/core": "^3.9.2",
"@shikijs/transformers": "^3.8.1", "@shikijs/transformers": "^3.9.2",
"@shikijs/types": "^3.8.1", "@shikijs/types": "^3.9.2",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.1",
"@vue/devtools-api": "^7.7.7", "@vue/devtools-api": "^8.0.0",
"@vue/shared": "^3.5.18", "@vue/shared": "^3.5.18",
"@vueuse/core": "^13.5.0", "@vueuse/core": "^13.6.0",
"@vueuse/integrations": "^13.5.0", "@vueuse/integrations": "^13.6.0",
"focus-trap": "^7.6.5", "focus-trap": "^7.6.5",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"minisearch": "^7.1.2", "minisearch": "^7.1.2",
"shiki": "^3.8.1", "shiki": "^3.9.2",
"vite": "^7.0.6", "vite": "^7.1.1",
"vue": "^3.5.18" "vue": "^3.5.18"
}, },
"devDependencies": { "devDependencies": {
"@clack/prompts": "^1.0.0-alpha.1", "@clack/prompts": "^1.0.0-alpha.1",
"@iconify/utils": "^2.3.0", "@iconify/utils": "^3.0.0",
"@mdit-vue/plugin-component": "^2.1.4", "@mdit-vue/plugin-component": "^2.1.4",
"@mdit-vue/plugin-frontmatter": "^2.1.4", "@mdit-vue/plugin-frontmatter": "^2.1.4",
"@mdit-vue/plugin-headers": "^2.1.4", "@mdit-vue/plugin-headers": "^2.1.4",
@ -139,8 +139,8 @@
"@types/markdown-it-container": "^2.0.10", "@types/markdown-it-container": "^2.0.10",
"@types/markdown-it-emoji": "^3.0.1", "@types/markdown-it-emoji": "^3.0.1",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "^24.1.0", "@types/node": "^24.2.1",
"@types/picomatch": "^4.0.1", "@types/picomatch": "^4.0.2",
"@types/postcss-prefix-selector": "^1.16.3", "@types/postcss-prefix-selector": "^1.16.3",
"@types/prompts": "^2.4.9", "@types/prompts": "^2.4.9",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@ -149,10 +149,10 @@
"debug": "^4.4.1", "debug": "^4.4.1",
"esbuild": "^0.25.8", "esbuild": "^0.25.8",
"execa": "^9.6.0", "execa": "^9.6.0",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.1",
"get-port": "^7.1.0", "get-port": "^7.1.0",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"lint-staged": "^16.1.2", "lint-staged": "^16.1.5",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"lru-cache": "^11.1.0", "lru-cache": "^11.1.0",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
@ -165,13 +165,13 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
"ora": "^8.2.0", "ora": "^8.2.0",
"oxc-minify": "^0.78.0", "oxc-minify": "^0.81.0",
"p-map": "^7.0.3", "p-map": "^7.0.3",
"package-directory": "^8.1.0", "package-directory": "^8.1.0",
"path-to-regexp": "^6.3.0", "path-to-regexp": "^6.3.0",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"picomatch": "^4.0.3", "picomatch": "^4.0.3",
"playwright-chromium": "^1.54.1", "playwright-chromium": "^1.54.2",
"polka": "^1.0.0-next.28", "polka": "^1.0.0-next.28",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-prefix-selector": "^2.1.1", "postcss-prefix-selector": "^2.1.1",
@ -179,22 +179,22 @@
"prompts": "^2.4.2", "prompts": "^2.4.2",
"punycode": "^2.3.1", "punycode": "^2.3.1",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"rollup": "^4.45.1", "rollup": "^4.46.2",
"rollup-plugin-dts": "6.1.1", "rollup-plugin-dts": "6.1.1",
"rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-esbuild": "^6.2.1",
"semver": "^7.7.2", "semver": "^7.7.2",
"simple-git-hooks": "^2.13.0", "simple-git-hooks": "^2.13.1",
"sirv": "^3.0.1", "sirv": "^3.0.1",
"sitemap": "^8.0.0", "sitemap": "^8.0.0",
"tinyglobby": "^0.2.14", "tinyglobby": "^0.2.14",
"typescript": "^5.8.3", "typescript": "^5.9.2",
"vitest": "^4.0.0-beta.4", "vitest": "4.0.0-beta.4",
"vue-tsc": "^3.0.4", "vue-tsc": "^3.0.5",
"wait-on": "^8.0.4" "wait-on": "^8.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"markdown-it-mathjax3": "^4", "markdown-it-mathjax3": "^4",
"oxc-minify": "^0.78.0", "oxc-minify": "^0.81.0",
"postcss": "^8" "postcss": "^8"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
@ -208,5 +208,5 @@
"optional": true "optional": true
} }
}, },
"packageManager": "pnpm@10.13.1" "packageManager": "pnpm@10.14.0"
} }

File diff suppressed because it is too large Load Diff

@ -168,7 +168,7 @@ export async function resolveConfig(
global.VITEPRESS_CONFIG = config global.VITEPRESS_CONFIG = config
// resolve pages after setting global, so that path loaders can access it // resolve pages after setting global, so that path loaders can access it
Object.assign(config, await resolvePages(srcDir, userConfig, logger, true)) await resolvePages(config, true)
return config as SiteConfig return config as SiteConfig
} }

@ -274,16 +274,7 @@ export async function createVitePressPlugin(
} }
// update pages, dynamicRoutes and rewrites on md file creation / deletion // update pages, dynamicRoutes and rewrites on md file creation / deletion
if (file.endsWith('.md')) { if (file.endsWith('.md')) await resolvePages(siteConfig)
Object.assign(
siteConfig,
await resolvePages(
siteConfig.srcDir,
siteConfig.userConfig,
siteConfig.logger
)
)
}
if (!added && importerMap[file]) { if (!added && importerMap[file]) {
delete importerMap[file] delete importerMap[file]

@ -61,6 +61,7 @@ const pathLoaderRE = /\.paths\.m?[jt]s$/
const routeModuleCache = new Map<string, ResolvedRouteModule>() const routeModuleCache = new Map<string, ResolvedRouteModule>()
let moduleGraph = new ModuleGraph() let moduleGraph = new ModuleGraph()
let discoveredPages = new Set<string>()
/** /**
* Helper for defining routes with type inference * Helper for defining routes with type inference
@ -69,20 +70,21 @@ export function defineRoutes(loader: RouteModule): RouteModule {
return loader return loader
} }
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
export async function resolvePages( export async function resolvePages(
srcDir: string, siteConfig: Optional<SiteConfig, 'pages' | 'dynamicRoutes' | 'rewrites'>,
userConfig: UserConfig,
logger: Logger,
rebuildCache = false rebuildCache = false
): Promise<Pick<SiteConfig, 'pages' | 'dynamicRoutes' | 'rewrites'>> { ): Promise<void> {
if (rebuildCache) { if (rebuildCache) {
moduleGraph = new ModuleGraph() moduleGraph = new ModuleGraph()
routeModuleCache.clear() routeModuleCache.clear()
discoveredPages.clear()
} }
const allMarkdownFiles = await glob(['**/*.md'], { const allMarkdownFiles = await glob(['**/*.md'], {
cwd: srcDir, cwd: siteConfig.srcDir,
ignore: userConfig.srcExclude ignore: siteConfig.userConfig.srcExclude
}) })
const pages: string[] = [] const pages: string[] = []
@ -94,22 +96,33 @@ export async function resolvePages(
}) })
const dynamicRoutes = await resolveDynamicRoutes( const dynamicRoutes = await resolveDynamicRoutes(
srcDir, siteConfig.srcDir,
dynamicRouteFiles, dynamicRouteFiles,
logger siteConfig.logger
) )
pages.push(...dynamicRoutes.map((r) => r.path)) pages.push(...dynamicRoutes.map((r) => r.path))
const rewrites = resolveRewrites(pages, userConfig.rewrites) const externalDynamicRoutes =
siteConfig.dynamicRoutes?.filter((r) => !discoveredPages.has(r.path)) || []
const externalPages =
siteConfig.pages?.filter((p) => !discoveredPages.has(p)) || []
return { const finalDynamicRoutes = [...dynamicRoutes, ...externalDynamicRoutes].sort(
pages, (a, b) => a.path.localeCompare(b.path)
dynamicRoutes, )
const finalPages = [...pages, ...externalPages].sort()
const rewrites = resolveRewrites(pages, siteConfig.userConfig.rewrites)
Object.assign(siteConfig, {
pages: finalPages,
dynamicRoutes: finalDynamicRoutes,
rewrites, rewrites,
// @ts-expect-error internal flag to reload resolution cache in ../markdownToVue.ts // @ts-expect-error internal flag to reload resolution cache in ../markdownToVue.ts
__dirty: true __dirty: true
} } satisfies Partial<SiteConfig>)
discoveredPages = new Set(pages)
} }
export const dynamicRoutesPlugin = async ( export const dynamicRoutesPlugin = async (
@ -193,10 +206,7 @@ export const dynamicRoutesPlugin = async (
pathLoaderRE.test(normalizedFile) pathLoaderRE.test(normalizedFile)
) { ) {
// path loader module or deps updated, reset loaded routes // path loader module or deps updated, reset loaded routes
Object.assign( await resolvePages(config)
config,
await resolvePages(config.srcDir, config.userConfig, config.logger)
)
} }
return modules.length ? [...existingMods, ...modules] : undefined return modules.length ? [...existingMods, ...modules] : undefined

@ -54,6 +54,8 @@ export class ModuleGraph {
*/ */
delete(module: string): Set<string> { delete(module: string): Set<string> {
const deleted = new Set<string>() const deleted = new Set<string>()
if (!this.nodes.has(module)) return deleted
const stack: string[] = [module] const stack: string[] = [module]
// Traverse the reverse dependency graph (using dependents). // Traverse the reverse dependency graph (using dependents).

Loading…
Cancel
Save