Hugo 中的数学公式渲染:在 Markdown 中使用 LaTeX 标记的完整指南
2026/9/18 9:09:05 网站建设 项目流程

Hugo 中的数学公式渲染:在 Markdown 中使用 LaTeX 标记的完整指南

【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo

导读

本指南面向使用 Hugo 构建学术、科学类网站的开发者,讲解如何在 Markdown 内容中嵌入 LaTeX 数学公式与表达式,并提供完整的配置与模板方案。读完本文,你将掌握基于 Goldmark passthrough 扩展保留原始数学标记、通过 MathJax 或 KaTeX 在前端渲染公式、按页面按需启用数学功能(math参数)的完整实战流程,以及$...$内联分隔符的坑与规避方法。

概述:为什么需要在 Markdown 中写数学

数学公式与表达式(以 LaTeX 标记书写)在学术与科学出版物中极为常见。浏览器本身并不认识 LaTeX 语法,通常需要借助 MathJax 或 KaTeX 这类开源 JavaScript 显示引擎将其渲染为可视化的数学排版。

例如,下面这段 LaTeX 标记(KL 散度与 JS 散度的定义):

\[ \begin{aligned} KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\ JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2})) \end{aligned} \]

渲染后的结果就是经典的公式排版效果。

公式既可以在文本中行内(inline)显示,也可以作为独立块(block)显示(后者也被称为 display 模式)。一个公式是行内还是块级,取决于包围数学标记的分隔符(delimiters)。分隔符成对出现,每一对由开始分隔符与结束分隔符组成,二者可以相同(如$$...$$),也可以不同(如\[...\])。

[!NOTE] 你有两种路线在 Hugo 中渲染数学标记:一是配置 Hugo 在客户端使用 MathJax 或 KaTeX 引擎渲染(本指南下文详述);二是使用transform.ToMath函数在构建项目时完成渲染。

前置条件:启用 Goldmark passthrough 扩展

要保留 Markdown 中被分隔符包裹的原始文本(包括分隔符本身)不被 Goldmark 的默认解析器改写,需要启用 passthrough 扩展。该扩展在 Hugo 源码中位于 markup/goldmark/passthrough/passthrough.go,底层基于github.com/gohugoio/hugo-goldmark-extensions/passthrough实现。从源码可以看到,扩展在Extend方法中把配置中的行内/块级分隔符逐对转换为passthrough.Delimiters{Open, Close}结构(passthrough.go#L44-L72),并注册对应的 HTML 渲染器。

在项目配置中启用并配置该扩展:

[markup.goldmark.extensions.passthrough] enable = true [markup.goldmark.extensions.passthrough.delimiters] block = [['\[', '\]'], ['$$', '$$']] inline = [['\(', '\)']] [params] math = true

上述配置的要点:

  • enable = true开启 passthrough 扩展;
  • blockinline均为一组「开始/结束」分隔符对的列表,格式与源码 goldmark_config/config.go#L247-L257 中DelimitersConfig的注释完全一致:每个条目是长度为 2 的字符串列表,第一个是开始分隔符,第二个是结束分隔符;
  • [params] math = true用于控制是否加载前端渲染脚本(见下文 Step 3)。

[!NOTE] 上述配置中math = true意味着每个页面都会启用数学渲染。若希望按需启用,可在项目配置中把math设为false,再在需要的页面 front matter 中单独设置math = true

[!WARNING] 上面的配置刻意排除了$...$内联分隔符。虽然你可以把$...$同时加进配置与 JavaScript,但一旦在非数学语境中使用$符号(如美元金额),就会引发意外的格式错乱,必须对$做双重转义(详见下文「Inline delimiters」一节)。

变体一:只保留块级公式

如果不需要行内公式的 passthrough,省略inline键即可:

[markup.goldmark.extensions.passthrough.delimiters] block = [['\[', '\]'], ['$$', '$$']]

变体二:自定义分隔符

你可以定义自己的开始/结束分隔符,但必须与前端引擎(Step 2)中设置的分隔符保持一致。例如用@@作为块级、@作为行内分隔符:

[markup.goldmark.extensions.passthrough.delimiters] block = [['@@', '@@']] inline = [['@', '@']]

完整配置步骤:5 步接入数学渲染

Step 1:配置 Goldmark passthrough 扩展

即上文「前置条件」中的配置。启用扩展后,Goldmark 解析器会原样保留分隔符包裹的原始内容(含分隔符本身),交给前端渲染引擎处理。

Step 2:创建加载渲染引擎的 partial 模板

创建 layouts/_partials/math.html 来加载 MathJax 或 KaTeX。下面的示例加载 MathJax:

<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script> <script> MathJax = { tex: { displayMath: [['\\[', '\\]'], ['$$', '$$']], // block inlineMath: [['\\(', '\\)']] // inline }, loader:{ load: ['ui/safe'] }, }; </script>

这里 JavaScript 中声明的displayMathinlineMath分隔符必须与项目配置中的分隔符一一对应,否则公式无法被识别渲染。

Step 3:在 base 模板中按条件加载 partial

在 layouts/baseof.html 的<head>中条件调用:

<head> {{ if .Param "math" }} {{ partialCached "math.html" . }} {{ end }} </head>

说明:

  • 若页面 front matter 中设置了math = true,则加载该 partial;
  • 若页面 front matter 未设置math,则回退读取项目配置中的[params] math值;
  • 使用partialCached可以避免多页面重复渲染该脚本。

Step 4:按需启用时在 front matter 中声明

如果你在项目配置中把math设为了false,则需要在每个需要公式的页面 front matter 中显式开启:

title = 'Math examples' date = 2024-01-24T18:09:49-08:00 [params] math = true

Step 5:在 Markdown 中书写公式

以下示例展示了行内与块级公式的完整写法(对应文件 docs/content/en/content-management/mathematics.md 中的示例):

This is an inline \(a^*=x-b^*\) equation. These are block equations: \[a^*=x-b^*\] \[ a^*=x-b^* \] \[ a^*=x-b^* \] These are also block equations: $$a^*=x-b^*$$ $$ a^*=x-b^* $$ $$ a^*=x-b^* $$

可以看到,块级分隔符\[...\]$$...$$均支持「同一行紧凑书写」「带空格的书写」「独占多行书写」三种风格。

Inline delimiters:\(...\)$...$的选择

上文配置与 JavaScript 示例均使用\(...\)作为行内分隔符。$...$是更常见的备选,但在非数学语境中使用$符号时可能引发意外格式化。

如果你坚持把$...$加入配置与 JavaScript,那么当$出现在数学语境之外时,必须双重转义,例如:

I will give you \\$2 if you can solve $y = x^2$.

[!NOTE] 若你使用$...$行内分隔符,且偶尔会在数学语境之外使用$符号,就必须选用MathJax 而非 KaTeX,以规避 KaTeX 的已知限制(该限制会导致未转义的$触发意外格式化)。

渲染引擎:MathJax 与 KaTeX 对比使用

MathJax 与 KaTeX 都是开源 JavaScript 显示引擎。二者的取舍除了渲染速度与功能差异外,如上文所述,若采用$...$行内分隔符且正文会用到$符号,则应选用 MathJax。

使用 KaTeX 时,把 Step 2 的 partial 模板替换为如下内容(使用 KaTeX 0.17.0,通过 CDN 引入样式、核心脚本与 auto-render 组件):

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.17.0/dist/katex.min.css" integrity="sha384-vlBdW0r3AcZO/HboRPznQNowvexd3fY8qHOWkBi5q7KGgqJ+F48+DceybYmrVbmB" crossorigin="anonymous"> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.17.0/dist/katex.min.js" integrity="sha384-AtrdNsnxl/75rvBneBVH7DtOvCxSVahR2zWqle1coBKd8DEmLoviqNeJSx64gNAs" crossorigin="anonymous"></script> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.17.0/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous" onload="renderMathInElement(document.body);"> </script> <script> document.addEventListener("DOMContentLoaded", function() { renderMathInElement(document.body, { delimiters: [ {left: '\\[', right: '\\]', display: true}, // block {left: '$$', right: '$$', display: true}, // block {left: '\\(', right: '\\)', display: false}, // inline ], throwOnError : false }); }); </script>

KaTeX 方案的关键点:

  • 通过auto-render扩展在DOMContentLoaded后扫描document.body,依据delimiters数组中声明的分隔符自动渲染;
  • display: true表示块级(display)模式,display: false表示行内模式;
  • throwOnError: false确保遇到无法解析的内容时不抛出异常打断页面;
  • 同样,这里的分隔符必须与项目配置保持一致。

化学方程式:mhchem 支持

MathJax 与 KaTeX 都支持化学方程式。例如(水的定压热容):

$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$

渲染结果为标准的化学热力学表达式。

如 Step 2 所示,MathJax无需额外配置即可支持化学方程式;KaTeX 则需要按官方 KaTeX 文档启用mhchem 扩展

原理纵深:passthrough 扩展与渲染钩子

Hugo 的数学渲染方案可以拆成两层理解:

  1. 构建期(Go 端):Goldmark passthrough 扩展负责在 Markdown 解析时识别分隔符对,将内部原始文本(含分隔符)原样保留,不会像普通 Markdown 解析那样把$\_等字符当作格式语法处理。源码 markup/goldmark/passthrough/passthrough.go 中的renderPassthroughBlock会优先查找用户注册的渲染钩子(hooks.PassthroughRenderer),若未找到则直接输出原始内容(passthrough.go#L122-L127)。

  2. 渲染期(浏览器端):MathJax 或 KaTeX 在页面加载后扫描 DOM,把分隔符包围的 LaTeX 文本渲染为数学排版。Hugo 侧的math参数只是控制是否加载引擎脚本,真正的排版渲染完全发生在客户端。

集成测试 markup/goldmark/passthrough/passthrough_integration_test.go 验证了这条链路:配置block = [['$$','$$']]inline = [['$','$']]后,页面中的$a^*=x-b^*$会被渲染钩子输出为Passthrough inline: a^*=x-b^*|inline|0:END,块级$$a^*=x-b^*$$输出为Passthrough block: a^*=x-b^*|block|1:END——可见行内与块级 passthrough 共享同一个序号计数器(Ordinal),且分隔符本身会被裁剪,只保留内部的 LaTeX 内容。

常见问题与最佳实践

  • 公式不渲染:优先检查 Step 1 配置与 Step 2/KaTeX partial 中的分隔符是否完全一致;其次确认 base 模板的条件判断(Step 3)与math参数的取值链路。
  • 正文中的$引发错乱:避免使用$...$行内分隔符,改用\(...\);如确需使用,对$双重转义,并选用 MathJax 引擎。
  • 所有页面都加载引擎脚本:将[params] math默认设为false,仅在需要的页面 front matter 中开启,配合partialCached控制脚本重复加载。
  • 自定义分隔符:只要保证 Hugo 配置与前端引擎两侧的分隔符一一对应,就可以使用任意成对字符(如@@/@)。

按上述 5 个步骤完成配置后,即可在 Hugo 站点中自由书写行内与块级 LaTeX 数学公式,并平滑支持化学方程式,兼顾学术写作的严谨性与站点的构建性能。

【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询