カスタムテーマのphpから、body配下にプラグインによって出力されたclass="mathjax"がある時だけ<head></head>にmathjaxのスクリプトコードを記述する機能を持たせることができた。実際にはmathjaxが無い場合に出力の<script>タグをコメントアウトしているがどちらも同じこと。それについてメモする。

onOutputGeneratedをフックする

public static function getSubscribedEvents()
{
  return [
    'onThemeInitialized' => ['onThemeInitialized', 0],
  ];
}

public function onThemeInitialized()
{
  if ($this->isAdmin()) {
    $this->active = false;
    return;
  }

  $this->enable([
    'onOutputGenerated' => ['onOutputGenerated', 10]
  ]);
}

このイベントが最終的なHTMLテキストデータが生成されて、かつechoが行われる直前に呼び出される。このタイミングで$this->grav->outputを上書きすることで実際にechoされる内容を直前に変更することが出来る。イベントのpriorityを10としてる。これは、AdvancedPageCacheプラグインによって出力がキャッシュされるよりも前にこの処理を挟みたいから。

mathjaxクラスを検索し、無ければscriptタグをコメントアウト

public function onOutputGenerated()
{
  $output = $this->grav['output'];

  /* comment out mathjax script code if no mathjax conponent */
  if( !preg_match('@<(p|span) +?class="([^"]*?mathjax[^"]*)"@', $output, $result) ){
    if( preg_match('@<script +?src="([^"]*?mathjax[^"]*)".*?</script>@', $output, $result) ) {
      if($result[1] == 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js') {
        $this->grav->output = str_replace($result[0], "<!-- no mathjax component -->\n<!--".$result[0].'-->', $output);
      }
    }
  }
}

正規表現はよくわかっていないが、要はmathjaxプラグインが吐き出す<p class="mathjax mathjax--block">と、<span class="mathjax mathjax--inline">の2種類を検出できれば良い。これが無ければmathjaxのscriptタグをコメントアウトする。

必要な時だけMathjaxを読み込んで何が嬉しいのか?

Mathjaxはかなり重いライブラリなので読み込み速度に影響を与える。もちろん遅延読み込みだとか非同期だとかでごまかすことは出来ても実際にサイズが大きいものをフェッチするという事実とそれだけの大きさのスクリプトを解析して実行するという事実はごまかせない。数式のあるページでmathjaxをロードするのは仕方ないとして、数式が無いページでは使わないはずのmathjaxのjavascriptはそもそもロードさせないのが一番。だから嬉しい。

そのトレードオフとして、サーバ側でデータを吐き出す直前に正規表現を走らせているのでレスポンスが低下するかもというのがある。うまくこの出力ままキャッシュしてくれればよいのだが(追記:AdvancedPageCacheを使うと出来る)。ただ、もしキャッシュが働かなくてもmathjaxのサイズは結構大きいし全体のレスポンスを下げてでも数式が無いページの恩恵が大きいだろうという見解。サーバの強さとか、数式ありページの比率などによりけりだけどもこういうこともできるという話。そう、Gravならね。

あとこのページは数式無い。