You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vitepress/src/markdownToVue.ts

95 lines
2.4 KiB

import path from 'path'
import matter from 'gray-matter'
import LRUCache from 'lru-cache'
import { createMarkdownRenderer, MarkdownOpitons } from './markdown/markdown'
import { deeplyParseHeader } from './utils/parseHeader'
import { PageData } from './config'
const debug = require('debug')('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
interface MarkdownCompileResult {
vueSrc: string
pageData: PageData
}
export function createMarkdownToVueRenderFn(
root: string,
options: MarkdownOpitons = {}
) {
const md = createMarkdownRenderer(options)
return (src: string, file: string, lastUpdated: number) => {
file = path.relative(root, file)
const cached = cache.get(src)
if (cached) {
debug(`[cache hit] ${file}`)
return cached
}
const start = Date.now()
const { content, data: frontmatter } = matter(src)
const { html, data } = md.render(content)
// TODO validate data.links?
// inject page data
const pageData: PageData = {
title: inferTitle(frontmatter, content),
frontmatter,
headers: data.headers,
lastUpdated
}
const additionalBlocks = injectPageData(
data.hoistedTags || [],
pageData
)
// double wrapping since tempalte root node is never hoisted or turned into
// a static node.
const vueSrc =
`<template><div><div class="vitepress-content">${html}</div></div></template>\n` +
additionalBlocks.join('\n')
debug(`[render] ${file} in ${Date.now() - start}ms.`)
const result = { vueSrc, pageData }
cache.set(src, result)
return result
}
}
const scriptRE = /<\/script>/
function injectPageData(
tags: string[],
data: PageData
) {
const code = `\nexport const __pageData = ${JSON.stringify(data)}`
const existingScriptIndex = tags.findIndex((tag) => scriptRE.test(tag))
if (existingScriptIndex > -1) {
tags[existingScriptIndex] = tags[existingScriptIndex].replace(
scriptRE,
code + `</script>`
)
} else {
tags.push(`<script>${code}\nexport default {}</script>`)
}
return tags
}
const inferTitle = (frontmatter: any, content: string) => {
if (frontmatter.home) {
return 'Home'
}
if (frontmatter.title) {
return deeplyParseHeader(frontmatter.title)
}
const match = content.match(/^\s*#+\s+(.*)/m)
if (match) {
return deeplyParseHeader(match[1].trim())
}
return ''
}