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 插件。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>

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

参考资料

相关推荐
Autumn_ing2 小时前
Design.md生成UI怎么验收?举例运营后台的颜色、状态和数据核对
前端·ui·ai·设计系统·paico·ai生成ui·design.md
远离UE43 小时前
UE5 Substrate Thin Film 源码拆析
开发语言·javascript·ue5
keyipatience3 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
朱涛的自习室3 小时前
我给 Gemini 4 和 DeepSeek 搞了套「末位淘汰」,AI 干活会更猛吗?(Day 3)
android·前端·后端
java1234_小锋6 小时前
【技术专题】HTML5 网页技术 - HTML5简介 & HTML5 文档结构
前端·html·html5
鶴哥只手遮天6 小时前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 1 章 项目全景:monorepo 架构与工程化体系
前端·vue.js·开源
流星。。方6 小时前
小团队官网SEO落地:前端语义化与静态生成的工程实践
前端·状态模式
用户530429765796 小时前
盘点危化品搬运诊断的6类工具与自处理边界判断
前端
curtain6 小时前
从古法编程到 Agent:我入行这两年,AI 改变了什么
前端·openai
zyfts7 小时前
10分钟自制Codex钉钉通知Skill:AI干完活自动钉钉提醒你
前端