随着 Hexo 的发展,有越来越多的小伙伴使用 Hexo 搭建技术博客,其中不乏大量包含数学公式的场合:为了满足这种需要,一般是在 Markdown 中使用部分 \(\LaTeX\) 语法书写数学公式,Hexo 也支持使用 Mathjax
渲染这些公式;本是一件很舒服的事情,但是当编写多行公式的时候,会发现渲染出现错误:
比如下面的数学公式:
1 | \begin{bmatrix} |
是一个和范德蒙矩阵相关的算式;它正确渲染后的样子应该是这样的: \[ \begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^n \\ 1 & x_1 & x_1^2 & \cdots & x_1^n \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & x_n & x_n^2 & \cdots & x_n^n \end{bmatrix} \begin{bmatrix} a_0 \\ a_1 \\ \vdots \\ a_n \end{bmatrix} = \begin{bmatrix} y_0 \\ y_1 \\ \vdots \\ y_n \end{bmatrix} \] 但是实际上,渲染出来的结果很有可能是下面这样…… \[ \begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^n \ 1 & x_1 & x_1^2 & \cdots & x_1^n \ \vdots & \vdots & \vdots & \ddots & \vdots \ 1 & x_n & x_n^2 & \cdots & x_n^n \end{bmatrix} \begin{bmatrix} a_0 \ a_1 \ \vdots \ a_n \end{bmatrix} = \begin{bmatrix} y_0 \ y_1 \ \vdots \ y_n \end{bmatrix} \] 实在是让人头秃(
原因分析
一般而言,Hexo 是将公式块(被 $
或者 $$
包裹)渲染为一个元素,然后在页面中加载 MathJax
的 JS
文件。浏览器加载页面的时候运行 MathJax
读取这些元素中的公式并渲染完成替换的。但是 marked
会优先转义 Markdown 的语法,再考虑数学公式;因此当两者的语法冲突的时候,就会使得最终提供给 MathJax
的公式出现异常,导致渲染错误。
解决方法
既然已经确定了渲染不正确的原因是 hexo-renderer-marked
的原因,那么就可以从这方面入手考虑解决方法了:
修改 Marked.js
源码
Marked.js
源码因为 Marked.js
会先将下划线 escape 成 <em>
,将 \(LaTeX\) 中用于换行的 \\
转移成 \
,使得客户端的 MathJax
在渲染的时候无法正确读取公式导致渲染异常。因此,可以修改 nodes_modules
中的 marked
的源代码,或自行发布一个私有的 marked
作为依赖。
在 marked/lib/marked.js
中:
- 去掉
\
的额外转义 - 将
em
标签对应的符号中,去掉_
:因为markdown
中有*
可以表示斜体
修改方式如下:
首先删除对于反斜杠的转义
1 | - escape: /^\\([\\`*{}\[\]()# +\-.!_>])/, |
再删除对于 em
的多余的转义:
1 | - em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, |
遗憾地是上述做法并不适合目前较新版本的 Marked.js
,所以现在无法使用。如果有新版本的 Marked.js
的修改方法也欢迎分享(
强行适配渲染规则
因为主要受到影响的是多行公式,所以在需要使用多行公式的时候,使用 \\\\
替换 \\
,就可以保证页面的正常渲染。
但是这样并不能解决关于下划线的渲染问题还是得看脸,而且这样在 Typora 等可以正确渲染的本地环境下,看到的公式之间会有莫名的空行(并不优雅)。
更换渲染引擎(推荐)
既然是渲染引擎的锅,那么换一个渲染引擎就好了;下面列举作者尝试过的一些渲染引擎,并简单介绍一下它们的优缺点:
作者的博客在进行这些尝试的时候使用的主题是 Volantis 的 4.3.1
版本。
hexo-renderer-markdown-it
和 Hexo 默认使用的 hexo-renderer-marked
不同,在博文的 front-matter
不开启 mathjax
的情况下,似乎会先使用 Katex
渲染公式。Katex
会在生成静态文件的过程中就完成数学公式的渲染,而不是在客户端的浏览器中。因此可以获得很棒的加载速度(毕竟在一些性能较差的设备上经常能看见公式半天加载不出来的情况);但是问题在于 katex
支持的 \(\LaTeX\) 实在是太少了,远远不够用。
一些 Hexo 主题的作者也会基于这个渲染器进行一些修改,作者就没有一一尝试了。
hexo-renderer-kramed
这是一个 hexo-renderer-marked
的分支,也会和 hexo-renderer-marked
产生冲突。本来应该是一个很不错的选项,但是作者在使用它作为渲染引擎后运行 gulp
来最小化 HTML
会报错…… 所以就没有继续尝试了。
hexo-renderer-syzoj-renderer
大佬 Menci 出品,使用她为开源的 SYZOJ
写的 Markdown 渲染器来渲染文章的插件其实使用的还是 。优点是渲染行为和 markdown-it
marked
非常类似,且对于 MathJax
的支持和语法容忍度非常高:基本上是你在 Typora
里写的什么样,Hexo 渲染出来的就是什么样特别是对于 SYZOJ 用户,可以说是非常的舒服。
而且,虽然此渲染器使用 MathJax
渲染,但是这个过程也是在后端完成的。因此客户端浏览器的公式渲染速度很快,解决了 MathJax
公式渲染阻塞留白的问题。
但它的缺点就是在某些兼容性上可能有一些微妙的问题:博主使用的主题 Volantis 在开启 Pjax
后,包含数学公式的页面的 Pjax
跳转会失效(Issue #621 · volantis-x/hexo-theme-volantis);此外,很多主题会对于配置文件里写的字符串在布局的 ejs
文件中调用 markdown()
接口渲染成 Markdown,这个渲染器没有提供这个接口(也就是说,还得保留 hexo-rederer-marked
才可以使得这些部分正常渲染)。
如果你使用的博客主题没有 Pjax
,或者和这个渲染器没有什么冲突的话,还是非常推荐使用的。
hexo-renderer-pandoc
目前本博客采取的方案,在客户端使用 mathjax
渲染数学公式。相比于上面的 hexo-renderer-syzoj-renderer
容忍性较差:用来包裹行内公式的 $
和公式之间不能有空格,否则会渲染失败。此外,它对于一些其他的元素的渲染行为和 hexo-renderer-marked
的行为不同:比如对于 Markdown 图片 ![alt](url)
,它们的渲染结果分别如下:
使用 hexo-renderer-marked
的渲染结果:
1 | <img alt src="url"> |
使用 hexo-renderer-pandoc
的渲染结果:
1 | <figure> |
此外,另一个显著的问题是:直接写在文本中的链接(没有被 < >
和 []()
包裹的链接)不会被自动加上超链接。
不过这对于 Volantis 而言倒也问题不大:上面这个问题实际上只要外挂一个 CSS
就没什么问题了;至少这样的渲染并不会影响到 z-lazyload
的脚本对于图片的标记。但是对于其他的,按照 hexo-renderer-marked
的渲染行为而设计主题,就可能会产生兼容性问题。
并且,使用 hexo-renderer-pandoc
可以正常渲染配置文件中那些使用 markdown()
渲染的字符串。因此真的可以卸载 hexo-renderer-marked
了!
另一个问题就是 hexo-renderer-pandoc
的渲染需要依赖本地的 pandoc
:Pandoc - Installing pandoc;如果只是本机生成静态文件再上传到服务器 / Github Page 倒也不算是什么问题了,但是如果我们使用自动部署,就需要对原来的部署脚本进行一些修改:
Github Action
在编译和部署的命令之前增加安装 Pandoc 的命令:
1 | + - name: Setup Pandoc |
Vercel
目前无法在 Vercel 上安装 Pandoc
,所以无法使用 Vercel 来构建博客的静态文件。
总结
hexo-renderer-syzoj-renderer
:如果对Pjax
没有什么要求,且使用的主题没有兼容性的问题;hexo-renderer-pandoc
:兼容性略好,但是无法使用Vercel
自动构建静态页面;
Volantis 的社区里也确实没有说这个相关的文章啊,还是写一篇好了(
虽然但是,还是希望能够解决这个兼容性问题能用上 hexo-renderer-syzoj-renderer
啊()用 Pandoc 渲染在各种方面还是有点麻烦了==