Math 渲染引擎评估与加载策略

Math 渲染引擎评估与加载策略

背景

Valaxy 需要支持 Markdown 中的数学公式渲染。此前仅支持 KaTeX,VitePress 则选择了 MathJax3。本文评估两种引擎的优劣,以及 KaTeX 的加载策略。

KaTeX vs MathJax3 对比

渲染性能

维度KaTeXMathJax3
渲染速度极快(专为速度优化)较慢(功能更全面)
渲染位置Node 端构建时渲染为 HTMLNode 端构建时渲染为 SVG
客户端 JS零(构建时已渲染完成)零(构建时已渲染完成)

两者在 Valaxy 中都是构建时渲染,不依赖客户端 JS,因此运行时性能差异不大。

输出格式与依赖

维度KaTeXMathJax3
输出格式HTML + CSS spansSVG(自包含矢量图)
外部 CSS需要 katex.min.css(~1.2KB gzip)
字体文件~20 个 woff2(浏览器按需加载)(SVG 内嵌字形)
FOUC 风险CSS 未加载前有闪烁风险(SVG 自包含)
按需特性需要全局加载 CSS天然按需——无公式页面零开销

功能完整性

维度KaTeXMathJax3
LaTeX 覆盖度大部分常用命令更全面,支持更多扩展
交换图/XyJax不支持支持(通过 XyJax-v3)
\ce{} 化学需要额外扩展内置支持
\cancel/\xcancel支持支持
自定义宏支持支持(更灵活)
可访问性MathML 输出MathML + SVG

依赖体积(npm 包大小)

KaTeXMathJax3 (markdown-it-mathjax3)
安装大小katex: ~3.5MB(含字体)markdown-it-mathjax3@4: ~40MB(mathjax-full
客户端影响~1.2KB CSS(gzip)

MathJax 的 npm 安装体积更大,但这仅影响 node_modules,不影响客户端产物。

为什么 VitePress 选择 MathJax3?

  1. 零运行时依赖:SVG 内联在 HTML 中,无需 CSS/字体/JS
  2. 天然按需:无公式页面完全零开销
  3. 通用文档工具:大多数文档站不使用数学公式,MathJax 的 SVG 方案确保零影响
  4. math: false 默认关闭:仅需要时才安装和启用

Valaxy 的选择:KaTeX + MathJax 分离配置

Valaxy 通过两个独立配置分别控制两种引擎,语义清晰,对齐 VitePress:

ts
// valaxy.config.ts

// KaTeX(默认开启)
export default defineValaxyConfig({
  features: { katex: true },
})

// MathJax3(对齐 VitePress,零 CSS 依赖)
// 需先安装:pnpm add markdown-it-mathjax3
export default defineValaxyConfig({
  math: true,
})

// 禁用所有数学渲染
export default defineValaxyConfig({
  features: { katex: false },
})
  • features.katex — 控制 KaTeX(Valaxy 原有配置,保持不变)
  • math — 控制 MathJax(对齐 VitePress markdown.math
  • 两者互斥:启用 math 时 KaTeX 自动禁用

KaTeX 加载策略评估

前提

当选择 KaTeX 引擎时,首页首屏不需要渲染数学公式,但 katex.min.css(~25KB)及字体文件会在所有页面全局加载。评估是否应改为按需加载。

方案对比

方案 A:全局条件加载(当前方案)

virtual/styles.ts 中,当 math engine 为 katex 时全局引入 katex.min.css + katex.scss

方案 B:按需加载

从全局样式移除 KaTeX CSS,通过 node 端正则检测文章内容中的数学公式语法、客户端 DOM 检测 .katex 元素,按需动态 import() CSS。

对比

维度全局加载按需加载
首屏性能~1.2KB gzip CSS无数学页面零开销
实现复杂度一行 importnode 检测 + composable + DOM 检测,分布 5+ 文件
可靠性不可能遗漏依赖正则和 DOM 检测的完备性
FOUC 风险列表页 DOM 检测路径存在短暂 FOUC
SSG 友好度完美文章详情页 OK;列表页 onMounted 路径不参与 SSR
维护成本中——多处代码联动,正则需持续维护

按需加载方案的具体问题

1. 正则检测误判与遗漏(严重)

ts
const hasInlineMath = /(?<![\\$])\$(?!\$)(?!\s).+?(?<!\s)\$(?!\$)/s.test(content)
const hasBlockMath = /\$\$[\s\S]+?\$\$/.test(content)
  • 误判:代码块中的 $(如 $HOME)、金额 $100 会被误匹配
  • 遗漏\( ... \)\[ ... \]\begin{equation} 等 LaTeX 语法未检测

2. 列表页摘要的 FOUC(中等)

文章列表页通过 v-html 渲染含 KaTeX 的 excerpt。DOM 检测在 onMounted 后执行,CSS 动态 import() 存在加载延迟,导致公式短暂以无样式原始文本呈现。

3. SSR 模块级状态(中等)

loaded 变量为模块级别,SSR 中跨请求共享,可能导致部分页面的 CSS 未被 Vite 正确收集。

4. 覆盖范围不完整

列表页、摘要组件(YunPostCardPressArticleCard)不经过 ValaxyMd.vue,需要额外的全局 DOM 检测兜底。

关键认知

KaTeX CSS 的实际成本被高估了:

  • katex.min.css 原始 ~25KB,但 gzip 后仅 ~1.2KB(大量重复的 @font-face 声明压缩率极高)
  • 字体文件由浏览器天然按需加载——只有页面实际渲染了对应 @font-face 的元素才会下载字体,不使用 KaTeX 的页面不会下载任何字体文件
  • 全局加载的真实代价仅为 1.2KB gzip CSS,无额外网络请求

KaTeX 加载结论

KaTeX 引擎保持全局条件加载,理由:

  1. 1.2KB 的 CSS 开销对首屏性能影响可忽略不计
  2. 按需加载引入的工程复杂度、FOUC 风险、正则维护成本远超其收益
  3. 博客场景下技术文章普遍使用数学公式,按需加载的实际收益有限
  4. SSG 场景下 CSS 内联到 HTML 中,不存在额外请求

如果需要零 CSS 开销,推荐切换到 MathJax math: true,从架构层面彻底消除外部 CSS/字体依赖。


总结

KaTeXMathJax3
适用场景简单公式、追求极速渲染复杂 LaTeX、需要零客户端依赖
推荐人群大多数博客作者(默认)学术写作、重度数学用户
客户端开销~1.2KB CSS
配置features: { katex: true }math: true

To Be Continued.