Vue2 项目里怎么高效用 Markdown?从渲染到编辑器全流程解析
做前端项目时,不少同学在 Vue2 技术栈里碰到 Markdown 相关需求——比如写博客要渲染文章、做后台系统要做 Markdown 编辑器、搞组件库文档要嵌入交互 Demo……但从“把 Markdown 转成网页”到“做个好用的编辑器”,再到“优化性能和 SEO”,每个环节都有不少细节要踩坑,这篇文章用问答形式,把 Vue2 + Markdown 从基础到进阶的实操要点拆明白,帮你少走弯路。
Vue2 里怎么把 Markdown 文本渲染成网页内容?
想在页面上展示 Markdown 写的文档、文章,核心是把 .md 格式的纯文本转换成 HTML,Vue2 项目里常用这三种思路:
用现成的 Vue 组件(如 vue-markdown)
这类组件把 Markdown 解析和 Vue 的响应式结合得很丝滑,以 vue-markdown 为例:
-
第一步:装依赖
npm install vue-markdown --save
-
第二步:在组件里引入 + 使用
<template> <div> <vue-markdown :source="articleMd"></vue-markdown> </div> </template> <script> import VueMarkdown from 'vue-markdown' export default { components: { VueMarkdown }, data() { return { articleMd: `# 我的第一篇 Vue 博客 这里用 **Markdown** 写内容,列表: - 功能 1 - 功能 2` } } } </script>它会自动把
source里的 Markdown 转成带样式的 HTML,如果需要自定义(比如代码高亮、链接跳转规则),可以传options参数,比如关闭自动转义:<vue-markdown :source="md" :options="{ escapes: { block: [] } }"></vue-markdown>。
手动用解析库(如 marked.js)+ v-html
如果想更灵活控制解析过程,用 marked.js 自己处理更自由。
-
装依赖:
npm install marked --save
-
组件里解析 + 渲染:
<template> <div v-html="renderedHtml"></div> </template> <script> import marked from 'marked' export default { data() { return { rawMd: '# 标题\n内容' } }, computed: { renderedHtml() { // 自定义解析规则,比如给链接加 target="_blank" const renderer = new marked.Renderer() renderer.link = function(href, title, text) { return `<a href="${href}" title="${title}" target="_blank">${text}</a>` } return marked(this.rawMd, { renderer }) } } } </script>但注意:
v-html直接渲染 HTML 有 XSS 风险,生产环境要对内容过滤(比如用DOMPurify库清洗恶意脚本)。
用 webpack loader(如 vue-markdown-loader)
如果是写静态文档(比如组件库说明),可以在构建时把 .md 文件转成 Vue 组件。
-
装 loader:
npm install vue-markdown-loader --save-dev
-
配 webpack(或 vue-cli 的
chainWebpack):// vue.config.js module.exports = { chainWebpack: config => { config.module .rule('md') .test(/\.md$/) .use('vue-loader') .loader('vue-loader') .end() .use('vue-markdown-loader') .loader('vue-markdown-loader') .options({ // 配置:比如启用代码高亮 highlight: code => require('highlight.js').highlightAuto(code).value }) } } -
然后在项目里直接导入
.md文件当组件用:<template> <div> <MyDoc /> </div> </template> <script> import MyDoc from './docs/guide.md' export default { components: { MyDoc } } </script>这种方式适合静态内容,构建时就把 Markdown 转成 HTML,页面加载更快。
Vue2 项目需要 Markdown 编辑器,选哪个插件方便?
场景不同,选的编辑器也不一样,从“轻量写文档”到“复杂富文本 + Markdown”,推荐这几个常用选项:
mavon-editor:轻量又全能
个人博客、小后台系统选它准没错,功能覆盖实时预览、大纲导航、代码高亮、图片上传,而且文档特别全。
-
快速上手:
npm install mavon-editor --save
<template> <mavon-editor v-model="articleContent" :toolbars="customToolbars"></mavon-editor> </template> <script> import { mavonEditor } from 'mavon-editor' import 'mavon-editor/dist/css/index.css' // 引入默认样式 export default { components: { mavonEditor }, data() { return { articleContent: '', customToolbars: { bold: true, // 加粗 italic: true, // 斜体 header: true, // 标题 image: true // 图片上传(要自己写上传逻辑) } } } } </script>图片上传得自己处理:通过
@image-upload事件,把用户选的文件传给后端,拿到 URL 后替换到 Markdown 里。<mavon-editor @image-upload="handleImageUpload" ></mavon-editor> methods: { async handleImageUpload(file, done) { // 1. 调后端接口传 file const res = await uploadFileApi(file) // 2. done 回调把图片 URL 插入编辑器 done(res.data.imgUrl) } }
TOAST UI Editor:功能强大但略重
如果要做多人协作、版本管理、复杂表格这类高级功能,选 TOAST UI Editor(以前叫 tui-editor),它支持 Markdown 和富文本双模式切换,生态成熟。
-
安装 + 基本使用:
npm install @toast-ui/vue-editor --save
<template> <tui-editor :initialValue="initMd" :options="{ previewStyle: 'vertical' }" ></tui-editor> </template> <script> import { Editor } from '@toast-ui/vue-editor' import '@toast-ui/editor/dist/toastui-editor.css' // 核心样式 import '@toast-ui/editor/dist/toastui-editor-preview.css' // 预览样式 export default { components: { 'tui-editor': Editor }, data() { return { initMd: '# 标题\n内容' } } } </script>缺点是体积大(打包后增加几百 KB),如果项目对体积敏感,优先选
mavon-editor。
vue2-editormd:对程序员友好
基于经典的 editormd 封装,内置代码高亮、流程图、时序图支持,适合写技术文档、接口说明。
- 用法类似上面的库,装依赖后引入组件,配置工具栏,它的优势是代码块功能特别全,支持一键复制代码、切换主题。
Markdown 渲染后的代码块怎么实现高亮?
代码块高亮是技术类博客、文档的刚需,核心逻辑是:让 Markdown 转成的代码块 HTML,能被高亮库识别并染色。
步骤拆解(以 highlight.js + vue-markdown 为例)
-
装高亮库和样式:
npm install highlight.js --save
-
在 Vue 项目里全局引入样式(比如选
atom-one-light主题):// main.js import 'highlight.js/styles/atom-one-light.css'
-
让 Markdown 渲染器给代码块加“语言标识”:
用vue-markdown时,默认会给代码块加class="language-xxx"(xxx 是代码语言,js、html),如果用marked.js,要手动配置 renderer:import marked from 'marked' const renderer = new marked.Renderer() renderer.code = function(code, lang) { // lang 是代码块的语言(```js 里的 js) return `<pre><code class="language-${lang}">${code}</code></pre>` } -
页面渲染后,触发高亮:
因为 Markdown 转 HTML 是异步的,所以要等 DOM 更新后再执行高亮,用vue-markdown时,可以监听@rendered事件:<vue-markdown :source="md" @rendered="handleHighlight" ></vue-markdown> import hljs from 'highlight.js' methods: { handleHighlight() { // 找到所有代码块,执行高亮 document.querySelectorAll('pre code').forEach(block => { hljs.highlightElement(block) }) } }如果是用
v-html渲染marked.js的结果,就在mounted或updated里执行高亮:mounted() { this.$nextTick(() => { this.highlightCode() }) }, methods: { highlightCode() { document.querySelectorAll('pre code').forEach(block => { hljs.highlightElement(block) }) } }
其他高亮库对比
highlight.js:开箱即用,支持 100 + 语言,有几十种主题,适合快速实现。prism.js:更轻量,支持按需加载语言和主题,但需要手动注册语言(比如只需要 js 高亮,就只装prism-js)。
Vue2 中怎么给 Markdown 内容自定义样式?
默认的 Markdown 渲染样式可能和项目 UI 不搭,得改样式,分两种思路:
直接改渲染后的 HTML 元素样式
Markdown 转 HTML 后,结构是固定的(比如标题是 h1 - h6,列表是 ul>li,代码块是 pre>code),可以在 Vue 的样式里写:
<style scoped>
/* 作用域样式,用 /deep/ 穿透 */
/deep/ h1 {
color: #2f54eb;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
/deep/ p {
line-height: 1.8;
margin: 16px 0;
}
/deep/ pre {
background: #f7fafc;
padding: 16px;
border-radius: 4px;
}
</style>
但要注意:scoped 样式里的 /deep/(或 >>>)是为了让样式穿透到子组件(这里是 Markdown 渲染出的 HTML),如果不用 scoped,直接写全局样式也能生效,但要注意命名冲突。
自定义 Markdown 渲染器,给元素加 class
如果想更精细控制(比如不同场景的 h1 用不同 class),可以在解析 Markdown 时,给 HTML 元素插自定义 class,以 marked.js 为例:
import marked from 'marked'
// 自定义 renderer,给 h2 加 class="article-subheading"
const renderer = new marked.Renderer()
renderer.heading = function(text, level) {
const className = level === 2 ? 'article-subheading' : ''
return `<h${level} class="${className}">${text}</h${level}>`
}
// 渲染时用这个 renderer
const html = marked(markdownContent, { renderer })
然后在样式里写:
.article-subheading {
color: #666;
font-size: 24px;
margin: 24px 0 16px;
}
这种方式适合做主题切换、多场景复用(比如博客详情和列表预览用不同标题样式)。
大型 Vue2 项目中,大量 Markdown 渲染怎么优化性能?
如果页面要渲染几万字的 Markdown(比如长文档、技术手册),直接转 HTML 会阻塞主线程,导致页面卡,这时候得做性能优化:
分片渲染:把大文本拆成小块分批渲染
原理:把 Markdown 内容按段落(或换行)拆成数组,用 setTimeout 或 requestIdleCallback 分批转 HTML + 渲染,让浏览器有时间处理动画、事件等。
示例逻辑:
<template>
<div ref="mdContainer"></div>
</template>
<script>
import marked from 'marked'
export default {
data() { return { bigMd: '...(几万字内容)...' } },
mounted() {
this.splitAndRender()
},
methods: {
splitAndRender() {
const chunks = this.bigMd.split('\n\n') // 按空行拆分成段落
const container = this.$refs.mdContainer
let index = 0
const renderChunk = () => {
if (index >= chunks.length) return
// 渲染当前块
const html = marked(chunks[index])
container.innerHTML += html
index++
// 下一帧再渲染下一块(给浏览器喘息时间)
requestAnimationFrame(renderChunk)
}
renderChunk()
}
}
}
</script>
服务端渲染(SSR)+ 静态生成
如果用 Nuxt.js 这类 SSR 框架,可以把 Markdown 转 HTML 的过程放到服务端,这样客户端拿到的是现成的 HTML,减少前端计算压力。
Nuxt.js 项目里,在 asyncData 里处理 Markdown:
// pages/article/_id.vue
export default {
async asyncData({ params }) {
const mdContent = await import(`~/content/articles/${params.id}.md`)
const html = marked(mdContent.default)
return { html }
}
}
然后在模板里直接渲染:
<template> <div v-html="html"></div> </template>
缓存:避免重复解析相同内容
如果多个页面用到相同的 Markdown 片段(比如公共文档),可以用 Vuex 或全局对象缓存解析后的 HTML:
// utils/mdCache.js
const cache = new Map()
export function getCachedHtml(mdKey, mdContent) {
if (cache.has(mdKey)) {
return cache.get(mdKey)
}
const html = marked(mdContent)
cache.set(mdKey, html)
return html
}
组件里用:
import { getCachedHtml } from '@/utils/mdCache.js'
export default {
computed: {
renderedHtml() {
return getCachedHtml('doc-guide', this.mdContent)
}
}
}
虚拟滚动:只渲染可视区域内容
Markdown 转成的 HTML 特别长,结合虚拟滚动库(如 vue-virtual-scroll-list),只渲染用户能看到的部分,原理是计算滚动位置,动态加载对应区域的 HTML 片段。
Markdown 和 Vue 组件怎么结合?比如在 Markdown 里嵌入 Vue 组件?
场景很常见:写技术文档时,想在 Markdown 里插个“在线 Demo 组件”(比如点按钮展示效果),或者插个图表组件,实现思路是自定义 Markdown 解析规则,识别特定标记并替换成 Vue 组件。
步骤拆解(以 marked.js + 动态组件为例)
假设我们约定:用 ::: component-name props={...} ::: 这种语法表示要插入 Vue 组件。
- 自定义
marked.js的 renderer,解析特殊语法:import marked from 'marked'
const renderer = new marked.Renderer() // 重写 paragraph 方法,识别以 ::: 开头的行 renderer.paragraph = function(text) { if (text.startsWith(':::')) { // 匹配语法:::: MyButton props={label:"点我"} ::: const match = text.match(/::: (\w+) props=({.*})
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网




发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。