直接使用marked.parse()存在xss风险,需配合dompurify净化、禁用html解析、隔离实例配置,并确保highlight.js在渲染后正确调用以实现代码高亮。

直接用 marked.parse() 是最简单可行的起点,但不加处理就塞进 innerHTML 会出安全问题,也容易漏掉代码高亮、公式等常见需求。
为什么不能直接 innerHTML = marked.parse(text)
浏览器不会自动执行 marked.parse() 返回的 HTML 中的脚本,但攻击者可以构造恶意链接、onerror 属性、SVG 标签等绕过基础过滤。比如输入 [xss](javascript:alert(1)) 或 <img src="https://img.php.cn/?x-oss-process=image/resize,p_40" alt="HTML怎么做Markdown渲染_html Markdown转HTML渲染方法【汇总】">,在未净化时会触发执行。
生产环境必须做两件事:
- 用
DOMPurify.sanitize()过滤 HTML 字符串(推荐配置{ALLOWED_TAGS: ['b','i','em','strong','p','h1','h2','h3','ul','ol','li','code','pre','blockquote']}) - 禁用
marked的原始 HTML 解析:调用marked.use({html: false}),避免用户输入里带<script></script>被原样保留
如何加载外部 .md 文件而不是写死在 HTML 里
用 fetch('article.md') 拿内容是最常用方式,但要注意两点:
- 服务器必须返回
Content-Type: text/plain或text/markdown,否则某些浏览器(如 Safari)会拒绝解析 - 如果文件路径是相对路径(如
./docs/intro.md),确保它在构建后仍可被静态服务访问;Vite/Next.js 等工具默认不托管.md文件,需显式配置 public 目录或 API 路由中转 - 错误处理不能只靠
.catch():fetch成功但响应状态码是 404 时,r.ok为false,需手动判断
示例片段:
fetch('./manual.md')
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.text();
})
.then(mdText => {
const html = marked.parse(mdText);
document.getElementById('content').innerHTML = DOMPurify.sanitize(html);
});
渲染后代码块没高亮?别只引 highlight.js CSS
highlight.js 的 JS 文件必须在渲染 HTML 后再遍历 pre code 节点调用 hljs.highlightElement(),否则样式只是空壳。
- CSS 只控制颜色和字体,JS 才真正做语法识别和 class 注入
- 如果用了
async: true选项(marked.use({async: true})),marked.parse()返回 Promise,必须await完再调高亮 - 注意版本兼容:highlight.js 11.x 的
highlightElement()不接受第三个参数,旧写法hljs.highlightBlock(block)已废弃
多个 Markdown 区域共存时,选项互相干扰怎么办
marked 默认是单例,全局共享配置。比如你在页面某处调用 marked.use({gfm: false}),所有后续 marked.parse() 都会关闭表格、任务列表支持。
解决方法是创建隔离实例:
import { Marked } from 'marked';
const mdRenderer = new Marked({ gfm: true, breaks: true });
const html = mdRenderer.parse('# Hello\n\n- item');
这样其他模块继续用默认 marked 实例也不会受影响。Node.js 环境下尤其要注意——服务端 SSR 渲染多个用户内容时,全局配置错乱会导致内容错乱。
真正麻烦的不是语法转换本身,而是净化、异步时机、实例隔离这三处细节,漏掉任一个都可能让页面在特定输入下崩溃或被利用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











