markdown与hexo:下划线冲突与终极方案



以下内容完全由gemini 3.1pro生成(因为我大半夜的搏斗了两个小时快累死了)


如果你经常在博客里敲写复杂的公式(比如带海量下标的矩阵和行列式推导),并且使用的是 Hexo 博客框架,那么你大概率经历过这样的绝望时刻:

在本地 Typora 里,克莱姆法则(Cramer’s rule)的推导公式优雅完美:
$$A_i = [\mathbf{a}_1, \dots, \mathbf{a}_{i-1},\mathbf{b},\mathbf{a}_{i+1},\dots,\mathbf{a}_n]$$

满心欢喜地 hexo deploy 推送到网站后,打开网页一看,下划线全没了,排版炸成一团乱码,甚至公式里凭空出现了刺眼的明黄色斜体字。

这篇文章记录了我与 Hexo 7.x 底层渲染器长达数天的“生死搏斗”,并为你提供一个无需更换引擎、无需安装第三方废弃插件的终极物理隔离方案

🕵️‍♂️ 案发现场还原:谁吃了我的下划线?

在 Hexo 的世界里,数学公式需要经过两道关卡:

  1. 后端的 Markdown 引擎(通常是默认的 marked:负责把 Markdown 语法转成 HTML。
  2. 前端的主题脚本(如 Fluid 内置的 MathJax/KaTeX):负责把 HTML 里的 LaTeX 代码渲染成漂亮的数学符号。

灾难就发生在这第一道关卡。

Markdown 语法中,成对的下划线 _文本_ 会被转换成 HTML 的斜体标签 <em>文本</em>
虽然现在的 Markdown 引擎有所谓的“词内豁免权”(比如 x_1 不会被转义),但一旦你的下标碰到了标点符号或大括号 {},比如 \mathbf{a}_{i-1},豁免权立刻失效!

底层引擎会像推土机一样,把 _{i-1} 前面的下划线和 _{i+1} 前面的下划线强行配对,把它们中间包裹的所有内容全部变成斜体 <em>
等这堆千疮百孔的残缺代码送到网页前端时,MathJax 发现公式里混进了 HTML 标签,直接宣告罢工。

💣 避坑指南:为什么网上的老教程都失效了?

在查阅资料时,你会搜到无数教程,但它们在最新的 Hexo 7.x 环境下几乎全部失效:

  1. 安装 hexo-filter-mathjax 插件?
    失效。在 Hexo 7.x 搭配的新版 marked 引擎面前,这个旧时代插件的生命周期完全被打乱,它根本拦截不住新引擎激进的正则匹配。
  2. 更换 kramed 引擎?
    失效。这是一个早已年久失修的老古董,对现代 Node.js 环境兼容极差,换上后往往会导致公式“连锅端”全军覆没。
  3. 更换 markdown-it 引擎?
    半失效。虽然它底层基于 AST(抽象语法树),能完美保护公式,但它生成了极其复杂的私有 HTML 标签。如果你的主题(如 Fluid)前端没有精确匹配它的 CSS 样式表,公式依然会“裸奔”。

🚀 终极杀招:编写原生“渲染前拦截器”

既然第三方插件靠不住,Markdown 引擎又是个没长眼睛的“破坏王”,那我们就不和它讲道理了,直接进行降维打击(物理隔离)

利用 Hexo 强大的原生 scripts 扩展功能,我们可以写一个拦截脚本:在 Markdown 引擎触碰文章之前,把所有的公式挖出来藏进保险箱,原地留下一个安全的占位符;等引擎把普通文字渲染完,我们再把公式完好无损地放回原地。

实施步骤:

  1. 在你的 Hexo 博客根目录(与 sourcethemes 同级)下,新建一个文件夹,命名为 scripts(注意全小写)。
  2. 在该文件夹内,新建一个文件名为 protect-math.js
  3. 将以下代码完整复制进去并保存:
/* scripts/protect-math.js */

// 1. 在 Markdown 渲染之前执行拦截:把公式藏起来
hexo.extend.filter.register('before_post_render', function(data) {
    data.mathBlocks = [];
    
    // 提取并保护块级公式 $$...$$
    data.content = data.content.replace(/\$\$([\s\S]*?)\$\$/g, function(match) {
        data.mathBlocks.push(match);
        return '@@MATHBLOCK_' + (data.mathBlocks.length - 1) + '@@';
    });
    
    // 提取并保护行内公式 $...$
    data.content = data.content.replace(/\$([^$\n]+?)\$/g, function(match) {
        data.mathBlocks.push(match);
        return '@@MATHINLINE_' + (data.mathBlocks.length - 1) + '@@';
    });
    
    return data;
});

// 2. 在 Markdown 渲染完成之后执行归还:把公式放回原地
hexo.extend.filter.register('after_post_render', function(data) {
    if (!data.mathBlocks) return data;
    
    // 还原块级公式
    data.content = data.content.replace(/@@MATHBLOCK_(\d+)@@/g, function(match, i) {
        return data.mathBlocks[i];
    });
    
    // 还原行内公式
    data.content = data.content.replace(/@@MATHINLINE_(\d+)@@/g, function(match, i) {
        return data.mathBlocks[i];
    });
    
    return data;
});

markdown与hexo:下划线冲突与终极方案
https://goyeah.org/2026/03/31/markdown的下划线渲染问题/
作者
goyeah
发布于
2026年3月31日
许可协议