Markdown编辑器如何实现代码块高亮并保留缩进?

司徒心虹 阅读 35

我在用Vue项目集成Markdown编辑器时卡住了。用了marked库渲染,代码块能显示但没有高亮,手动引入highlight.js后虽然能高亮了,但输入的缩进会被浏览器自动压缩成一个空格,代码看起来乱糟糟的。比如写:

<script>
function test() {
  console.log('缩进全没了')
}
</script>

渲染后代码块里的缩进全变成单空格,而且高亮只对第一行生效。试过设置

标签的tabIndex属性和添加preserveSpaces配置,但都没用。搞不懂为啥marked和highlight.js配合时会出现这种情况,有没有更好的解决办法?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
Air-艳君
这个问题我遇到过好几次,确实挺烦人的。我来给你捋清楚整个流程和解决方案。

首先得明白为啥会这样。marked负责把markdown转成html,highlight.js负责给代码加高亮。问题出在marked生成的html结构可能不符合highlight.js的预期,而且浏览器默认会把连续空白符合并成一个空格。

解决方案分这几步走:

1. 确保marked生成正确的pre标签结构
2. 配置highlight.js正确识别代码块
3. 用css强制保留空白符

先看marked配置部分:

import marked from 'marked';
import hljs from 'highlight.js';

marked.setOptions({
highlight: function(code, lang) {
// 这个回调处理每个代码块
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(lang, code, true).value;
} catch (e) {
console.error(e);
}
}
return hljs.highlightAuto(code).value;
},
// 这个很重要,确保生成的html保留换行
breaks: false,
// 这个保证代码块外层有pre标签
gfm: true
});


然后处理css部分,必须加这条规则:

pre code {
white-space: pre !important;
tab-size: 2;
}


white-space: pre是关键,它告诉浏览器保留所有空白和换行。tab-size控制缩进宽度,按你的习惯设置2或4。

还有个小坑要注意,vue的模板里如果用v-html渲染marked结果,记得处理转义:

// 在vue组件里
computed: {
compiledMarkdown() {
return marked(this.content, {
sanitize: false // 避免html被转义
});
}
}


如果还不行,可能是highlight.js的版本问题。可以试试手动初始化:

mounted() {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
});
}


我上周刚在项目里解决过一模一样的问题,这么处理之后代码缩进和高亮都正常了。有时候前端这些小问题真的很磨人,特别是几个库配合用的时候。
点赞 1
2026-03-09 09:13
露露 ☘︎
你这问题出在两个地方,一个是marked的配置没对,另一个是highlight.js的调用时机错了。

首先,marked默认会把代码里的空格和制表符处理掉,得手动关掉这个行为。你初始化marked的时候要加个选项:mangle: falseheaderIds: false 其实不关键,重点是你要确保传给marked的源文本里缩进是保留的,而且渲染时不要让DOM自动吃掉空白。

真正的解法是配合CSS和正确的API调用顺序。先装好highlight.js,然后这么配marked:

import marked from 'marked';
import hljs from 'highlight.js';

// 设置marked的renderer
const renderer = new marked.Renderer();
renderer.code = function(code, lang) {
// 确保lang存在就高亮,否则也保留code原样
const validLang = !!hljs.getLanguage(lang) ? lang : 'plaintext';
const highlighted = hljs.highlight(validLang, code).value;
return
${highlighted}
;
};

// 设置marked使用这个renderer
marked.setOptions({ renderer });


注意这里直接返回了带pre包裹的结构,因为代码块必须在外层用pre才能保留空白。然后你在CSS里加上:

code.hljs {
white-space: pre;
word-wrap: normal;
overflow: auto;
padding: 1em;
}


white-space设成pre或者pre-wrap是关键,不然浏览器会把多个空格合并。

还有个常见坑是你可能在Vue里用v-html直接插字符串,但数据流不对的话,highlight.js可能会在DOM更新前就跑完了。如果你发现高亮不生效,别用mounted里一次性调,改成用nextTick或者watch文本变化后手动触发一次highlight,或者干脆全交给marked在render阶段做完。

最后建议换prism.js也行,但它也有类似问题。最稳的方式还是我上面写的——在marked的code渲染钩子里直接调用highlight.js API完成高亮,这样流程可控,缩进也能保住。
点赞 7
2026-02-11 09:05