mirror of https://github.com/vuejs/vitepress
parent
96c30a6d83
commit
9cbfa9529d
@ -1,35 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import { createMarkdownRenderer, MarkdownOpitons } from './markdown'
|
|
||||||
import LRUCache from 'lru-cache'
|
|
||||||
|
|
||||||
const matter = require('gray-matter')
|
|
||||||
const debug = require('debug')('vitepress:md')
|
|
||||||
const cache = new LRUCache<string, string>({ max: 1024 })
|
|
||||||
|
|
||||||
export function createMarkdownToVueRenderFn(
|
|
||||||
root: string,
|
|
||||||
options: MarkdownOpitons = {}
|
|
||||||
) {
|
|
||||||
const md = createMarkdownRenderer(options)
|
|
||||||
|
|
||||||
return (src: string, file: string) => {
|
|
||||||
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 } = matter(src)
|
|
||||||
const { html } = md.render(content)
|
|
||||||
|
|
||||||
// TODO validate links?
|
|
||||||
|
|
||||||
const vueSrc =
|
|
||||||
`<template>${html}</template>` + (data.hoistedTags || []).join('\n')
|
|
||||||
debug(`[render] ${file} in ${Date.now() - start}ms.`, data)
|
|
||||||
cache.set(src, vueSrc)
|
|
||||||
return vueSrc
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,32 @@
|
|||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import { MarkdownParsedData } from '../markdown'
|
||||||
|
import { deeplyParseHeader } from '../../utils/parseHeader'
|
||||||
|
import { slugify } from './slugify'
|
||||||
|
|
||||||
|
export interface Header {
|
||||||
|
level: number
|
||||||
|
title: string
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractHeaderPlugin = (
|
||||||
|
md: MarkdownIt & { __data: MarkdownParsedData },
|
||||||
|
include = ['h2', 'h3']
|
||||||
|
) => {
|
||||||
|
md.renderer.rules.heading_open = (tokens, i, options, env, self) => {
|
||||||
|
const token = tokens[i]
|
||||||
|
if (include.includes(token.tag)) {
|
||||||
|
const title = tokens[i + 1].content
|
||||||
|
const idAttr = token.attrs!.find(([name]) => name === 'id')
|
||||||
|
const slug = idAttr && idAttr[1]
|
||||||
|
const data = md.__data
|
||||||
|
const headers = data.headers || (data.headers = [])
|
||||||
|
headers.push({
|
||||||
|
level: parseInt(token.tag.slice(1), 10),
|
||||||
|
title: deeplyParseHeader(title),
|
||||||
|
slug: slug || slugify(title)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return self.renderToken(tokens, i, options)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import matter from 'gray-matter'
|
||||||
|
import LRUCache from 'lru-cache'
|
||||||
|
import { createMarkdownRenderer, MarkdownOpitons } from './markdown/markdown'
|
||||||
|
import { Header } from './markdown/plugins/header'
|
||||||
|
import { deeplyParseHeader } from './utils/parseHeader'
|
||||||
|
|
||||||
|
const debug = require('debug')('vitepress:md')
|
||||||
|
const cache = new LRUCache<string, string>({ max: 1024 })
|
||||||
|
|
||||||
|
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 additionalBlocks = injectPageData(
|
||||||
|
data.hoistedTags || [],
|
||||||
|
content,
|
||||||
|
frontmatter,
|
||||||
|
data.headers || [],
|
||||||
|
lastUpdated
|
||||||
|
)
|
||||||
|
|
||||||
|
const vueSrc =
|
||||||
|
`<template><div class="vitepress-content">${html}</div></template>\n` +
|
||||||
|
additionalBlocks.join('\n')
|
||||||
|
debug(`[render] ${file} in ${Date.now() - start}ms.`)
|
||||||
|
cache.set(src, vueSrc)
|
||||||
|
return vueSrc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptRE = /<\/script>/
|
||||||
|
function injectPageData(
|
||||||
|
tags: string[],
|
||||||
|
content: string,
|
||||||
|
frontmatter: object,
|
||||||
|
headers: Header[],
|
||||||
|
lastUpdated: number
|
||||||
|
) {
|
||||||
|
const code = `\nexport const __pageData = ${JSON.stringify({
|
||||||
|
title: inferTitle(frontmatter, content),
|
||||||
|
frontmatter,
|
||||||
|
headers,
|
||||||
|
lastUpdated
|
||||||
|
})}`
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue