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 扩展;block与inline均为一组「开始/结束」分隔符对的列表,格式与源码 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 中声明的displayMath与inlineMath分隔符必须与项目配置中的分隔符一一对应,否则公式无法被识别渲染。
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 = trueStep 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 的数学渲染方案可以拆成两层理解:
构建期(Go 端):Goldmark passthrough 扩展负责在 Markdown 解析时识别分隔符对,将内部原始文本(含分隔符)原样保留,不会像普通 Markdown 解析那样把
$、\、_等字符当作格式语法处理。源码 markup/goldmark/passthrough/passthrough.go 中的renderPassthroughBlock会优先查找用户注册的渲染钩子(hooks.PassthroughRenderer),若未找到则直接输出原始内容(passthrough.go#L122-L127)。渲染期(浏览器端):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),仅供参考