markdown-it
markdown-it 是一个流行的 JavaScript Markdown 解析器,它将 Markdown 文本解析成 HTML 标记。下面是 markdown-it 的渲染流程:
markdown-it首先会将输入的 Markdown 文本转换成 Token 流,每个 Token 代表一个 Markdown 元素,如标题、段落、代码块等。- 接着,
markdown-it会将 Token 流传递给插件系统,插件可以对 Token 进行修改、添加或删除。 - 经过插件系统处理后,
markdown-it会将 Token 流转换成 AST(抽象语法树),AST 是一个树形结构,每个节点代表一个 Markdown 元素,如标题、段落、代码块等。 - 最后,
markdown-it会将 AST 转换成 HTML 标记,这个过程被称为渲染。
总的来说,markdown-it 的渲染流程可以归纳为以下几个步骤:输入 Markdown 文本 -> Token 流 -> 插件处理 -> AST -> HTML 标记。
安装 markdown-it
sh
pnpm add @types/markdown-it -D
pnpm add markdown-it将 markdown 内容渲染成 html
ts
import MarkdownIt from 'markdown-it'
const md = MarkdownIt({
// 允许在文件中写 HTML 标签
html: true,
// 自动将 url 转换为链接
linkify: true,
})
const html = md.render('# Markdown')插件使用
ts
export function snippetPlugin(md: MarkdownIt, srcDir: string) {
}markdown-it-container
https://github.com/markdown-it/markdown-it-container#readme
javascript
import MarkdownIt from 'markdown-it'
import MarkdownItContainer from 'markdown-it-container'
md.use(MarkdownItContainer, 'demo', {
marker: ':',
validate(params) {
return params.trim().match(/^demo(.*)$/)
},
render(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo(.*)$/)
if (tokens[idx].nesting === 1) {
const description = m && m.length > 1 ? m[1] : ''
const content
= tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : ''
return `<demo-block>
${description ? `<div>${md.render(description)}</div>` : ''}
<!--element-demo: ${content}:element-demo-->
`
}
return '</demo-block>'
},
})将
markdown
<demo-block>
<div>
<p>Progress 组件设置<code>percentage</code>属性即可,表示进度条对应的百分比,<strong>必填</strong>,必须在 0-100。通过 <code>format</code> 属性来指定进度条文字内容。</p>
</div>
<!--element-demo:
<el-progress :percentage="50">
</el-progress>
<el-progress :percentage="100" :format="format"></el-progress>
<el-progress :percentage="100" status="success"></el-progress>
<el-progress :percentage="100" status="warning"></el-progress>
<el-progress :percentage="50" status="exception"></el-progress>
<script>
export default {
methods: {
format(percentage) {
return percentage === 100 ? '满' : `${percentage}%`
},
},
}
</script>
:element-demo-->
<pre><code class="language-html"><el-progress :percentage="50"></el-progress>
<el-progress :percentage="100" :format="format"></el-progress>
<el-progress :percentage="100" status="success"></el-progress>
<el-progress :percentage="100" status="warning"></el-progress>
<el-progress :percentage="50" status="exception"></el-progress>
<script>
export default {
methods: {
format(percentage) {
return percentage === 100 ? '满' : `${percentage}%`
},
},
}
</script>
</code></pre>
</demo-block>markdown-it-chain
vue
<script lang="js">
import * as Vue from 'vue'
export default {
name: 'ComponentDoc',
components: {
ElementDemo0: (function () {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
function render(_ctx, _cache) {
const _component_el_progress = _resolveComponent('el-progress')
return (_openBlock(), _createBlock('div', null, [
_createVNode(_component_el_progress, { percentage: 50 }),
_createVNode(_component_el_progress, {
percentage: 100,
format: _ctx.format
}, null, 8 /* PROPS */, ['format']),
_createVNode(_component_el_progress, {
percentage: 100,
status: 'success'
}),
_createVNode(_component_el_progress, {
percentage: 100,
status: 'warning'
}),
_createVNode(_component_el_progress, {
percentage: 50,
status: 'exception'
})
]))
}
const democomponentExport = {
methods: {
format(percentage) {
return percentage === 100 ? '满' : `${percentage}%`
},
},
}
return {
render,
...democomponentExport
}
})(),
}
}
</script>
<template>
<section class="content element-doc">
<demo-block>
<div>
<p>
Progress
组件设置<code>percentage</code>属性即可,表示进度条对应的百分比,<strong>必填</strong>,必须在
0-100。通过 <code>format</code> 属性来指定进度条文字内容。
</p>
</div>
<template #source>
<ElementDemo0 />
</template>
<pre><code class="language-html"><el-progress :percentage="50"></el-progress>
<el-progress :percentage="100" :format="format"></el-progress>
<el-progress :percentage="100" status="success"></el-progress>
<el-progress :percentage="100" status="warning"></el-progress>
<el-progress :percentage="50" status="exception"></el-progress>
<script>
export default {
methods: {
format(percentage) {
return percentage === 100 ? '满' : `${percentage}%`
},
},
}
</script>
</code></pre>
</demo-block>
</section>
</template>代码块渲染
ts
import { MarkdownRenderer } from 'vitepress'
import { FenceDemoTag } from './constants'
import { genDemoByCode } from './utils'
export function fencePlugin(md: MarkdownRenderer) {
const defaultRender = md.renderer.rules.fence
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx]
if (token.info.trim() !== FenceDemoTag)
return defaultRender!(tokens, idx, options, env, self)
const content = token.content
const path = env.path
// 自定义渲染
const demoScripts = genDemoByCode(md, env, path, content)
return demoScripts
}
}代码高亮
安装 prismjs
sh
pnpm add prismjs
pnpm add -D @types/prismjs将代码片段转换成 HTML
ts
import prism from 'prismjs'
// 要高亮的代码片段字符串
const code = `var data = 1;`
// Returns a highlighted HTML string
const html = prism.highlight(code, Prism.languages.javascript, 'javascript')Prism 会自动加载默认的 markup, css, clike 和 javascript. 可以使用 loadLanguages() 加载自己所需要的高亮语言
ts
import prism from 'prismjs'
import loadLanguages from 'prismjs/components/index'
// required to make embedded highlighting work...
loadLanguages(['markup', 'css', 'javascript'])
// The code snippet you want to highlight, as a string
const code = `var data = 1;`
// Returns a highlighted HTML string
const html = prism.highlight(code, Prism.languages.javascript, 'javascript')ts
import prism from 'prismjs'
import loadLanguages from 'prismjs/components/index'
// required to make embedded highlighting work...
loadLanguages(['markup', 'css', 'javascript'])
function getLangCodeFromExtension(extension: string) {
const extensionMap: Record<string, string> = {
vue: 'markup',
html: 'markup',
md: 'markdown',
rb: 'ruby',
ts: 'typescript',
py: 'python',
sh: 'bash',
yml: 'yaml',
styl: 'stylus',
kt: 'kotlin',
rs: 'rust',
}
return extensionMap[extension] || extension
}
function wrap(str: string, lang: string) {
return `<pre class="language-${lang}"><code>${str}</code></pre>`
}
export default (str: string, lang: string) => {
lang = getLangCodeFromExtension(lang)
if (!prism.languages[lang]) {
try {
loadLanguages([lang])
}
catch (error) {
throw new Error(
`Syntax highlight for language "${lang}" is not supported.`,
)
}
}
if (prism.languages[lang]) {
const code = prism.highlight(str, prism.languages[lang], lang)
return wrap(code, lang)
}
return wrap(str, 'text')
}添加语法高亮
https://github.com/vuejs/vitepress/issues/1533
ts
import { BUNDLED_LANGUAGES } from 'shiki'
// Include `cs` as alias for csharp
BUNDLED_LANGUAGES.find(lang => lang.id === 'csharp').aliases.push('cs')
// Include `fs` as alias for fsharp
BUNDLED_LANGUAGES.find(lang => lang.id === 'fsharp').aliases.push('fs')自定义渲染
包含 token 的渲染规则。可以进行更新和扩展。
ts
export function mdDemoPlugin(md: MarkdownRenderer) {
const addRenderRule = (type: string) => {
const defaultRender = md.renderer.rules[type]
md.renderer.rules[type] = (tokens, idx, options, env, self) => {
const token = tokens[idx]
console.log('token: ', token)
const { content } = token
if (!content.startsWith(`<demo `))
return defaultRender!(tokens, idx, options, env, self)
return ''
}
}
addRenderRule('html_block')
addRenderRule('html_inline')
}