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
// supported in Node 12+, which is required by Vite.
const allMarkdownFiles = (
await glob(['**.md'], {
await glob(['**/*.md'], {
cwd: srcDir,
ignore: [
'**/node_modules/**',
@ -71,9 +71,12 @@ interface RouteModule {
config: {
paths:
| UserRouteConfig[]
| (() => UserRouteConfig[] | Promise<UserRouteConfig[]>)
| ((
watchedFiles: string[]
) => UserRouteConfig[] | Promise<UserRouteConfig[]>)
}
dependencies: string[]
watch?: string[] | string
}
const routeModuleCache = new Map<string, RouteModule>()
@ -141,20 +144,36 @@ export const dynamicRoutesPlugin = async (
async hotUpdate({ file, modules: existingMods }) {
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 mods = config.dynamicRoutes.fileToModulesMap[file]
const mods = config.dynamicRoutes.fileToModulesMap[normalizedFile]
if (mods) {
// path loader module or deps updated, reset loaded routes
if (!file.endsWith('.md')) {
if (!normalizedFile.endsWith('.md')) {
Object.assign(
config,
await resolvePages(config.srcDir, config.userConfig, config.logger)
)
}
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 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
const loader = mod.config.paths
if (!loader) {
logger.warn(
c.yellow(
@ -233,9 +240,61 @@ export async function resolveDynamicRoutes(
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 paths = await (typeof loader === 'function' ? loader() : loader)
return paths.map((userConfig) => {
let pathsData: UserRouteConfig[]
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(
dynamicRouteRE,
(_, key) => userConfig.params[key]

Loading…
Cancel
Save