mirror of https://github.com/vuejs/vitepress
feat: dynamic routes plugin overhaul (#4525)
BREAKING CHANGES: Internals are modified a bit to better support vite 6 and handle HMR more correctly. For most users this won't need any change on their side.pull/4600/head
parent
d1f2afdf0f
commit
a62ea6a832
@ -1,8 +1,14 @@
|
|||||||
export default {
|
import { defineRoutes } from 'vitepress'
|
||||||
async paths() {
|
import paths from './paths'
|
||||||
return [
|
|
||||||
{ params: { id: 'foo' }, content: `# Foo` },
|
export default defineRoutes({
|
||||||
{ params: { id: 'bar' }, content: `# Bar` }
|
async paths(watchedFiles: string[]) {
|
||||||
]
|
// console.log('watchedFiles', watchedFiles)
|
||||||
|
return paths
|
||||||
|
},
|
||||||
|
watch: ['**/data-loading/**/*.json'],
|
||||||
|
async transformPageData(pageData) {
|
||||||
|
// console.log('transformPageData', pageData.filePath)
|
||||||
|
pageData.title += ' - transformed'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export default [
|
||||||
|
{ params: { id: 'foo' }, content: `# Foo` },
|
||||||
|
{ params: { id: 'bar' }, content: `# Bar` }
|
||||||
|
]
|
@ -0,0 +1,72 @@
|
|||||||
|
import { ModuleGraph } from 'node/utils/moduleGraph'
|
||||||
|
|
||||||
|
describe('node/utils/moduleGraph', () => {
|
||||||
|
let graph: ModuleGraph
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
graph = new ModuleGraph()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly delete a module and its dependents', () => {
|
||||||
|
graph.add('A', ['B', 'C'])
|
||||||
|
graph.add('B', ['D'])
|
||||||
|
graph.add('C', [])
|
||||||
|
graph.add('D', [])
|
||||||
|
|
||||||
|
expect(graph.delete('D')).toEqual(new Set(['D', 'B', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle shared dependencies correctly', () => {
|
||||||
|
graph.add('A', ['B', 'C'])
|
||||||
|
graph.add('B', ['D'])
|
||||||
|
graph.add('C', ['D']) // Shared dependency
|
||||||
|
graph.add('D', [])
|
||||||
|
|
||||||
|
expect(graph.delete('D')).toEqual(new Set(['A', 'B', 'C', 'D']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('merges dependencies correctly', () => {
|
||||||
|
// Add module A with dependency B
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
// Merge new dependency C into module A (B should remain)
|
||||||
|
graph.add('A', ['C'])
|
||||||
|
|
||||||
|
// Deleting B should remove A as well, since A depends on B.
|
||||||
|
expect(graph.delete('B')).toEqual(new Set(['B', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles cycles gracefully', () => {
|
||||||
|
// Create a cycle: A -> B, B -> C, C -> A.
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
graph.add('B', ['C'])
|
||||||
|
graph.add('C', ['A'])
|
||||||
|
|
||||||
|
// Deleting any module in the cycle should delete all modules in the cycle.
|
||||||
|
expect(graph.delete('A')).toEqual(new Set(['A', 'B', 'C']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cleans up dependencies when deletion', () => {
|
||||||
|
// Setup A -> B relationship.
|
||||||
|
graph.add('A', ['B'])
|
||||||
|
graph.add('B', [])
|
||||||
|
|
||||||
|
// Deleting B should remove both B and A from the graph.
|
||||||
|
expect(graph.delete('B')).toEqual(new Set(['B', 'A']))
|
||||||
|
|
||||||
|
// After deletion, add modules again.
|
||||||
|
graph.add('C', [])
|
||||||
|
graph.add('A', ['C']) // Now A depends only on C.
|
||||||
|
|
||||||
|
expect(graph.delete('C')).toEqual(new Set(['C', 'A']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles independent modules', () => {
|
||||||
|
// Modules with no dependencies.
|
||||||
|
graph.add('X', [])
|
||||||
|
graph.add('Y', [])
|
||||||
|
|
||||||
|
// Deletion of one should only remove that module.
|
||||||
|
expect(graph.delete('X')).toEqual(new Set(['X']))
|
||||||
|
expect(graph.delete('Y')).toEqual(new Set(['Y']))
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,115 @@
|
|||||||
|
export class ModuleGraph {
|
||||||
|
// Each module is tracked with its dependencies and dependents.
|
||||||
|
private nodes: Map<
|
||||||
|
string,
|
||||||
|
{ dependencies: Set<string>; dependents: Set<string> }
|
||||||
|
> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or updates a module by merging the provided dependencies
|
||||||
|
* with any existing ones.
|
||||||
|
*
|
||||||
|
* For every new dependency, the module is added to that dependency's
|
||||||
|
* 'dependents' set.
|
||||||
|
*
|
||||||
|
* @param module - The module to add or update.
|
||||||
|
* @param dependencies - Array of module names that the module depends on.
|
||||||
|
*/
|
||||||
|
add(module: string, dependencies: string[]): void {
|
||||||
|
// Ensure the module exists in the graph.
|
||||||
|
if (!this.nodes.has(module)) {
|
||||||
|
this.nodes.set(module, {
|
||||||
|
dependencies: new Set(),
|
||||||
|
dependents: new Set()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const moduleNode = this.nodes.get(module)!
|
||||||
|
|
||||||
|
// Merge the new dependencies with any that already exist.
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
if (!moduleNode.dependencies.has(dep) && dep !== module) {
|
||||||
|
moduleNode.dependencies.add(dep)
|
||||||
|
// Ensure the dependency exists in the graph.
|
||||||
|
if (!this.nodes.has(dep)) {
|
||||||
|
this.nodes.set(dep, {
|
||||||
|
dependencies: new Set(),
|
||||||
|
dependents: new Set()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Add the module as a dependent of the dependency.
|
||||||
|
this.nodes.get(dep)!.dependents.add(module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a module and all modules that (transitively) depend on it.
|
||||||
|
*
|
||||||
|
* This method performs a depth-first search from the target module,
|
||||||
|
* collects all affected modules, and then removes them from the graph,
|
||||||
|
* cleaning up their references from other nodes.
|
||||||
|
*
|
||||||
|
* @param module - The module to delete.
|
||||||
|
* @returns A Set containing the deleted module and all modules that depend on it.
|
||||||
|
*/
|
||||||
|
delete(module: string): Set<string> {
|
||||||
|
const deleted = new Set<string>()
|
||||||
|
const stack: string[] = [module]
|
||||||
|
|
||||||
|
// Traverse the reverse dependency graph (using dependents).
|
||||||
|
while (stack.length) {
|
||||||
|
const current = stack.pop()!
|
||||||
|
if (!deleted.has(current)) {
|
||||||
|
deleted.add(current)
|
||||||
|
const node = this.nodes.get(current)
|
||||||
|
if (node) {
|
||||||
|
for (const dependent of node.dependents) {
|
||||||
|
stack.push(dependent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deleted nodes from the graph.
|
||||||
|
// For each deleted node, also remove it from its dependencies' dependents.
|
||||||
|
for (const mod of deleted) {
|
||||||
|
const node = this.nodes.get(mod)
|
||||||
|
if (node) {
|
||||||
|
for (const dep of node.dependencies) {
|
||||||
|
const depNode = this.nodes.get(dep)
|
||||||
|
if (depNode) {
|
||||||
|
depNode.dependents.delete(mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.nodes.delete(mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all modules from the graph.
|
||||||
|
*/
|
||||||
|
clear(): void {
|
||||||
|
this.nodes.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a deep clone of the ModuleGraph instance.
|
||||||
|
* This is useful for preserving the state of the graph
|
||||||
|
* before making modifications.
|
||||||
|
*
|
||||||
|
* @returns A new ModuleGraph instance with the same state as the original.
|
||||||
|
*/
|
||||||
|
clone(): ModuleGraph {
|
||||||
|
const clone = new ModuleGraph()
|
||||||
|
for (const [module, { dependencies, dependents }] of this.nodes) {
|
||||||
|
clone.nodes.set(module, {
|
||||||
|
dependencies: new Set(dependencies),
|
||||||
|
dependents: new Set(dependents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue