fix dep tracking in dynamic routes plugin, support providing custom watch patterns similar to static data plugin

pull/4525/head
Divyansh Singh 7 months ago
parent 7356890cd8
commit 7e83f0a6eb

@ -26,7 +26,7 @@ export async function resolvePages(
// JavaScript built-in sort() is mandated to be stable as of ES2019 and // JavaScript built-in sort() is mandated to be stable as of ES2019 and
// supported in Node 12+, which is required by Vite. // supported in Node 12+, which is required by Vite.
const allMarkdownFiles = ( const allMarkdownFiles = (
await glob(['**.md'], { await glob(['**/*.md'], {
cwd: srcDir, cwd: srcDir,
ignore: [ ignore: [
'**/node_modules/**', '**/node_modules/**',
@ -71,9 +71,12 @@ interface RouteModule {
config: { config: {
paths: paths:
| UserRouteConfig[] | UserRouteConfig[]
| (() => UserRouteConfig[] | Promise<UserRouteConfig[]>) | ((
watchedFiles: string[]
) => UserRouteConfig[] | Promise<UserRouteConfig[]>)
} }
dependencies: string[] dependencies: string[]
watch?: string[] | string
} }
const routeModuleCache = new Map<string, RouteModule>() const routeModuleCache = new Map<string, RouteModule>()
@ -141,20 +144,36 @@ export const dynamicRoutesPlugin = async (
async hotUpdate({ file, modules: existingMods }) { async hotUpdate({ file, modules: existingMods }) {
if (this.environment.name !== 'client') return if (this.environment.name !== 'client') return
routeModuleCache.delete(file) const normalizedFile = normalizePath(file)
// Invalidate any cached route modules whose key or dependencies include the changed file.
for (const [cacheKey, mod] of routeModuleCache.entries()) {
const normalizedCacheKey = normalizePath(cacheKey)
if (
normalizedCacheKey === normalizedFile ||
mod.dependencies.some(
(dep) => normalizePath(path.resolve(dep)) === normalizedFile
)
) {
routeModuleCache.delete(cacheKey)
}
}
const modules: EnvironmentModuleNode[] = [] const modules: EnvironmentModuleNode[] = []
const mods = config.dynamicRoutes.fileToModulesMap[file] const mods = config.dynamicRoutes.fileToModulesMap[normalizedFile]
if (mods) { if (mods) {
// path loader module or deps updated, reset loaded routes // path loader module or deps updated, reset loaded routes
if (!file.endsWith('.md')) { if (!normalizedFile.endsWith('.md')) {
Object.assign( Object.assign(
config, config,
await resolvePages(config.srcDir, config.userConfig, config.logger) await resolvePages(config.srcDir, config.userConfig, config.logger)
) )
} }
for (const id of mods) { for (const id of mods) {
modules.push(this.environment.moduleGraph.getModuleById(id)!) const mod = this.environment.moduleGraph.getModuleById(id)
if (mod) {
modules.push(mod)
}
} }
} }
@ -210,19 +229,7 @@ export async function resolveDynamicRoutes(
} }
} }
// this array represents the virtual modules affected by this route const loader = mod.config.paths
const matchedModuleIds = (routeFileToModulesMap[
normalizePath(path.resolve(srcDir, route))
] = new Set())
// each dependency (including the loader module itself) also point to the
// same array
for (const dep of mod.dependencies) {
// deps are resolved relative to cwd
routeFileToModulesMap[normalizePath(path.resolve(dep))] = matchedModuleIds
}
const loader = mod!.config.paths
if (!loader) { if (!loader) {
logger.warn( logger.warn(
c.yellow( c.yellow(
@ -233,9 +240,61 @@ export async function resolveDynamicRoutes(
continue continue
} }
// Create or retrieve the set of virtual module IDs affected by this route.
const routeKey = normalizePath(path.resolve(srcDir, route))
const matchedModuleIds =
routeFileToModulesMap[routeKey] || new Set<string>()
routeFileToModulesMap[routeKey] = matchedModuleIds
// Track loader dependencies (merging sets if shared)
for (const dep of mod.dependencies) {
const depPath = normalizePath(path.resolve(dep))
if (!routeFileToModulesMap[depPath]) {
routeFileToModulesMap[depPath] = matchedModuleIds
} else {
for (const id of matchedModuleIds) {
routeFileToModulesMap[depPath].add(id)
}
}
}
// Process custom watch files if provided.
let watch: string[] | undefined
if (mod.watch) {
watch = typeof mod.watch === 'string' ? [mod.watch] : mod.watch
watch = watch.map((p) =>
p.startsWith('.')
? normalizePath(path.resolve(path.dirname(pathsFile), p))
: normalizePath(p)
)
for (const watchFile of watch) {
if (!routeFileToModulesMap[watchFile]) {
routeFileToModulesMap[watchFile] = matchedModuleIds
} else {
for (const id of matchedModuleIds) {
routeFileToModulesMap[watchFile].add(id)
}
}
}
}
const resolveRoute = async (): Promise<ResolvedRouteConfig[]> => { const resolveRoute = async (): Promise<ResolvedRouteConfig[]> => {
const paths = await (typeof loader === 'function' ? loader() : loader) let pathsData: UserRouteConfig[]
return paths.map((userConfig) => { if (typeof loader === 'function') {
let watchedFiles: string[] = []
if (watch) {
watchedFiles = (
await glob(watch, {
ignore: ['**/node_modules/**', '**/dist/**'],
expandDirectories: false
})
).sort()
}
pathsData = await loader(watchedFiles)
} else {
pathsData = loader
}
return pathsData.map((userConfig) => {
const resolvedPath = route.replace( const resolvedPath = route.replace(
dynamicRouteRE, dynamicRouteRE,
(_, key) => userConfig.params[key] (_, key) => userConfig.params[key]

Loading…
Cancel
Save