Core Web Vitalに関する連載の第5回はCSSとJSの遅延読み込みについて説明する。この2点は主に「レンダリングを妨げるリソースの除外」という項目でPage Speed Insightsに表示される。

プラグインで対応する方法もあるのでプラグインを導入して済めばそれでよし。本稿では原理の説明とコードによる解決を紹介する。テーマ本体に手を入れなくてもよいソリューションなので、ぜひ取り組んでほしい。筆者の開発したテーマSide Businessでもほぼ似たようなことをやっている。
CSSの遅延読み込み
CSSは通常head
タグ内に挿入される。ブラウザは <link>
タグを見つけるたびにCSSをダウンロードし、その内容を解析してスタイルコンテキストツリーを構築する。この間はHTMLのパース(DOMモデル構築)やJavaScriptの実行などがストップするので、レンダリングブロックつまり描画を止めてしまう要因となる。
これを防ぐためには rel="preload"
という手法がある。すでにKunoichi Marketで記事を書いたのだが、再度同じ内容を掲載する。
rel=”preload”の基本
まず、スタイルシートがレンダリングブロックにならないようするには、次のような書き方がある。
<link rel="preload" href="/path/to/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
この書き方だと、CSSはレンダリングブロックにならない。この記法 rel="preload"
を「リソースヒント」と呼ぶ。要するに、ブラウザがそのリソースをどう扱うかについて指示を与えるためだ。
しかし、リソースヒントはIE11に対応していないため、ポリフィルが必要になる。これは次に紹介するWordPressでの実装方法であわせて紹介する。
WordPressのlinkタグを書き換え
それでは、早速やり方を紹介しよう。WordPressがCSSを読み込む wp_enqueue_style
にフックをかけ、すべてのリンクタグを書き換える。
/**
* link タグを rel="preload" にする。
*
* @param string $tag HTMLタグ
* @param string $handel handle名。
* @param string $href URL。
* @param string $media メディア属性
*/
add_filter( 'style_loader_tag', function( $tag, $handle, $href, $media ) {
// 管理画面およびログイン画面ではオフ。
// もちろん、管理画面やログイン画面で遅延読み込みにしても問題ない。
if ( is_admin() || did_action( 'login_head' ) ) {
return $tag;
}
// クリティカルCSSのハンドル名を列挙。多くの場合、テーマのCSS。
$criticals = [ 'my-theme-style' ];
// クリティカルCSSかプリントメディアの場合は何もしない。
// media="print" の場合はスクリーンではほっといても優先度低。
if ( in_array( $handle, $criticals, true ) || 'print' === $media ) {
return $tag;
}
// カスタマイズしたタグを出力。念の為noscriptも追加。
$html = <<<'HTML'
<link rel="preload" href="%1$s" as="style" onload="this.onload=null;this.rel='stylesheet'" data-handle="%3$s" media="%4$s" />
<noscript>
%2$s
</noscript>
HTML;
return sprintf( $html, $href, $tag, $handle, $media );
}, 10, 4 );
上記でモダンブラウザに関してはCSSが遅延読み込みになる。コメント欄にでてきた「クリティカルCSS」という概念を覚えておいてほしい。すべてのCSSを遅延読み込みにしてしまうと、あたりまえだがスタイルの適用されていない画面が一瞬表示される。これを FOUC = Flash Of Unstyled Contentという。

このFOUCを避けるためには、少なくともファーストビューをスタイリングするCSSだけは即時に読み込みする必要がある。多くのWordPressテーマにおいてそれはメインスタイルシートだろうから、それだけは除外する。なにがクリティカルかはサイトによって異なると思うので、適宜調整してほしい。
ポリフィルの実装
WordPress 5.8からサポートされなくなるIE11だが、対応が必要な場合もあるのでやり方を紹介しよう。まず、loadCSSというライブラリを利用する。ただ、このライブラリはバージョン3から「media属性変えた方がいいんじゃね?」というアプローチになっており、これはこれで印刷用CSSに対応しているサイトでは面倒くさいことになりそうなので、バージョン2を利用する。npmを利用するなら npm install --save-dev fg-loadcss@2.1.0
でインストールできるし、直接ダウンロードしてもよい。
このライブラリの中の src/cssrelpreload.js
をインラインでフッターあたりに書き込むと、ポリフィルの適用となる。このファイル自体はミニファイされていないので、webpackなどを通じて圧縮した方が良いだろう。
/**
* ポリフィルをインラインで書き込む
*/
add_action( 'wp_footer', function() {
printf( "<script>\n%s\n</script>", file_get_contents( get_template_directory() . '/dist/vendor/fg-loadcss/cssrelpreload.min.js' ) );
}, 100 );
以上でCSSの遅延読み込みは完了だ。
JavaScriptの遅延読み込み
JavaScriptは遅延読み込みをすることができる。defer
および async
属性が存在し、両方とも非同期読み込みにはなるのだが、違いは実行タイミングと実行順である。defer
はHTMLパース後にscript
タグで書かれた順番で実行されるので、WordPressの wp_enqueue_script
の依存関係を考えると「何かに依存するスクリプトは defer
、単独で動くものはasync
」と考えれば良いのではないだろうか。条件分岐しても良いが、ここではとりあえず「可能な限りdefer」という戦略を取る。
JSにdefer属性をつける
それでは、実際のコードを紹介する。
/**
* scriptタグにdeferをつける
*
* @param string $tag scriptタグ
* @param string $handle ハンドル名
* @return string
*/
add_filter( 'script_loader_tag', function( $tag, $handle ) {
// deferにできるハンドル名のリスト
$deferable = [];
if ( ! in_array( $handle, $deferable, true ) ) {
return $tag;
}
// deferをつける
return str_replace( 'src=', 'defer src=', $tag );
}, 10, 2 );
とりあえず、wp_enqueue_script
で読み込まれるスクリプトに defer
属性をつけることは上記で可能なのだが、ではどのスクリプトなら安全に遅延読み込みできるのだろうか?
遅延読み込みできないJS
WordPressにおける開発の難しさは、サードパーティーのコードがたくさん入ってくるところである。コアだけを追っていてもダメで、プラグインなどから色々と追加される。そうなると、それらのコードがdeferに対応しているかどうかが重要になってくる。
NGケース1. インラインで処理を行なっている
WordPressには wp_add_inline_script
という関数があり、特定のJSがエンキューされた場合、その直後または直前にインラインJSを追加することができる。多くの場合、これはライブラリの利用などだろう。
// スライダーのライブラリを読み込み
wp_enqueue_script( 'super-slider' );
// スライダーを有効化
wp_add_inline_script( 'super-slider', 'new SuperSlide(".super-slider")' );
上記のPHPはWordPressによってHTML上で次のように出力される。
<script src="/path/to/super-slider.js"></script>
<script>
new SuperSlide(".super-slider")
</script>
このJSを遅延読み込みした場合、当然ながら SuperSlide
はまだ存在していないのでエラーになる。よって、遅延読み込みできない。コアのJSでも @wordpress/i18n などはこのケースに該当する。
NGケース2. jQueryをなめている
WordPressはjQueryに深く依存しており、なにもしなくてもテーマ側でjQueryが読み込まれる。ただし、昨今のESNextのブームにより「jQueryは不要!」などといったブログ記事を読んで浮き足立ち、readyState などのイベントハンドラーで初期化処理を行なっているケースも増えてきた。この場合、イベントハンドリングのやり方によっては遅延読み込みによって「初期化処理が発火しない」ということもありえる。祭りはとうに終わったというのに浴衣で着飾った同級生が来るのを待つような事態だ。誰もお前を愛さない。
一つずつ許可していくのが現実的か
なんにせよ、WordPressにおいてJSの遅延読み込みをする場合、「できるかできないかわからない」という問題が常につきまとう。許可リストでも禁止リストでも良いが、とにかく遅延読み込みはフィルタリングをする必要がある。
これはCSSの遅延読み込みに比べるとけっこうタフな作業だ。いちいちチェックする必要があるし、エラーが起きた場合はどのJSでエラーが起きたかを確かめる必要がある。JavaScriptの専門的な知識も必要だ。
ちなみに、「現在のページで読み込まれているスクリプト」を確かめるには、Query Monitorが便利だ。ChromeのLighthouse拡張などと組み合わせながら、優先度の高いものを一つずつ遅延読み込みをしていくのが現実的だろう。

まとめ
本稿ではJSおよびCSSの遅延読み込みについて紹介した。繰り返しになるが、このソリューションはテーマ本体に手を入れなくても対応可能なので、取り組みやすいだろう。また、Capital Pで以前紹介したjQueryをフッターに移すというのも遅延読み込み以外にちょっとした効果を生む対処方法なので、あわせて参考にして欲しい。
コメントを残す