Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vite/plugins/llms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function llms(): Promise<PluginOption[]> {
})

llmsTxtContent.push(
`- [${title}](${basePath}${path})${description ? `: ${description}` : ''}`,
`- [${title}](${basePath}${path}.md)${description ? `: ${description}` : ''}`,
)
})

Expand Down
38 changes: 37 additions & 1 deletion src/vite/prerender.tsx
Original file line number Diff line number Diff line change
@@ -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 }

Expand All @@ -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) {
Expand Down Expand Up @@ -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 = `${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.`)
Expand Down
32 changes: 32 additions & 0 deletions src/vite/utils/mdxToMarkdown.ts
Original file line number Diff line number Diff line change
@@ -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<string> => {
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)
}