MathJax 配置资产:Quarto 网站与 Reveal 幻灯片

MathJax 4 本地运行时、相对路径与和 xaringan 资产的分工

Quarto
MathJax
Reveal.js
JavaScript
Author

Harness 技术文档

Published

October 13, 2026

摘要

js/mathjax-quarto/ 提供 Quarto 普通 HTML 与 Reveal.js 幻灯片 的离线公式运行时。运行时是 MathJax 4.1.3 的组合组件 tex-chtml.js,加上 CHTML 需要的 woff2/ 与 dynamic/。入口脚本 load-mathjax.js 按自身地址设置字体路径,再加载这个组件。

js/mathjax-xaringan/ 继续只服务 xaringan。两套资产的宿主、MathJax 世代和接入点都不同。

本地清点见 验收指引。


1. 和 js/mathjax-xaringan 的分工

js/mathjax-xaringan js/mathjax-quarto
宿主 xaringan / remark.js Quarto HTML 与 Reveal.js
MathJax 2.x,由 xaringan 自己加载 4.1.3,仓库内自带组合组件
配置入口 nature.beforeInit 指向 mathjax-config.js html-math-method.url 与 Reveal 的 mathjax3.mathjax
配置对象 tex2jax、TeX.extensions tex、output.fontURL;3 的配置对象可继续用在 4 上
配套文件 wrap-display-math.R(knitr hook,成对使用) 无 R hook;Reveal 另需改 Pandoc 模板插件名
字体 随 MathJax 2 的按需请求 只收录 @mathjax/mathjax-newcm-font 的 CHTML woff2 与 dynamic

把 mathjax-config.js 配进 Quarto,或把 tex-chtml.js 配进 xaringan 的 beforeInit,都不能得到另一套资产的行为。

flowchart LR
  subgraph xaringanHost [xaringan]
    beforeInit["mathjax-config.js<br/>MathJax 2 tex2jax"]
    hook["wrap-display-math.R"]
  end
  subgraph quartoHost [Quarto]
    html["html-math-method<br/>相对路径加载 load-mathjax.js"]
    reveal["RevealMath.MathJax3<br/>字面相对路径"]
    runtime["tex-chtml.js + woff2 + dynamic"]
  end
  beforeInit --> hook
  html --> runtime
  reveal --> runtime


2. 这套资产解决的问题

Quarto 默认把公式脚本指到 jsDelivr。课堂网络不稳,或用 Chrome、Cursor 直接打开 _site 里的 HTML 时,请求发不出去,页面上会留下 \(u_i\) 和 \[...\]。

MathJax 2.7.9 的完整发行包含未压缩源码、文档、测试和多套字体,解压后约 40MB。放进 project.resources 时,Quarto 递归扫描可能报 Maximum call stack size exceeded。2.x 还要先加载 MathJax.js,再按 ?config= 继续请求组件。3 与 4 改为一个组合组件,官方说明见 配置 与 加载。

Reveal 自带模板使用 math: 与插件 RevealMath。那是 MathJax 2 插件,加载后调用 MathJax.Hub。MathJax 4 没有这个接口。应改用模板里已经提供的 RevealMath.MathJax3:它先写入 window.MathJax,再加载一个组件文件。load-mathjax.js 会保留插件已经写好的 startup,翻页后仍会重新排版。

还有一条只在直接打开 HTML 时出现的路径问题。若用 new URL(...).href 从 reveal.js 的地址去推脚本地址,会同时错两处:从 site_libs/revealjs/dist/ 向上两级只到 site_libs/,请求落到 site_libs/vendor/;.href 还会把 Windows 盘符冒号编成 %3A,Chrome 不把 file:///d%3A/... 当作 D:\...。插件配置里的脚本地址因此写成字面相对路径。加载器内部的 new URL("woff2/", currentScript.src) 使用的是浏览器已经解析好的脚本 URL,这和“自己拼 file URL”不是同一件事。


3. 运行时内容

runtime/ 安装到业务项目的 vendor/mathjax/:

vendor/mathjax/
├── load-mathjax.js
├── tex-chtml.js
├── input/tex/extensions/boldsymbol.js
├── VERSION
├── LICENSE
├── NOTICE
├── woff2/
└── dynamic/

tex-chtml.js 约 1MB,是 TeX 输入加 CommonHTML 输出,来自 MathJax 4.1.3。woff2/ 现为 105 个文件,dynamic/ 现为 40 个文件,取自 @mathjax/mathjax-newcm-font 4.1.3 的 CHTML 子集。数量与版本写在 runtime/VERSION。组件与字体均为 Apache License 2.0,全文在 runtime/LICENSE,归属在 runtime/NOTICE。其中没有 MathJax 2 整包,也没有 SVG 字体包。boldsymbol.js 是 TeX 扩展,组合组件的预载列表里没有 \boldsymbol。

load-mathjax.js 用当前 <script> 的地址解析同目录资源,并合并 Reveal 插件可能已经写入的 window.MathJax。它还会把 $$ 里的 align、equation 等外层环境改成 aligned、gathered,避免 MathJax 4 报嵌套公式结构,并加载 [tex]/boldsymbol。普通 HTML、Reveal 与 xaringan 离线稿共用这一份加载器。xaringan 的联网稿仍用 MathJax 2,见 xaringan 双工作流。


4. 安装到业务项目

Profile 名是 mathjax-quarto。它没有放进 all,避免每个课程仓库都复制字体。

cd D:\github\harness
.\scripts\powershell\Install-Harness.ps1 `
  -InstallProfile mathjax-quarto `
  -ProjectRoot "D:\path\to\your-quarto-site"

安装结果:

源 目标
js/mathjax-quarto/runtime/ vendor/mathjax/
js/mathjax-quarto/snippets/quarto-html-math.yml _quarto.harness-mathjax.yml

把 _quarto.harness-mathjax.yml 合并进项目自己的 _quarto.yml:

project:
  resources:
    - vendor/mathjax
html-math-method:
  method: mathjax
  url: /vendor/mathjax/load-mathjax.js

/vendor/... 只作为 Quarto 的源配置。普通 HTML 渲染时会改写成相对于该页面的路径,例如主题页两级目录写成 ../../vendor/mathjax/load-mathjax.js,更深的页面层数更多。这条相对路径在 HTTP 与直接打开文件时都能找到资源。

.renvignore 建议加入 vendor/,避免 R 依赖扫描把这些 JavaScript 当成包代码。


5. Reveal 模板

Reveal 不走上面的自动改写。Quarto 1.10 的 share/formats/revealjs/pandoc/template.html 在 math: 里写入 mathjax: '$mathjaxurl$',插件名是 RevealMath。$mathjaxurl$ 会变成站点根绝对路径 /vendor/mathjax/load-mathjax.js,只有 HTTP 根目录正好是站点输出目录时才成立。

在业务项目根生成一份模板副本:

.\js\mathjax-quarto\patch-reveal-template.ps1 `
  -Destination "D:\path\to\your-quarto-site\vendor\mathjax\reveal-template.html"

默认路径是两级目录用的 ../../vendor/mathjax/load-mathjax.js。课件放在输出目录下三级时,把 -RelativePath 改成 ../../../vendor/mathjax/load-mathjax.js。脚本拒绝盘符、file:、http: 和反斜杠。

然后在幻灯片格式里引用这份模板,例如:

format:
  revealjs:
    template: ../../vendor/mathjax/reveal-template.html

这里的 ../../ 是 qmd 到模板文件 的路径,按源文件位置调整。模板内部的 mathjax: 则按 生成 HTML 到输出根 的深度填写。两套深度分别计算。

脚本针对 Quarto 1.10 模板中的 math: / TeX-AMS_HTML-full 块。升级 Quarto 后若脚本报找不到该块,按 snippets/reveal-math-block.txt 手工改,并确认插件仍是 RevealMath.MathJax3。

Pandoc 模板里的 $ 是变量。模板 JavaScript 若要输出 $ 定界符,需写成 $$。现行片段只保留 \( 与 \[。

生成后的幻灯片源码应类似:

mathjax3: {
  mathjax: "../../vendor/mathjax/load-mathjax.js"
}

插件列表中出现 RevealMath.MathJax3。源码里不应出现 cdn.jsdelivr.net、MathJax.Hub 或 file:///。


6. 验收

  1. 在 harness 中执行 Rscript js/mathjax-quarto/test/Rscript/test-inventory.R,看到 L0 PASS。
  2. 直接打开 js/mathjax-quarto/test/fixture/page.html,确认 \(u_i\) 已排版。
  3. 业务项目渲染后,普通页面的 <script src> 是指向 vendor/mathjax/load-mathjax.js 的相对路径。
  4. 同一份 Reveal HTML 用站点 HTTP 服务打开一次,再用浏览器直接打开文件一次。两种方式下公式都排版,且脚本 URL 的盘符未被编成 %3A。

移动 Reveal 课件的目录深度后,要改模板里的相对路径,并重新渲染。只改 _quarto.yml 的 html-math-method 不会更新幻灯片模板。

已经提交进仓库、且不是当前 qmd 渲染产物的静态 HTML,仍可能指向旧的 CDN。改 YAML 不会改写这些文件。


7. 相关文档