Markdown 作为 Vue 组件导入

前言

最近学习 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 插件。markdownitPluginFenceToVSFCvite-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>

Button.md

这个文档包含一个 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 组件来实现,比如显示隐藏"高亮代码"以及边框等样式。

参考资料

相关推荐
mCell3 小时前
AI 时代 SVG 画图的潜力
前端·agent·svg
我命由我123458 小时前
CesiumJS 笔记 - 获取容器中心点、Cartesian3 clone 方法、修改 Cartesian3 对象的高度
前端·javascript·css·前端框架·html·html5·js
Hopebearer_9 小时前
页面突然只剩 DOM?一次静态资源版本错配排查
前端·部署
抱抱宝9 小时前
Agent-study项目教程(03):手写 Mini-ReAct Agent(不依赖框架)
javascript·人工智能·gpt·react.js·prompt·agent
东风破_10 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范
用户9385156350710 小时前
Next.js 笔记系统(二):Redis 数据服务与侧边栏组件拆分实战
javascript·全栈
java1234_小锋11 小时前
Vue3专题 - 组件基础
vue.js·vite
圣殿骑士-Khtangc11 小时前
Go字符串高效拼接性能对比与底层原理分析
服务器·前端·golang
BigTopOne12 小时前
【ijkplayer】 硬解码流程
前端
kyriewen12 小时前
DeepSeek Harness开源第一天我就上手了——和Claude Code的差距比想象中大
前端·ai编程·deepseek