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

参考资料

相关推荐
2501_9400417415 小时前
纯前端实战:5个高复杂度业务与交互场景
前端
renke336415 小时前
写给前端的 CANN-torchtitan-npu:昇腾PyTorch Titan适配到底是啥?
前端·人工智能·pytorch·cann
lihaozecq15 小时前
Agent 开发的 skills 机制设计 - 渐进式披露
前端·agent·ai编程
安妮的小熊呢15 小时前
CRMEB标准版v6.0: 商城DIY装修新升级,PS级自由设计!
运维·javascript·平面·信息可视化·小程序·开源软件
安生生申15 小时前
uni-app 连接 JDY-31 蓝牙串口模块实践
c语言·前端·javascript·stm32·单片机·嵌入式硬件·uni-app
Restart-AHTCM15 小时前
LangChain学习之模型 I/O 与输出解析器 (Output Parsers)(3/8)
前端·学习·langchain
Liu.77415 小时前
Vue3结合Element Plus封装点击查看大图的自定义指令
javascript·vue.js·elementui
lqj_本人15 小时前
鸿蒙PC:electron-markdownify 从普通 Electron 迁移到 OpenHarmony Electron HAP 的完整实践
前端·javascript·electron
代码搬运媛1 天前
Jest 测试框架详解与实现指南
前端
counterxing1 天前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程