mirror of https://github.com/vuejs/vitepress
parent
ddb9020545
commit
9c4c2117a6
@ -0,0 +1,110 @@
|
|||||||
|
import { loadConfigFromFile, type Plugin } from 'vite'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import c from 'picocolors'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export const dynamicRouteRE = /\[(\.\.\.)?\w+\]/
|
||||||
|
|
||||||
|
interface UserRouteConfig {
|
||||||
|
params: Record<string, string>
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RouteModule {
|
||||||
|
path: string
|
||||||
|
config: {
|
||||||
|
paths:
|
||||||
|
| UserRouteConfig[]
|
||||||
|
| (() => UserRouteConfig[] | Promise<UserRouteConfig[]>)
|
||||||
|
}
|
||||||
|
dependencies: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvedRouteConfig = UserRouteConfig & {
|
||||||
|
/**
|
||||||
|
* the raw route, e.g. foo/[bar].md
|
||||||
|
*/
|
||||||
|
route: string
|
||||||
|
/**
|
||||||
|
* the actual path with params resolved, e.g. foo/1.md
|
||||||
|
*/
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamicRoutesPlugin = async (
|
||||||
|
routes: string[]
|
||||||
|
): Promise<Plugin> => {
|
||||||
|
const pendingResolveRoutes: Promise<ResolvedRouteConfig[]>[] = []
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
// locate corresponding route paths file
|
||||||
|
const jsPathsFile = route.replace(/\.md$/, '.paths.js')
|
||||||
|
let pathsFile = jsPathsFile
|
||||||
|
if (!fs.existsSync(jsPathsFile)) {
|
||||||
|
pathsFile = route.replace(/\.md$/, '.paths.ts')
|
||||||
|
if (!fs.existsSync(pathsFile)) {
|
||||||
|
console.warn(
|
||||||
|
c.yellow(
|
||||||
|
`missing paths file for dynamic route ${route}: ` +
|
||||||
|
`a corresponding ${jsPathsFile} or ${pathsFile} is needed.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the paths loader module
|
||||||
|
let mod: RouteModule
|
||||||
|
try {
|
||||||
|
mod = (await loadConfigFromFile(
|
||||||
|
{} as any,
|
||||||
|
path.resolve(pathsFile)
|
||||||
|
)) as RouteModule
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`invalid paths file export in ${pathsFile}.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod) {
|
||||||
|
const resolveRoute = async (): Promise<ResolvedRouteConfig[]> => {
|
||||||
|
const loader = mod.config.paths
|
||||||
|
const paths = await (typeof loader === 'function' ? loader() : loader)
|
||||||
|
return paths.map((userConfig) => {
|
||||||
|
return {
|
||||||
|
route,
|
||||||
|
path:
|
||||||
|
'/' +
|
||||||
|
route.replace(/\[(\w+)\]/g, (_, key) => userConfig.params[key]),
|
||||||
|
...userConfig
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pendingResolveRoutes.push(resolveRoute())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedRoutes = (await Promise.all(pendingResolveRoutes)).flat()
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vitepress:dynamic-routes',
|
||||||
|
load(id) {
|
||||||
|
const matched = resolvedRoutes.find((r) => r.path === id)
|
||||||
|
if (matched) {
|
||||||
|
const { route, params, content } = matched
|
||||||
|
let baseContent = fs.readFileSync(route, 'utf-8')
|
||||||
|
|
||||||
|
// inject raw content at build time
|
||||||
|
if (content) {
|
||||||
|
baseContent = baseContent.replace(/<!--\s*@content\s*-->/, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// params are injected with special markers and extracted as part of
|
||||||
|
// __pageData in ../markdownTovue.ts
|
||||||
|
return `__VP_PARAMS_START${JSON.stringify(
|
||||||
|
params
|
||||||
|
)}__VP_PARAMS_END__${baseContent}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO HMR
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue