这阵子在做某单位内部知识库迁移的时候,被WordPress挂公式这个问题折腾得不轻。项目背景很简单:要把一批产品手册和技术文档迁到新平台上,但所有终端都必须跑在国产化替代的环境里。一开始以为WordPress本身跨平台,迁移应该不复杂,结果一测公式编辑就傻眼了——要么编辑器按钮点了没反应,要么页面渲染出来一堆LaTeX源码,要么干脆特殊符号变成乱码。前后折腾了两周,把渲染引擎、插件机制、安全策略、浏览器适配整个链路都过了一遍,才算是完全调通。
这篇文章不打算做通用教程搬运,就想站在实操角度,把这次在国产化替代(信创)环境下让WordPress公式编辑正常工作的完整思路、选型对比、配置细节和踩坑记录整理出来。如果你也在做类似的知识库、在线文档或教育平台迁移,这篇内容可以直接帮你少走弯路。
1. 项目概述与需求拆解:国产化替代环境下WordPress公式编辑到底难在哪
先说清楚国产化替代环境(信创环境)到底是个什么情况,它不是一个单一产品,而是一整套生态标准。落到终端用户设备上,主要涉及三类硬性约束:
- CPU架构不一致:常见的有ARM架构(比如某国产芯片)、MIPS架构(比如某国产龙芯处理器),还有少数x86架构的加固设备。
- 操作系统非Windows:多为基于Linux内核的国产操作系统,内核版本和图形库裁剪程度不一。
- 浏览器内核版本偏旧或定制:为了安全管控,很多单位强制使用国产安全浏览器,这些浏览器虽然大多基于Chromium,但内核版本往往停留在某个保守的版本上,而且会默认开启一系列安全拦截策略。
这三个变量叠加起来,对WordPress这种老牌PHP应用来说,问题不大。真正卡脖子的地方是公式编辑这条技术链——它依赖大量前端JavaScript做排版和渲染,而这恰恰是版本兼容性最敏感的区域。
1.1 公式编辑场景的核心需求拆解
不同的项目对“公式编辑”的要求其实差别很大。这次项目里的需求是物理和数学类文档,需要支持分式、根号、求和符号、上下标、矩阵,偶尔还要处理一些特殊符号。具体拆解下来有以下几点:
- 编辑体验:用户在后台编辑器中点击按钮插入公式,不能要求他们手写LaTeX(虽然我本人习惯手写,但实际用户是编辑和工程师,不会)。
- 前端展示:发布后,在前台页面公式要平滑渲染,在国产浏览器里不能有乱码、错位、加载失败。
- 数据可迁移:公式内容不能存成base64图片,否则后期改一个系数都要重新编辑,必须存可检索的文本格式(LaTeX或MathML)。
- 离线可用:单位内网环境很多是不能访问公网CDN的,前端渲染脚本必须本地化部署。
1.2 产业链条上的兼容性风险点
公式编辑从插入到最终展示,经过的环节比想象中多:
| 环节 | 涉及技术点 | 国产化环境下的风险 |
|---|---|---|
| 编辑器触发 | TinyMCE / Gutenberg 按钮,JS弹窗 | 安全浏览器禁用弹窗或拦截内联脚本 |
| 公式转换 | JavaScript解析LaTeX语法 | 内核版本低,解析器报错中止 |
| 渲染输出 | MathJax / KaTeX 排版引擎 | 字体加载失败,SVG计算异常 |
| 数据存储 | MySQL数据库字段 | 字符集不是utf8mb4导致特殊符号丢失 |
| 请求安全 | WAF、Nginx规则、CSP策略 | 拦截含特殊字符的POST请求或内联脚本执行 |
任何一个环节出问题,表现出来的都是“公式挂了”。所以排查时切忌只盯着渲染脚本,必须从输入到存储到输出整条链路去排查。
2. 方案选型:MathJax与KaTeX在国产化替代中的兼容性较量
公式渲染引擎市面上主流就两个半:MathJax、KaTeX,还有半个是各家编辑器自带的简易渲染。这次我把MathJax和KaTeX都完整地试了一遍,结论可能和大多数技术博客推荐的不太一样。
2.1 MathJax的兼容性优势与性能代价
MathJax是目前兼容性最强的数学排版引擎,没有之一。它的原理是通过JavaScript扫描页面DOM,找到LaTeX或MathML标记,然后用内部排版引擎计算并渲染成SVG或HTML+CSS输出。
兼容性逻辑:MathJax v3要求浏览器支持ES6,但如果内核太旧,可以退回到v2.x版本。v2.x虽然速度慢,但对老内核的容忍度高得多。我们在某国产浏览器(内核版本较低)上测试,v3的tex-svg.js直接无法执行,退回v2的tex-chtml.js就一切正常。这是其他渲染引擎做不到的“信用背书”。
渲染模式选择:MathJax支持HTML-CSS(原生DOM操作),也支持SVG。在国产化环境下,我用SVG输出的比例高很多。原因很直接:HTML-CSS模式依赖浏览器的字体排版引擎,在部分国产Linux系统上字体缺失或字体配置不当,渲染结果会出现错位;而SVG本质上是一段矢量图代码,不受字体环境影响,一致性极高。
性能代价:SVG渲染是出了名的慢,尤其当一个页面有几十条公式时,CPU计算量很大。在ARM架构的低功耗终端上,可能出现滚动卡顿。这个问题的解法后面在实操部分会专门讲到。
2.2 KaTeX的速度幻觉与硬伤
KaTeX一直被宣传为“性能比MathJax快一个数量级”,这句话在标准x86环境、新版Chrome下确实成立。但在国产化替代场景里,KaTeX有几个硬伤:
- 对内核版本极不友好:KaTeX的代码大量使用现代JavaScript语法(class字段、可选链等),在旧内核上连解析阶段就会报错。除非你能保证所有终端浏览器都升级到较新的内核,否则不要选它。
- 依赖字体资源:KaTeX需要加载一系列专属字体文件(woff2),这些字体在国内CDN资源很少,离线部署必须全套下载。一旦某个字体文件缺失,整体渲染静默失败,前台就是一片空白。
- 容错性差:数学公式写法不标准时,MathJax会渲染出一个近似结果,KaTeX则直接抛异常,终端用户看到的是未渲染源码。
2.3 本次选型的最终决策
最终没有用KaTeX,也没用MathJax v3,而是选了MathJax v2.7.9 的 tex-chtml 版本,具体理由如下:
- 兼容性好,能在内核版本较低的国产安全浏览器中稳定执行。
- 支持SVG渲染,能规避字体环境差异。
- 生态成熟,网上几乎所有的配置问题都能搜到解决方案。
提示:如果你所在环境的内核版本较新(比如基于Chromium 85以上),直接用MathJax v3完全没问题。选型唯一原则是先摸清终端浏览器内核版本,再做决定。
3. 实操过程:在国产服务器上从零搭建可用公式编辑环境
下面进入正题,按照我实际操作的顺序来,从环境准备到最终验证。
3.1 环境假设与基础架构准备
这里用一个虚构项目来演示:某单位的知识库系统,名为“模拟项目X”。服务器环境如下:
- 服务器:ARM架构,操作系统为某国产Linux发行版
- Web服务:Nginx + PHP 7.4
- 数据库:MySQL 5.7
- WordPress版本:6.x
- 终端浏览器:某安全浏览器(兼容模式,内核版本约 Chromium 70 左右)
WordPress本身的安装不多说,重点强调两个基础准备:
第一,PHP的mbstring和gd扩展必须安装,否则后续处理特殊字符和图片会出问题。在国产OS上,一般用系统的包管理器安装,比如执行yum install php-mbstring php-gd后重启PHP服务。
第二,确认Nginx能正常处理.woff、.woff2、.js、.svg等资源请求。默认配置里这些文件的MIME类型可能缺失,导致浏览器拒绝了字体或脚本加载。在Nginx的http块中加入:
types { font/woff2 woff2; font/woff woff; image/svg+xml svg; application/javascript js; }这一步不提前做,后面MathJax的字体资源加载会一直404,而且排查起来极其隐蔽。
3.2 安装公式插件并实现MathJax离线化
WordPress上常见的公式插件有好几个,比如GMathType、WP QuickLaTeX等。但要注意,这些插件默认都是从公共CDN(如cdnjs、jsdelivr)加载MathJax脚本的。在国产化内网环境里,终端用户访问不到公网,必须将脚本本地化。
我这边没用现成的完整插件,而是直接写了一个轻量级的功能模块,这样可控性最强。
第一步,在主题目录下新增一个文件mathjax-loader.php,核心代码如下:
<?php // 在页面头部输出MathJax配置 function xm_mathjax_head() { ?> <script type="text/x-mathjax-config"> MathJax.Hub.Config({ showMathMenu: false, messageStyle: "none", jax: ["input/TeX","output/HTML-CSS"], tex2jax: { inlineMath: [["$","$"],["\\(","\\)"]], displayMath: [["$$","$$"],["\\[","\\]"]], processEscapes: true }, "HTML-CSS": { availableFonts: [], imageFont: null, preferredFont: "SVG", webFont: "Neo-Extra" }, SVG: { useFontCache: true, scale: 100 } }); </script> <script type="text/javascript" src="/wp-content/themes/your-theme/js/MathJax-2.7.9/MathJax.js?config=TeX-AMS-MML_SVG"></script> <?php } add_action('wp_head', 'xm_mathjax_head');这里有一个非常关键的细节,注意"HTML-CSS"配置里我把preferredFont设成了"SVG",同时在SVG配置里启用了字体缓存。在国产化环境里,这能最大限度避免字体缺失问题。useFontCache开启后,MathJax会把常用的SVG路径缓存到浏览器本地,同一个页面第二次访问时渲染速度会有明显提升。
第二步,将MathJax 2.7.9完整包上传到主题目录下的js/MathJax-2.7.9/文件夹内。注意这里不能只上传精简版,必须包含fonts目录和extensions目录,否则复杂公式会渲染失败。
第三步,在前台的functions.php中注册脚本依赖:
function xm_enqueue_mathjax() { if (is_singular() || is_home()) { wp_enqueue_script('xm-mathjax', get_template_directory_uri() . '/js/MathJax-2.7.9/MathJax.js', array(), '2.7.9', true); } } add_action('wp_enqueue_scripts', 'xm_mathjax');这里用了wp_enqueue_script,是因为WordPress本身有脚本依赖管理,避免和插件冲突。
3.3 编辑器按钮的兼容性处理
要让用户在写文章时能可视化地插入公式,需要给后台编辑器加一个公式输入按钮。最常见的方式是TinyMCE插件机制,但国产浏览器对TinyMCE 4.x和5.x的弹窗支持有差异。
我采用了更稳妥的方案:不用TinyMCE自带的弹窗,而是自己写一个基于原生JavaScript的模态框,用jQuery暴露全局接口。
核心机制如下:
- 在文章编辑页的HTML头部输出一个隐藏的
<div>,里面放一个<textarea>用于输入LaTeX公式,以及一个“插入”按钮。 - 点击编辑器工具栏的“Σ”图标时,用
jQuery将当前选中文本填入<textarea>,然后显示模态框。 - 用户确认后,通过
wp.media(文章编辑器的编辑器实例)将\(...\)或$...$包裹的LaTeX文本插入到光标位置。
这段关键逻辑如下:
(function($) { $(document).ready(function() { $('#btn-formula').on('click', function() { var selected = ''; if (window.tinymce) { var ed = tinymce.activeEditor; if (ed) { selected = ed.selection.getContent(); } } $('#latex-input').val(selected); $('#math-modal').show(); }); $('#insert-formula').on('click', function() { var latex = $('#latex-input').val(); if (!latex.trim()) { return; } var wrapped = '$$' + latex + '$$'; if (window.tinymce) { var ed = tinymce.activeEditor; ed.insertContent(wrapped); } else { var ed = wp.data.select('core/editor'); var current = ed.getCurrentContent(); wp.data.dispatch('core/editor').replaceBlocks([], wp.blocks.createBlock('core/paragraph', { content: current + wrapped })); } $('#math-modal').hide(); }); }); })(jQuery);这样处理的优势在于:不依赖TinyMCE本身的弹窗机制,避开了一些国产浏览器对“模拟模态框”或“iframe弹窗”的拦截策略,稳定性和通用性都更好。
3.4 Nginx安全策略与内网CSP适配
国产化环境一般会部署Nginx反向代理,并开启严格的安全头。其中最影响公式渲染的是Content-Security-Policy(CSP)。如果CSP里限制了script-src和style-src,MathJax动态加载脚本会直接被拦截,前台公式区域一片空白,但页面其他部分正常。这个坑非常典型。
在Nginx配置中,至少要确保以下几点:
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; img-src 'self' data:;";解释一下为什么这么写:
script-src需要'unsafe-inline',因为MathJax的配置脚本是通过页面内联<script>输出的,而MathJax内部运行时也需要动态创建脚本。style-src需要'unsafe-inline',因为MathJax渲染SVG时会动态注入<style>标签到DOM中。font-src和img-src加入data:,是允许MathJax以base64形式加载字体或图片资源。
如果企业安全策略不允许unsafe-inline,那就需要将MathJax配置和脚本都外链到独立JS文件,并在CSP中通过nonce或hash来精确放行。但这会显著增加实施复杂度,一般只有等保三级以上的严格环境才需要这么处理。
4. 核心细节解析:公式数据存储、渲染输出与字符集陷阱
公式能顺利渲染不代表就万事大吉,数据层同样有一堆暗坑,尤其是和国产化环境里的数据库运维习惯结合起来后。
4.1 LaTeX短代码是首选存储方式
公式在WordPress里常见有几种存取方式:
| 存储方式 | 优点 | 缺点 |
|---|---|---|
LaTeX短代码(如$$x^2$$) | 可读性强、可检索、体积小、不依赖渲染器 | 对解析器要求高,渲染前必须扫描转换 |
| MathML实体 | 标准化、语义化、兼容性强 | 可读性差,难以手工编辑,数据库体积膨胀 |
| Base64图片 | 所见即所得、渲染零依赖 | 不可检索、不可修改、迁移成本高 |
我的结论简单明确:一律用LaTeX短代码存储。原因不光是可维护性,更重要的是在国产化环境里,LaTeX文本作为UTF-8字符存储,对数据库字符集要求最低,且不依赖任何渲染器版本。未来就算换渲染引擎(从MathJax换到别的),公式内容仍然可以复用。
4.2 数据库字符合集必须为utf8mb4
这是一个极其隐蔽但破坏力极强的坑。多数国产系统的服务器在初始化数据库时,默认字符集可能是utf8(注意不是utf8mb4)。utf8字符集在MySQL里最多只支持三字节编码,而数学公式中常用的某些符号(如某些特殊箭头、扩展拉丁符号)是四字节的。一旦存入数据库,就会被截断成?或者直接报错。
正确做法是在部署WordPress之前,就把数据库和所有数据表的字符集统一为utf8mb4。如果系统已经上线,可以执行如下转换:
ALTER DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE wp_posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE wp_postmeta CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE wp_comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE wp_terms CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;转换后记得去wp-config.php中确认连接字符集设置:
define('DB_CHARSET', 'utf8mb4'); define('DB_COLLATE', '');不夸张地说,一半以上的“公式显示乱码”问题,排查到最后都落在这个字符集上,而不是渲染引擎上。
4.3 安全插件与WAF对公式内容传输的误伤
国产化环境普遍会在Nginx前面或后面加一套Web应用防火墙(WAF),用于拦截SQL注入和XSS攻击。这套东西对常规文本没问题,但对公式内容很不友好。
比如,用户插入了一个包含\left(\right)或\begin{matrix}之类的LaTeX代码,WAF在解析POST请求时,可能会把\、{、}、&、<、>等字符误判为攻击特征,直接返回403。
解决方案有两种:
- Disable URL Encode(关闭URL编码传递):把编辑器的POST请求设置为
application/json或multipart/form-data,有些WAF对这种格式的解析不够严格,可以绕过误报;但这不是长期方案,且对安全配置有依赖。 - WAF白名单:在WAF后台将
/wp-admin/post.php和/wp-admin/admin-ajax.php这两个接口路径加入白名单,或精确放行包含特定LaTeX模式的请求体。
注意:白名单行为在实际生产中需要和单位安全团队确认,不能自行修改生产环境的WAF策略而不留审计记录。
操作上,更为稳妥的方式是在Nginx层面对这两个接口做专门的请求体大小限制放宽,并关闭mod_security对该路径的检测:
location ~ ^/wp-admin/(post\.php|admin-ajax\.php) { client_max_body_size 20m; modsecurity off; }这样可以避免公式内容在到达WordPress之前就被拦截,同时不影响其他路径的正常安全检查。
5. 常见问题排查与避坑技巧实录
这部分我从实际测试中整理出的高频问题,基本覆盖了国内外社区里关于WordPress公式编辑在特殊环境下的绝大多数疑难杂症。
5.1 公式原样显示为LaTeX源码,没有任何渲染
这是最常见的问题,前台整篇文档都是$$...$$或\(...\),而页面其他内容正常。
排查步骤依次执行:
打开浏览器开发者工具,切到Network面板,刷新页面。查找是否存在
MathJax.js请求,并看它的返回状态。- 如果请求404,说明本地脚本路径不对,重新检查
wp_enqueue_script中的路径配置。 - 如果请求状态为
200,再看请求的Content-Type是否正确,有些国产浏览器会把.js文件当文本类型显示,导致脚本不执行。解决方式是修改Nginx的MIME配置,确保application/javascript类型被正确声明。
- 如果请求404,说明本地脚本路径不对,重新检查
检查控制台是否有JavaScript报错。常见报错是
MathJax is not defined或SyntaxError。前者通常是脚本加载顺序不对,MathJax没在页面DOM解析前加载完成;后者是PHP上传时文件被截断或编码格式错误,重新上传MathJax包并确认不要使用Windows记事本默认的UTF-8 BOM格式保存。检查CSP。如果控制台出现
Refused to load the script ... because it violates the following Content Security Policy directive,直接按上文方法调整Nginx的CSP配置。
5.2 编辑器中点“Σ”按钮无反应或弹窗白屏
这个问题主要出现在后台编辑页面。我调试时发现,多半是WordPress后台加载的jQuery和TinyMCE在旧内核下加载顺序冲突。
- 提供的第一种解法:在
functions.php中将需要加载的脚本注册到wp_enqueue_editor的优先级之前,并用jQuery的document.ready包裹全部逻辑。 - 提供的第二种解法:如果仍然无反应,检查浏览器是否默认启用了“极速模式”和“兼容模式”的差异。在“兼容模式”(可能就是IE内核)下,很多ES6语法直接不支持。切换到“极速模式”(Chromium内核)后问题就会消失。
另外,有些安全浏览器默认会拦截页面里的“非用户手势触发的弹窗”。也就是说,代码中如果在页面加载后自动window.open,会被拦截;而用户点击按钮后触发的弹窗则不会被拦截。很多国产浏览器的拦截策略比Chrome更激进,所以尽量用DOM模态框(在页面内显示div层)而不是浏览器原生的window.open弹窗。
5.3 公式显示为问号或乱码
优先级最高的排查点就是数据库字符集,按上文的方法检查并转换。
另外注意PHP连接MySQL时的字符集设置,如果wp-config.php中的DB_CHARSET还是utf8,而数据库已经是utf8mb4,可能导致连接层转换异常。把DB_CHARSET改为utf8mb4后立即生效,不需要重启服务,刷新页面即可验证。
还有一种特殊情况:个别公式符号在Linux系统的中文字体包里没有覆盖,导致SVG渲染时找不到对应字形。这种属于环境字体问题,可以在服务器上安装fonts-noto-cjk包(某国产Linux的软件仓库里有)来补充字体覆盖范围。
5.4 整个页面加载缓慢,尤其是有大量公式时
根源是MathJax渲染是高CPU密集型操作,而在ARM或MIPS架构的终端上,JavaScript执行效率本就比x86低不少,一个页面几十条公式可能卡个好几秒。
亲测有效的优化路径:
- 开启WordPress缓存插件(如WP Super Cache或W3 Total Cache)。让人欣慰的是,把页面缓存成静态HTML后,MathJax的计算结果会保存在HTTP缓存中,第二次访问时直接按静态页面输出,公式渲染几乎零耗时。
- 对MathJax进行延迟加载。只在页面内容包含公式时才去加载脚本,通过判断
$post->post_content中是否含有$或\(即可:
function xm_should_load_mathjax($content) { if (preg_match('/\$\$|\\\\([\[\(])/', $content)) { return true; } return false; }- 关闭MathJax的菜单显示。默认情况下右键点击公式会弹出一个“MathJax”菜单,包含了缩放、复制等操作,但对普通用户毫无意义,也增加了JavaScript负担。配置中已经写了
showMathMenu: false,这个收益在低配置终端上非常明显。
5.5 公式编号和交叉引用失效
部分文档需要给公式自动添加编号(如(1)、(2)),这个功能原生MathJax是不支持的,需要额外接入autonumber扩展。
但在国产化环境里,开启这个扩展后有些特殊符号会被误处理。我的建议是:如果只是静态文档迁移,不要在页面上依赖自动编号,直接把编号写进公式文本里,例如$$ x^2 \quad (1) $$。这样既避免了扩展兼容问题,也保留了手动控制的灵活性,全文排版更可控。
最后分享一个小技巧
如果单位内部对安全要求极高,既不能上CDN,又不能放宽CSP的unsafe-inline,还有一线生机。我前两次调这类环境时,用的是一种物理隔离的思路:把MathJax的渲染脚本打包成一个独立的静态HTML文件,放在Nginx的一个单独目录下,然后用<iframe>方式嵌到文章页。iframe有自己的CSP上下文,不受主页面严格策略限制,公式在其中渲染完成后,再通过postMessage把高度回传给父页面。
这种做法虽然实现稍微复杂些,但好处是彻底避开了主文档的CSP限制,同时在内网环境下能长期稳定运行。尤其适合类似涉密级别或强审计要求的单位环境。
关于WordPress在国产化替代环境下的公式编辑,我能分享的实操经验和排查路径大概就是这些。兼容性问题没有银弹,一定要沿着“终端浏览器 → 前端脚本 → 传输层 → 数据层”这条链路逐级检查。只要关键节点的选型和配置合理,这套方案在绝大多数国产化项目里都能稳定落地。