前言
最近学习 Vue 框架相关开发知识,并且用 markdown 做学习笔记,但发现笔记中的案例不够直观,想着像 elementplus 官方那样,既能看到渲染结果又能看到代码,这样更有利于笔记的阅读学习。要实现这个方案,就是让 markdown 在浏览器渲染,而不仅仅是在本地用阅读器阅读。
Vue 应用中渲染 markdown 实际并不难,只需要在 Vue 组件中将 markdown 转换为 html 渲染,这个过程可以在 Vue 运行时实现,markdown 也可以是动态可变的内容。然而,我的 Markdown 笔记中有些是 vue 组件案例,要渲染这些案例必须经过构建编译,那么 markdown 只能作为 vue 组件导入,经过构建编译为最终的 Vue 应用。
Markdown 作为 Vue 组件导入渲染,实际上已经有现成的解决方案,例如 vite-plugin-vue-markdown 这个包,它能够将 markdown 转换为 SFC 导入。然而在使用这个依赖包过程中,发现它并不符合我的需求,比如它不能控制转换过程,也没办法渲染 markdown 中的案例代码。为了实现我的需求只能自己开发,最后形成 vite-plugin-vue-mdsfc 插件,它除了能像 vite-plugin-vue-markdown 那样,把 markdown 转换为标准的 SFC,在 Vue 组件中导入渲染以外,还能把 markdown 的围栏代码块,作为 markdown 的子组件渲染。 vite-plugin-vue-mdsfc 插件还有较高的自定义能力,利用钩子函数能自定义转换 SFC 过程,利用 markdown-it 插件能自定义案例渲染过程。
快速上手
根据你的包管理器执行以下命令安装依赖包。
shell
npm i vite-plugin-vue-mdsfc -D
pnpm i vite-plugin-vue-mdsfc -D
yarn add vite-plugin-vue-mdsfc -D
vite.config.js
在配置文件中导入插件并使用,其中需要配置两个 markdown-it 插件。markdownitPluginFenceToVSFC 是 vite-plugin-vue-mdsfc 内置插件,用于将 vue 语言围栏代码块,作为 markdown 的子组件渲染;markdownItPluginFenceToExample 是用户自定义插件,用于在 markdownitPluginFenceToVSFC 插件基础上保留围栏源码并高亮。
javascript
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueMDSFC from 'vite-plugin-vue-mdsfc'
import { markdownitPluginFenceToVSFC } from 'vite-plugin-vue-mdsfc'
/* MarkdownIt插件:围栏代码块处理为案例,渲染围栏代码外还保留源码
若不自定义这个插件,那么 vue 语言的围栏只会渲染,不会保留围栏源码 */
function markdownItPluginFenceToExample(md) {
const $rules = md.renderer.rules
const original = $rules.fence
$rules.fence = function (tokens, idx, options, env, self) {
const token = tokens[idx]
const language = token.info.trim().toLowerCase()
const fenceToSFCLang = ['vue']
if (fenceToSFCLang.includes(language)) {
// 渲染围栏代码
const sfcResult = original(tokens, idx, options, env, self)
// 高亮围栏代码
tokens[idx].info = 'html'
const htmlResult = original(tokens, idx, options, env, self)
// 返回渲染和高亮的结果,
// ExampleContainer 是自定义全局组件,用于显示渲染结果和高亮代码,
// 这里你也可以直接返回包含两个结果的 html
return (
`<ExampleContainer>
<template #render>${sfcResult}</template>
<template #source>${htmlResult}</template>
</ExampleContainer>`
)
}
return original(tokens, idx, options, env, self)
}
}
export default defineConfig({
plugins: [
// 使用插件,并配置 markdown-it 插件来自定义处理围栏案例代码,
// 若不配置插件,默认只会让 markdownitPluginFenceToVSFC 插件渲染 vue 语言的围栏
vueMDSFC({
markdownItPlugins: [
markdownitPluginFenceToVSFC,
markdownItPluginFenceToExample,
],
}),
vue({
include: [/\.vue$/, /\.md$/], // 让 vue 插件也编译 .md 文件
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
App.vue
将 markdown 文件作为组件导入。
xml
<!-- App.vue -->
<script setup>
import ArticleContent from './Button.md'
</script>
<template>
<ArticleContent/>
</template>
<style scoped></style>
这个文档包含一个 vue 语言的围栏案例,vite-plugin-vue-mdsfc 插件将把这个围栏作为 Button.md 的子组件渲染,前提是这个围栏是合法的 SFC 代码。
xml
# 按钮图标
按钮使用图标有两种方式。方式一是为按钮的 `icon` 属性绑定图标对象;方式二是在按钮标签体中,用 `el-icon` 标签显示图标。
您可以在 ElementPlus 的 Icon 组件中找到所需图标。
```vue
<script setup>
import {
Delete,
Edit,
Search,
Share,
Upload,
} from '@element-plus/icons-vue'
</script>
<template>
<div>
<el-button type="primary" :icon="Edit" />
<el-button type="primary" :icon="Share" />
<el-button type="primary" :icon="Delete" />
<el-button type="primary" :icon="Search">Search</el-button>
<el-button type="primary">
Upload
<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</div>
</template>
```
main.js
vite-plugin-vue-mdsfc 插件用 highlight.js 实现围栏代码高亮,因此你还需要安装 highlight.js 依赖包,并且全局导入高亮样式。我的应用还用到 element-plus 组件库,因此我还需要安装它并全局注册,若你的应用不需要可以忽略。
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
/* 导入全局样式 */
import 'element-plus/dist/index.css' // elementplus 的样式
import 'highlight.js/styles/github.css' // 围栏代码高亮样式
/* 创建 VUE 应用 */
const app = createApp(App)
app.use(ElementPlus) // ElementPlus 所有组件注册为全局组件
app.mount('#app') // 挂在 VUE 应用到 DOM 节点
npm run dev 启动开发服务器,将会看到 markdown 文档渲染内容,并且 vue 语言的围栏也会被渲染。

这段 markdown 渲染结果中,案例包含"渲染结果"和"高亮代码"。vite.config.js 中定义的 markdownItPluginFenceToExample 插件,负责组合"渲染结果"和"高亮代码",而案例渲染由内置的 markdownitPluginFenceToVSFC 插件实现,代码高亮由 highlight.js 实现。整个案例结果的展示,由自定义的 ExampleContainer 组件来实现,比如显示隐藏"高亮代码"以及边框等样式。