diff --git a/src/vite/plugins/llms.ts b/src/vite/plugins/llms.ts index 2541ca36..a633445b 100644 --- a/src/vite/plugins/llms.ts +++ b/src/vite/plugins/llms.ts @@ -77,7 +77,7 @@ export async function llms(): Promise { }) llmsTxtContent.push( - `- [${title}](${basePath}${path})${description ? `: ${description}` : ''}`, + `- [${title}](${basePath}${path}.md)${description ? `: ${description}` : ''}`, ) }) diff --git a/src/vite/prerender.tsx b/src/vite/prerender.tsx index 650e5899..1b1a76d6 100644 --- a/src/vite/prerender.tsx +++ b/src/vite/prerender.tsx @@ -1,10 +1,11 @@ -import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, relative, resolve } from 'node:path' import pc from 'picocolors' import type { Logger } from 'vite' import { toMarkup } from './utils/html.js' import { resolveOutDir } from './utils/resolveOutDir.js' import { resolveVocsConfig } from './utils/resolveVocsConfig.js' +import { convertMdxToMarkdown } from './utils/mdxToMarkdown.js' type PrerenderParameters = { logger?: Logger; outDir?: string } @@ -20,6 +21,7 @@ export async function prerender({ logger, outDir }: PrerenderParameters) { // Get routes to prerender. const routes = getRoutes(resolve(rootDir, 'pages')) + const pagesDir = resolve(rootDir, 'pages') // Prerender each route. for (const route of routes) { @@ -50,6 +52,40 @@ export async function prerender({ logger, outDir }: PrerenderParameters) { const fileName = path.split('/').pop()! logger?.info(`${pc.dim(relative(rootDir, path).replace(fileName, ''))}${pc.cyan(fileName)}`) + + // Generate .md file for MDX pages + const routeWithoutLeadingSlash = route.replace(/^\//, '') + const possibleSources = [ + resolve(pagesDir, `${routeWithoutLeadingSlash}.mdx`), + resolve(pagesDir, `${routeWithoutLeadingSlash}.md`), + resolve(pagesDir, `${routeWithoutLeadingSlash}/index.mdx`), + resolve(pagesDir, `${routeWithoutLeadingSlash}/index.md`), + ] + + let sourceFile: string | null = null + for (const src of possibleSources) { + if (existsSync(src)) { + sourceFile = src + break + } + } + + if (sourceFile) { + try { + const markdown = await convertMdxToMarkdown(sourceFile) + const mdFilePath = `${isIndex ? `${route.replace(/\/$/, '')}` : route}.md`.replace(/^\//, '') + const mdPath = resolve(outDir_resolved, mdFilePath) + const mdPathDir = dirname(mdPath) + + if (!isDir(mdPathDir)) mkdirSync(mdPathDir, { recursive: true }) + writeFileSync(mdPath, markdown) + + const mdFileName = mdPath.split('/').pop()! + logger?.info(`${pc.dim(relative(rootDir, mdPath).replace(mdFileName, ''))}${pc.cyan(mdFileName)}`) + } catch (error) { + logger?.warn(`Failed to convert ${route} to markdown: ${error}`) + } + } } logger?.info(`\n${pc.green('✓')} ${routes.length} pages prerendered.`) diff --git a/src/vite/utils/mdxToMarkdown.ts b/src/vite/utils/mdxToMarkdown.ts new file mode 100644 index 00000000..9767f6c1 --- /dev/null +++ b/src/vite/utils/mdxToMarkdown.ts @@ -0,0 +1,32 @@ +import { readFileSync } from 'node:fs' +import { unified } from 'unified' +import remarkParse from 'remark-parse' +import remarkMdx from 'remark-mdx' +import remarkFrontmatter from 'remark-frontmatter' +import remarkGfm from 'remark-gfm' +import remarkDirective from 'remark-directive' +import remarkStringify from 'remark-stringify' + +export const convertMdxToMarkdown = async (filePath: string): Promise => { + const content = readFileSync(filePath, 'utf-8') + + // Process MDX: parse, then stringify back to markdown + // This preserves JSX components, directives, and frontmatter + const result = await unified() + .use(remarkParse) // Parse markdown + .use(remarkMdx) // Parse MDX (JSX components) + .use(remarkFrontmatter, ['yaml', 'toml']) // Parse frontmatter + .use(remarkGfm) // Parse GFM (tables, strikethrough, etc.) + .use(remarkDirective) // Parse directives (::) + .use(remarkStringify, { + // Stringify back to markdown + bullet: '-', + fence: '`', + fences: true, + incrementListMarker: true, + }) + .process(content) + + return String(result) +} +