HTTP2 化に伴なって、サイト全体の最適化を行ないました

依存の整理

もはや jQuery なしでも簡単に書けそうなスクリプト部分から jQuery 依存を抜きました。また、JSDeferred を Promise で置き換えました。

script 要素の async / defer

script 要素については必要に応じて async や defer をつけるようにし、基本的に外部スクリプトでブロックする可能性を排除しました。

async は script 要素同士で独立している場合無条件につけられます (非シーケンシャル)。defer はページのDOMが構築されたあとに実行されるように遅延されます (シーケンシャル)

defer は DOMContentLoaded 直前にまとめて呼ばれるようです。

外部ライブラリを自分でホスト

外部ライブラリをCDN経由でロードしている部分がありましたが、TLS セッションの無駄遣いな気がするので、自分でホストするように変えました。自分のホストであれば HTTP2 のセッションが生きているので無駄な処理が減りそうです。

動的圧縮から静的圧縮に

h2o で compress: ON とすると accept-encoding に応じて gzip か br で動的に圧縮してクライアントに返却してくれます。

しかし殆ど変更されないライブラリみたいなものは動的にやるのがもったいないので、静的に圧縮するようにしました。特に brotli は圧縮処理が遅い感じなので意味がありそうです。

h2o 的には file.send-compressed: ON にしって、filename.js.gz と filename.js.brを置くようにします。

.gz と .br をつくるスクリプト

以下のようなスクリプトを作っておくと、指定したファイルの .gz と .br を作れて便利です。

静的に作っておけば、特に VPS のスペックが非力だと相対的に効果が高いはずです。

#!/bin/sh

for f in "$@"
do
        echo "$f"
        gzip --best < "$f"  > "$f".gz
        bro --quality 10 --input "$f" --output "$f".br
done

brotli について

h2o は brotli に対応していて、accept-encoding: br なときはオンデマンドも br を返すみたいです。

ただ、brotli は chrome に実装済みといっても flags で有効にしないとまだ使えないみたいです。

Firefox 45 は標準で accept-encoding: br を吐くようです。

ブログシステムでのキャッシュ

今まで表示のたびに DB からひいてきてテンプレート処理を行っていましたが、キャッシュするようにしました。もともとそこまで遅いわけではないのですが 、一応これも効果がありました。

ただ、キャッシュを入れるとキャッシュ無効化の処理のために考えることが大変増えます。今のところあまり筋の良い実装ではないのですが、一応動いています。

MathJax のサーバサイド化

[tech] サーバーサイド MathJax で数式表示を高速化する | Fri, Apr 8. 2016 - 氾濫原 別エントリにしました。

いろいろやった結果

キャッシュなしの状態でロードして、DOMContentLoaded まで 300ms を切るぐらいにできました。ただし onload までは2秒〜3秒かかっています。onload まで遅いのは画像のサイズが大きいというのと、あとは主にアドセンスのせいです。

同じ環境で www.google.co.jp のDOMContentLoaded を測ると 170ms ぐらいでしたので、要素数などを比較するといい線まできている気がします。

さらにできることは?

いわゆる minify をやっていないので、これをやる余地がまだあります。しかし結構面倒です。そしてたとえ削れても数kBなので、意味があるのか疑問を持っています。

  1. トップ
  2. tech
  3. サイトの最適化

このサイトでは数式を本文中に TeX 形式で書いて MathJax で処理させています。↓ こういうやつです。ベクターなので昨今の高解像度事情でもいい感じに綺麗に表示できます。

これはクライアントサイドで本文中の TeX フォーマットを探して全部 SVG とかに置き換えていくのですが、これがなかなか重い処理です。

特にスマートフォンの場合、数式がまともに表示されるまで5秒〜10秒ぐらいかかるので、それまでモヤモヤした感じになります。

さらにいえば、これは非同期で変換をかけているのでロード直後にリフローが頻発することになります。

ということで、これらの問題をなんとかしたいので、標題のようにサーバサイドで MathJax を使ってみるようにしてみました。

このサイトははてな記法で書いたエントリを保存時にHTMLに変換し、変換済みをDBにいれて、表示のときはそれを出しているだけです。なので、保存時に MathJax を通して本文中の数式を埋め込み SVG に変換するようにします。

HTML の断片をうけとって MathJax にかける HTTP サーバ

幸い MathJax-node という node.js 上で動かせる MathJax があったので、これをそのまま使い、http.Server でラップして、ブログシステムとは別に API サーバを立ち上げました。

#!/usr/bin/env node

var mjAPI = require("mathjax-node/lib/mj-page.js");


mjAPI.start();
mjAPI.config({
	tex2jax: {
		inlineMath: [["\\(","\\)"]],
		displayMath: [ ["$$", "$$"] ]
	},
	extensions: ["tex2jax.js"]
});

var http = require('http');
http.createServer(function (req, res) {
	var html = [];
	req.on('readable', function () {
		var chunk = req.read();
		console.log('readable');
		if (chunk) html.push(chunk.toString('utf8'));
	});
	req.on('end', function() {
		console.log('end');
		console.log('html', html);

		mjAPI.typeset({
			html: html.join(""),
			renderer: "SVG",
			inputs: ["TeX"],
			ex: 6,
			width: 40
		}, function (result) {
			console.log('typeset done');
			console.log(result);

			res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
			res.end(result.html);
		});
	});
}).listen(13370, '127.0.0.1');
console.log('Server running at http://127.0.0.1:13370/');

ブログシステムからは保存時にこのAPIを呼んでHTMLをフィルタしてDBに保存するようにしました。

ハマりどころ

動的な MathJax だと、ページのコンテナサイズから適切な幅を計算してくれるのですが、サーバサイドだとそれができませんので、適切な横幅を自分で指定する必要がありました。

スマートフォンでの閲覧を考えると、width: 40 ぐらいにして、念のため CSS で svg { max-width: 100% } とかを入れておくとよさそうです。width が大きいと、数式ごとのフォントサイズが変わりまくって大変うっとおしいです。

MathJax-node の速度

MathJax-node はなぜかものすごく遅いです。ライブラリロード済みでも10秒ぐらいかかります。そもそも起動も遅いです。オンデマンドでやるのは無理そうなレベルです。

MathJax の JS のロードを抑制

これに伴なって、MathJax を使わなくても良さそうな場合、JSのロードすらやめるようにしました。

所感

MathJax の JS は2段階ぐらいでロードされるので RTT が長いほど不利になります。サーバサイドでやってしまえばJSを転送する必要も実行する必要もなくなるのでエコです。おかげでロードが結構早くなりました。

しかもページロード直後から数式が完全な形で表示されるので、ページに数式がある場合、体感的な速度は数倍に感じます。

  1. トップ
  2. tech
  3. サーバーサイド MathJax で数式表示を高速化する