转载

MathJax 瘦身记

说来话长

对于理工科背景的人而言,无论是 Web x.0 的时代,对应的网站都应该支持数学公式。所以,QQ 不支持数学公式,就没必要用 QQ;微信不支持数学公式,就没必要用微信;微博不支持数学公式,就没必要用微博……然而这一切都要归罪于 HTML 的发明者,理工科的专家发明的东西居然不支持数学公式……

事实上,HTML 并非对数学公式完全的不支持,利用 Javascript + CSS + 一组完备的数学字体,是完全可以直接以 HTML 标记的形式产生数学公式图形的。MS Word 与 TeX 之所以能够排版数学公式,也是因为它们有自己的『CSS』与数学字体。这个方案也许是 HTML 对数学公式提供支持的终极方案,也是最为复杂的方案,但是 MathJax 的开发者们成功的实现了这个方案。

之所以说基于『Javascript + CSS + 一组完备的数学字体』的 HTML 数学公式支持方案是最复杂的方案,这个问题,经常折腾 CSS 的人一定会明白的,那就是 CSS 面向各种浏览器的兼容性问题。所以 MathJax 非常复杂,因此它也非常膨胀。

可以下载最新的 MathJax 包看一下,

$ wget https://github.com/mathjax/MathJax/archive/master.zip  $ du -h ./master.zip 33M     master.zip  $ unzip master.zip  # 解包后得到 MathJax-master 目录  $ du -h -s MathJax-master 176M    MathJax-master

对于网站而言,176 MB 的大小不算什么,本来 MathJax 就是为网站上部署网页数学公式支持而开发的,适配各种网页浏览器。但总有些人无法接受这么大的软件包,比如我。

自从实验室服务器坏掉之后,我们的公共 wiki 就没法用了。虽然一部分数据是有备份的,但是再重新搭建一个服务器环境实在是太麻烦了,而且也一直缺乏专人维护。

为了解决这个小团体内部信息的分享问题,我想了个办法,让大家在各自的机器上部署 Nikola 。所谓 Nikola,就是一款静态网站生成器,这意味着实验室每个人都建立了自己的个人静态站点。平时要分享一些内容,只需用 markdown 或 reStructuredText 写出文档,然后用 nikola build 命令产生 HTML 文件。在 nginx 的帮助下,我们可以互相访问彼此的站点,并且也可以利用 rsync 将其他人的站点的内容同步到本地目录,这有点像 git 的工作方式。

Nikola 与 MathJax

为了让 Nikola 能够支持数学公式,我给它配置了 MathJax,即在 Nikola 的配置文件 conf.py 中写入以下内容:

MATHJAX_CONFIG = """ <script type="text/javascript" src="../MathJax/MathJax.js?config=TeX-AMS_HTML-full"> </script> <script type="text/x-mathjax-config">  MathJax.Hub.Config({   tex2jax: {    inlineMath: [ ['$','$'], ["///(","///)"] ],    displayMath: [ ['$$','$$'], ["///[","///]"] ],    processEscapes: true   },   TeX: {     Macros: {      RR: '{//bf R}',      bold: ['{//bf #1}', 1]     }   },   "HTML-CSS": { imageFont: null }  }); </script> """ 

MathJax 目录是放在我的 Nikola 根目录下的,我的 Nikola 根目录构成如下:

$ tree -L 1 rca rca ├── cache ├── conf.py ├── conf.pyc ├── files ├── galleries ├── images ├── listings ├── MathJax ├── mycss ├── output ├── posts ├── stories └── templates

其中,rca 就是我的 Nikola 根目录;posts 是放置 markdown 或 reStructuredText 源文件的目录。

在 rca 目录下,如果执行:

$ nikola build

那么,posts 目录中的 markdown 或 reStructuredText 源文件就会被转换为 HTML 文件。output 目录用于存放所生成的 HTML 文件。

我为 rca 这个站点配置了局域网内的 nginx 服务器,即在 /etc/nginx/nginx.conf 文件中增加以下内容:

 server {  listen 192.168.0.7;  server_name localhost;  access_log /var/log/nginx/localhost.access_log main;  error_log /var/log/nginx/localhost.error_log info;  root /home/garfileo/var/rca/output; } 

也就是说,output 是我的站点的根目录,而 MathJax 目录则在站点根目录之外。这时,output 目录中的 HTML 文件就无法加载 MathJax.js 文件了。

如果将 MathJax 目录至于 output 目录中,可以解决上述问题,但是 Nikola 的官方文档不建议用户在 output 目录中放文件,因为 Nikola 的某些操作会将 output 目录中的所有内容清除。如果需要将一些文件放到 output 目录,那么 Nikola 支持文件的复制,亦即在 conf.py 文件中增加:

FILES_FOLDERS = {'MathJax': 'MathJax'}

这样,每次执行 nikola build 命令时, nikola 便会扫描 FILES_FOLDERS 目录中的文件有没有变化,如果有变化就会发生变化的文件复制到 output 目录中。

现在,问题终于出现了。因为 MathJax 解包后的大小是 176 MB,在我的机器上,它被 nikola 扫描一次就需要用 25 秒,而未部署 MathJax 时 nikola 扫描文件只需 3 秒。所以,想随时查看文档的输出结果就变得不现实了。不知道其他静态站点生成工具部署 MathJax 是不是也存在这个问题。

个人只需要小而精的东西

后来,我试着在 nginx.conf 中为 MathJax 目录单独配置一个服务器,然后在 Nikola 的配置文件中使用 MathJax 的 URL 来寻找 MathJax.js 文件,这样做是可以的,但是 MathJax.js 中所使用的字体路径是相对路径而非绝对路径,而这个相对路径是 Nikola 的。结果就是,MathJax.js 找不到字体,于是它只能用浏览器默认字体,虽然能显示数学公式,但是只是丑陋不堪的显示。

hack 一下 MathJax.js,让它能根据 MathJax 的 URL 找到字体目录,这样兴许能解决问题。然而,虽然 JavaScript 是无处不在的,但是我实在不愿意 hack 它。之所以不愿意,是因为我不会,而且还不愿意学。

还有一条路,就是对 MathJax 进行精简。因为它支持的浏览器那么多,而我却只是 firefox 的死忠,所以肯定可以去掉很多文件。分析了一下 MathJax 的目录,发现仅仅位图字体就占用了 116 MB,触目惊心啊!

对我来说,瘦身应该是最简单的方法。至于跨服务器调用 MathJax 的事,应该交给愿意做而且愿意分享的人来做。

MathJax-grunt-cleaner

在 google 上面用了 MathJax trim , MathJax slim 之类的关键词,很快就发现了这个: https://github.com/mathjax/MathJax-grunt-cleaner

MathJax-grunt-cleaner 的 README 看上去挺简单的,也许它的开发者认为凡是使用这个东西的人必然是了解 node.js 与 grunt 的。可惜,现在他偏偏碰到了我这种在他眼里可能什么都不会但是依然坚持要用他的东西的人,所以下面的 MathJax-grunt-cleaner 的用法可能非常笨拙且不对路。

我的做法是先下载 MathJax-grunt-cleaner:

$ git clone https://github.com/mathjax/MathJax-grunt-cleaner.git

然后将其中的 package.json 与 Gruntfile.js 文件复制到 MathJax-master 目录(如果不知道这个是什么目录的话,抓紧看本文的起始部分)。在 MathJax-master 目录中使用 npm 命令安装 package.json 中所记录的依赖包:

$ npm install

npm 是 node.js 的包管理命令。这也意味着,要使用 MathJax-grunt-cleaner,你需要 node.js……这对于 JS 程序猿而言,不是什么问题吧?

然后就开始修改 Gruntfile.js 文件中的 template 部分,即:

  grunt.registerTask("template", [     ...     ...   ]};

template 默认是干掉 MathJax 的一切,所以你想留下哪些内容,就注释掉相应的 clean 语句,然后执行 grunt 命令对 MathJax 进行残酷的阉割:

$ grunt template

如果将:

  grunt.registerTask("template"

换为:

  grunt.registerTask("default"

那么只需执行:

$ grunt

即可完成清理过程。

下面是我定制的 default 部分,仅供参考:

  grunt.registerTask("default", [   // **Notes** on the template. When instructions say "Pick one", this means commenting out one item (so that it"s not cleaned).   //   //      Early choices.   "clean:unpacked",   //"clean:packed", // pick one -- packed for production, unpacked for development.   //"clean:allConfigs", // if you do not need any combined configuration files.   //      Fonts. Pick at least one! Check notes above on configurations.   "clean:fontAsana",   "clean:fontGyrePagella",   "clean:fontGyreTermes",   "clean:fontLatinModern",   "clean:fontNeoEuler",   "clean:fontStix",   "clean:fontStixWeb",   //"clean:fontTeX",   //      Font formats. Pick at least one (unless you use SVG output; then clean all).   //"clean:dropFonts", // when using SVG output   "clean:eot",   //"clean:otf",   "clean:png",   "clean:svg",   "clean:woff",   //      Input. Pick at least one.   "clean:asciimathInput",   "clean:mathmlInput",   //"clean:texInput",   //       Output   //"clean:htmlCssOutput",   "clean:mathmlOutput",   "clean:svgOutput",   // Extensions. You probably want to leave the set matching your choices.   "clean:extensionsAsciimath",   "clean:extensionsMathml",   //"clean:extensionsTeX",   "clean:extensionHtmlCss",   // Other items   "clean:locales",   "clean:miscConfig",   //        "clean:miscExtensions", // you probably want that   "clean:images",   "clean:notcode" ]); 

清理过程结束后,从 MathJax-master 目录中移走 Gruntfile.js, package.json, 以及 npm 安装的 js 包目录 node_modules,可以找个地方把它们保存起来,以备日后使用。

精简后的 MathJax 大小为:

$ du -h -s MathJax-master 2.3M    MathJax-master

从 176 MB 到 2.3 MB,我阉割的也许是一部波澜壮阔的浏览器发展历史。

正文到此结束
Loading...