diff --git a/src/node/build/build.ts b/src/node/build/build.ts index a279fff7..eb1c0a64 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -9,6 +9,7 @@ import { bundle, okMark, failMark } from './bundle' import { createRequire } from 'module' import { pathToFileURL } from 'url' import pkgDir from 'pkg-dir' +import { resolveRoutes } from '../plugins/dynamicRoutesPlugin' export async function build( root?: string, @@ -31,9 +32,16 @@ export async function build( } try { + const [dynamicRoutes] = await resolveRoutes(siteConfig.dynamicRoutes) + const allPages = [ + ...siteConfig.pages, + ...dynamicRoutes.map((r) => r.path) + ] + const { clientResult, serverResult, pageToHashMap } = await bundle( siteConfig, - buildOptions + buildOptions, + allPages ) const entryPath = path.join(siteConfig.tempDir, 'app.js') @@ -64,23 +72,21 @@ export async function build( // as JS object literal. const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap)) - const pages = ['404.md', ...siteConfig.pages].map( - (page) => siteConfig.rewrites.map[page] || page - ) - await Promise.all( - pages.map((page) => - renderPage( - render, - siteConfig, - page, - clientResult, - appChunk, - cssChunk, - pageToHashMap, - hashMapString + ['404.md', ...allPages] + .map((page) => siteConfig.rewrites.map[page] || page) + .map((page) => + renderPage( + render, + siteConfig, + page, + clientResult, + appChunk, + cssChunk, + pageToHashMap, + hashMapString + ) ) - ) ) } catch (e) { spinner.stopAndPersist({ diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index 4de2dc3a..b3f43f22 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -20,7 +20,8 @@ export const failMark = '\x1b[31m✖\x1b[0m' // bundles the VitePress app for both client AND server. export async function bundle( config: SiteConfig, - options: BuildOptions + options: BuildOptions, + allPages: string[] ): Promise<{ clientResult: RollupOutput serverResult: RollupOutput @@ -34,7 +35,7 @@ export async function bundle( // the loading is done via filename conversion rules so that the // metadata doesn't need to be included in the main chunk. const input: Record = {} - config.pages.forEach((file) => { + allPages.forEach((file) => { // page filename conversion // foo/bar.md -> foo_bar.md const alias = config.rewrites.map[file] || file diff --git a/src/node/build/render.ts b/src/node/build/render.ts index c8dd9d89..07d78bff 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -197,9 +197,13 @@ function resolvePageImports( page = config.rewrites.inv[page] || page // find the page's js chunk and inject script tags for its imports so that // they start fetching as early as possible - const srcPath = normalizePath( - fs.realpathSync(path.resolve(config.srcDir, page)) - ) + let srcPath = page + try { + srcPath = normalizePath(fs.realpathSync(path.resolve(config.srcDir, page))) + } catch (e) { + // if the page is a virtual page generated by a dynamic route this would + // fail, which is expected + } const pageChunk = result.output.find( (chunk) => chunk.type === 'chunk' && chunk.facadeModuleId === srcPath ) as OutputChunk diff --git a/src/node/plugins/dynamicRoutesPlugin.ts b/src/node/plugins/dynamicRoutesPlugin.ts index ff71fc7c..0fd81890 100644 --- a/src/node/plugins/dynamicRoutesPlugin.ts +++ b/src/node/plugins/dynamicRoutesPlugin.ts @@ -9,7 +9,7 @@ import c from 'picocolors' import path from 'path' import type { SiteConfig } from '../config' -export const dynamicRouteRE = /\[(\.\.\.)?\w+\]/ +export const dynamicRouteRE = /\[(\w+?)\]/g interface UserRouteConfig { params: Record @@ -26,7 +26,7 @@ interface RouteModule { dependencies: string[] } -type ResolvedRouteConfig = UserRouteConfig & { +export type ResolvedRouteConfig = UserRouteConfig & { /** * the raw route, e.g. foo/[bar].md */ @@ -81,6 +81,17 @@ export const dynamicRoutesPlugin = async ( }) }, + resolveId(id) { + if (!id.endsWith('.md')) return + const normalizedId = id.startsWith(config.root) + ? normalizePath(path.relative(config.root, id)) + : id.replace(/^\//, '') + const matched = resolvedRoutes.find((r) => r.path === normalizedId) + if (matched) { + return normalizedId + } + }, + load(id) { const matched = resolvedRoutes.find((r) => r.path === id) if (matched) { @@ -121,7 +132,7 @@ export const dynamicRoutesPlugin = async ( } } -async function resolveRoutes(routes: string[]) { +export async function resolveRoutes(routes: string[]) { const pendingResolveRoutes: Promise[] = [] const routeFileToModulesMap: Record = {} @@ -165,9 +176,10 @@ async function resolveRoutes(routes: string[]) { const paths = await (typeof loader === 'function' ? loader() : loader) return paths.map((userConfig) => { return { - path: - '/' + - route.replace(/\[(\w+)\]/g, (_, key) => userConfig.params[key]), + path: route.replace( + dynamicRouteRE, + (_, key) => userConfig.params[key] + ), route, ...userConfig }