☰
@gitbook/react-math:KaTeX 优先、MathJax 兜底的 React 数学公式渲染组件
2026/10/1 2:32:26 网站建设 项目流程
  • 前端
  • 后端
  • 知识管理

【免费下载链接】gitbook

The open source frontend for GitBook doc sites

项目地址:https://gitcode.com/gh_mirrors/gi/gitbook
点击查看免费下载

导读

@gitbook/react-math是 GitBook 开源前端(gitbook 仓库)中负责渲染数学公式的 React 组件包。它以「KaTeX 优先、MathJax 兜底」的双引擎策略,在服务端优先用 KaTeX 快速产出 HTML,一旦解析失败再切换为客户端懒加载的 MathJax 进行排版,从而兼顾渲染速度、公式兼容性与包体积。读完本文,你将掌握该组件的完整 Props 契约、双引擎回退链路的底层实现、静态资源(MathJax 运行时)的发布方式,以及如何在你的 React / Next.js 项目中集成数学公式渲染能力。

一、组件定位与渲染策略

官方 README 对该包的定义只有一句话,却精准概括了它的全部设计意图:

React component to render a Math formula. It uses KaTeX when possible and fallbacks to MathJaX if needed.

翻译过来即:一个渲染数学公式的 React 组件;能使用 KaTeX 时优先使用 KaTeX,需要时回退到 MathJax(MathJaX)。

这句话背后是 GitBook 文档站渲染数学内容时的两个现实矛盾:

  1. KaTeX 快但覆盖有限:KaTeX 渲染性能优异、输出为纯 HTML+MathML,但它只支持 TeX 语法的一个子集,遇到不支持的宏或复杂构造会直接抛错;
  2. MathJax 全但重:MathJax 3 支持几乎完整的 TeX/LaTeX 语法,但体积大、需要额外加载运行时脚本,无法在服务端低成本输出最终样式。

@gitbook/react-math的做法是把两者的优势串成一条降级链路:先尝试 KaTeX(服务端同步渲染,几乎零额外请求);失败后再把公式交给懒加载的 MathJax(客户端异步排版)。对应的组件树结构如下,完整实现见 src/MathFormula.tsx:

MathFormula └── KaTeX (服务端组件,React.lazy 加载 KaTeXCSS) └── 渲染成功 → 输出 KaTeX HTML └── 渲染失败 → fallback └── MathJaXLazy (React.lazy + Suspense) └── MathJaXFormula (客户端组件,动态注入 tex-chtml.js)

从 package.json 可以看到该包的核心依赖与工程形态:

  • 运行时依赖katex@^0.16.25、mathjax@^3.2.2、object-hash@^3.0.0,其中mathjax在源码中主要用于bin/gitbook-math.js定位资源路径(见后文第六节);
  • 以react作为 peerDependency,与任意 React 版本解耦;
  • 提供gitbook-math二进制命令("bin": { "gitbook-math": "./bin/gitbook-math.js" }),用于把 MathJax 静态资源拷贝到站点公开目录;
  • 构建使用tsdown(见 tsdown.config.ts),sideEffects: false声明便于打包器 tree-shaking。

二、MathFormula 的 Props 契约

组件唯一的对外入口是MathFormula,其 Props 定义位于 src/MathFormula.tsx:

export interface MathFormulaProps { /** 要渲染的公式(TeX / LaTeX 语法字符串) */ formula: string; /** 是否以内联(inline)方式渲染,默认 false 表示块级展示 */ inline?: boolean; /** 附加到渲染结果上的额外 class 名 */ className?: string; /** 在 MathJax / KaTeX 加载期间展示的兜底内容(ReactNode) */ fallback?: React.ReactNode; /** 加载 MathJax 静态资源(tex-chtml.js)的基础 URL */ assetsUrl: string; }

各字段的行为要点如下:

字段必填默认值说明
formula是无待渲染的 TeX 公式字符串,如E = mc^2
inline否falsetrue时输出<span>内联公式;false时输出<div>块级公式
className否无透传给最终渲染容器的 class
fallback否公式原文默认兜底为「把formula原样包进span/div」,保证极端情况下内容仍可见
assetsUrl是无MathJax 资源的基础路径,用于拼接${assetsUrl}/mathjax@3.2.2/tex-chtml.js

组件内部默认的 fallback 实现值得注意(见 src/MathFormula.tsx):当调用方不传fallback时,它会用React.createElement生成一个与目标渲染形态(inline 决定span还是div)一致的元素,把formula字符串原样作为子节点输出。这意味着即使两个渲染引擎都不可用,读者依然能看到公式的 TeX 源码而非一片空白——这是文档场景下非常务实的降级策略。

import { MathFormula } from '@gitbook/react-math'; // 块级公式(默认) <MathFormula formula="\int_{-\infty}^{+\infty} e^{-x^2} dx = \sqrt{\pi}" assetsUrl="https://cdn.example.com/math" /> // 内联公式 + 自定义加载占位 <MathFormula formula="E = mc^2" inline className="my-inline-math" fallback={<span>正在排版公式…</span>} assetsUrl="https://cdn.example.com/math" />

2.1 assetsUrl 与资源版本号的对应关系

MathFormula内部会把assetsUrl与写死的版本号拼接成 MathJax 脚本地址(src/MathFormula.tsx):

const mathJaxUrl = `${assetsUrl}/mathjax@3.2.2/tex-chtml.js`;

这一路径格式与gitbook-math二进制拷贝出来的目录结构严格对应:资源会输出到public/math/mathjax@3.2.2/这样的目录(详见第六节),因此只要把assetsUrl指向资源部署后的公开根路径(例如https://cdn.example.com/math或站内/math),脚本地址即可正确解析。

三、KaTeX 优先:服务端同步渲染路径

KaTeX组件在 src/KaTeX.tsx 中实现,注释明确标注它是Server component(服务端组件),目的是在服务端把 KaTeX 公式编译成 HTML 字符串,避免把 KaTeX 核心打进客户端主包。

const html = katex.renderToString(formula, { displayMode: !inline, // 块级公式时开启 display 模式 output: 'htmlAndMathml', // 同时输出 HTML + MathML,兼顾视觉与可访问性 throwOnError: true, // 解析失败时抛出异常,触发回退链路 strict: false, // 关闭严格模式,宽容处理语法 }); const Tag = inline ? 'span' : 'div'; return ( <> <KaTeXCSS /> <Tag className={className} dangerouslySetInnerHTML={{ __html: html }} /> </> );

四个关键渲染选项各自的意义:

  • displayMode: !inline:KaTeX 的\displaystyle块级排版开关,与inlineProp 联动;
  • output: 'htmlAndMathml':同时生成 HTML(视觉渲染)与 MathML(辅助技术、语义检索),提升公式的可访问性与机器可读性;
  • throwOnError: true:这是整条降级链路的触发点——一旦 KaTeX 无法解析公式,就会抛错进入catch,从而把渲染权交给props.fallback(即 MathJax 分支)。注意这里特意没有用strict去掩盖错误,而是让错误「快速失败」以启动回退;
  • strict: false:放宽对 TeX 语法细节的检查,避免因一些历史遗留写法导致不必要的报错。

KaTeX组件外层还会渲染一个KaTeXCSS占位组件(src/KaTeXCSS.tsx),它通过React.lazy分包,并在渲染时立即import('katex/dist/katex.min.css')后给document.body添加katex-loadedclass,用于把 KaTeX 样式表从主 bundle 中拆出、按需加载。之所以不在 effect 里加载,是因为「需要尽快注入 CSS,避免首屏公式无样式闪烁」。

四、MathJax 兜底:客户端懒加载排版

当 KaTeX 抛错时,MathFormula把渲染权移交给MathJaXLazy(src/MathJaXLazy.tsx),其结构为:

<React.Suspense fallback={props.fallback}> <MathJaXFormula {...props} /> </React.Suspense>

即:用React.lazy按需拉取MathJaX模块,加载完成前展示调用方提供的fallback(默认是公式原文)。这保证 MathJax 的庞大运行时只有真正需要时才会被请求,不会拖累绝大多数 KaTeX 能搞定的页面。

真正的排版逻辑在客户端组件MathJaXFormula(src/MathJaX.tsx,文件顶部带'use client'指令)中,流程分为三步:

4.1 脚本加载:全局单例 + 缓存 Promise

React.use(loadMathJaxScript(mathJaxUrl)); // 挂起直到脚本就绪

loadMathJaxScript(src/MathJaX.tsx)用一个模块级变量mathJaxPromise缓存加载 Promise:同一页面多处公式只注入一次<script>,后续调用直接复用已完成的 Promise。加载前它还会预先写入全局window.MathJax配置:

window.MathJax = { tex: { inlineMath: [] }, // 不启用自动行内识别,公式由代码显式驱动 options: { enableMenu: false }, // 关闭 MathJax 右键菜单 startup: { elements: null, // 不自动扫描页面元素 typeset: false, // 不自动排版,完全由我们手动触发 }, };

这些配置的意义在于:MathJax 在文档站场景中不做任何自动扫描与自动排版,所有公式都由代码精确控制调用tex2chtml生成,避免与页面上其他文本内容发生意外匹配,也避免弹出干扰阅读的右键菜单。

4.2 手动排版:tex2chtml 生成 HTML

React.useEffect(() => { let cancelled = false; typeset(() => { if (cancelled) return; const domNode = MathJax.tex2chtml(formula, { display: !inline }); setHTML(domNode.outerHTML); }); return () => { cancelled = true; }; }, [inline, formula]);

typeset辅助函数(src/MathJaX.tsx)把tex2chtml的结果串进MathJax.startup.promise的链式调用中,确保排版发生在 MathJax 启动完成后;useEffect内的cancelled标记用于在公式变化或组件卸载时丢弃过期结果,避免状态错乱。

4.3 输出容器

const Component = inline ? 'span' : 'div'; return ( <Component ref={containerRef} className={className} aria-busy={!html ? true : undefined} dangerouslySetInnerHTML={{ __html: html }} /> );

渲染容器同样由inline决定是span还是div;aria-busy在 HTML 尚未生成时标记为忙碌,让屏幕阅读器等辅助技术感知「公式正在排版中」,体现了对可访问性的细致处理。

五、默认样式:隐藏加载态、统一字号

包内默认样式位于 css/default.css,由MathFormula直接import '../css/default.css'引入,包含两条关键规则:

/** Hide the KaTeX HTML output, while it's loading */ body:not(.katex-loaded) .katex-html { display: none; } /** Align the MathJax output with the font-size used by KaTeX */ mjx-container[jax="CHTML"] { font-size: 1.21em; }
  • 第一条与KaTeXCSS的katex-loadedclass 联动:在 KaTeX 样式表加载完成前隐藏.katex-html,防止公式以未排版(丑陋)的原始 HTML 形态闪现;
  • 第二条把 MathJax CHTML 输出的字号调整为 KaTeX 的 1.21em,让「KaTeX 渲染的公式」与「MathJax 兜底渲染的公式」在同一页面视觉上字号一致,保证混排时观感统一。

六、gitbook-math:拷贝 MathJax 静态资源

由于 MathJax 运行时是按需从assetsUrl拉取的,站点必须自行托管这些静态资源。@gitbook/react-math为此提供了gitbook-math命令行工具(bin/gitbook-math.js),用法为:

# 输出到默认目录 public/math npx gitbook-math # 指定输出目录(相对当前工作目录解析) npx gitbook-math ./public/assets/math

其核心逻辑:

  1. 通过import.meta.resolve('mathjax/package.json')定位本地安装的mathjax包路径,读取其package.json获取实际版本号;
  2. 递归创建输出目录(默认public/math,可用首个命令行参数覆盖);
  3. 把mathjax包内的es5目录整体拷贝到public/math/mathjax@<version>/。

也就是说,最终产物形如public/math/mathjax@3.2.2/tex-chtml.js,与MathFormula中${assetsUrl}/mathjax@3.2.2/tex-chtml.js的拼接逻辑一一对应——把assetsUrl指向该目录的公开根路径即可。该二进制自 v0.6.0 起随包发布(见 CHANGELOG.md:「Export binarygitbook-mathto copy assets to a directory」)。

七、在项目中集成的完整示例

下面给出一个可运行的集成示例,演示如何在 Next.js App Router(或任意支持 RSC 的 React 框架)页面中渲染混合公式:

// app/math-demo/page.tsx import { MathFormula } from '@gitbook/react-math'; export default function MathDemoPage() { return ( <article> <p> 质能方程:<MathFormula formula="E = mc^2" inline assetsUrl="/math" /> </p> <MathFormula formula="\sum_{k=1}^{n} k = \frac{n(n+1)}{2}" className="block-formula" assetsUrl="/math" /> <MathFormula formula="\def\unknowntex{}" // 故意使用 KaTeX 不支持的语法触发回退 assetsUrl="/math" fallback={<p>(公式排版失败,显示原文)</p>} /> </article> ); }

部署要点:

  1. 安装依赖:bun add @gitbook/react-math(或npm install @gitbook/react-math);
  2. 发布 MathJax 资源:npx gitbook-math,将生成的public/math目录部署到静态资源域名或站内路径;
  3. 将assetsUrl指向资源根路径:站内自托管时传/math,CDN 托管时传完整 URL(如https://cdn.example.com/math);
  4. MathFormula可在服务端组件中直接使用——KaTeX 分支本就是服务端渲染,MathJax 分支的MathJaX模块带'use client'指令且由React.lazy延迟加载,两者天然兼容 RSC 架构。

八、设计要点小结

  • 降级链路而非并行渲染:KaTeX 成功即终止,只有失败才引入 MathJax,绝大多数页面零额外脚本开销;
  • 可访问性贯穿始终:KaTeX 输出htmlAndMathml双格式,MathJax 容器带aria-busy状态;
  • 资源按需与全局复用:KaTeX 样式与 MathJax 运行时均懒加载,MathJax 脚本加载 Promise 全局缓存,同页多公式只请求一次;
  • 失败仍可见:默认 fallback 直接输出公式原文,任何引擎不可用时内容都不会消失。

延伸阅读

  • 组件实现总入口:双引擎降级链路的组装点
  • KaTeX 服务端渲染实现:renderToString与渲染选项详解
  • MathJax 客户端渲染实现:脚本加载、手动排版与可访问性处理
  • 默认样式:KaTeX 加载态隐藏与字号统一
  • 资源拷贝二进制:MathJax 静态资源发布工具
  • 包配置与发布信息、变更记录
  • 前端
  • 后端
  • 知识管理

【免费下载链接】gitbook

The open source frontend for GitBook doc sites

项目地址:https://gitcode.com/gh_mirrors/gi/gitbook
点击查看免费下载
上一篇:openEuler暑期2020任务详解:学生参与开源项目的绝佳机会
下一篇:PilotGo-plugin-grafana完整教程:5个步骤实现Grafana监控界面无缝集成

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

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

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

立即咨询