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

参考资料

相关推荐
RFCEO7 分钟前
前端编程 课程十三、:CSS核心基础1:CSS选择器
前端·css·css基础选择器详细教程·css类选择器使用方法·css类选择器命名规范·css后代选择器·精准选中嵌套元素
烬头882131 分钟前
React Native鸿蒙跨平台采用了函数式组件的形式,通过 props 接收分类数据,使用 TouchableOpacity实现了点击交互效果
javascript·react native·react.js·ecmascript·交互·harmonyos
Amumu1213832 分钟前
Vuex介绍
前端·javascript·vue.js
We་ct33 分钟前
LeetCode 54. 螺旋矩阵:两种解法吃透顺时针遍历逻辑
前端·算法·leetcode·矩阵·typescript
2601_9498095944 分钟前
flutter_for_openharmony家庭相册app实战+相册详情实现
javascript·flutter·ajax
qq_177767371 小时前
React Native鸿蒙跨平台通过Animated.Value.interpolate实现滚动距离到动画属性的映射
javascript·react native·react.js·harmonyos
2601_949833391 小时前
flutter_for_openharmony口腔护理app实战+饮食记录实现
android·javascript·flutter
2601_949480061 小时前
【无标题】
开发语言·前端·javascript
css趣多多1 小时前
Vue过滤器
前端·javascript·vue.js
理人综艺好会2 小时前
Web学习之用户认证
前端·学习