フロントエンドエンジニアのsakaguchiiです。
今現在アイスタイルではCore Web Vitals指標の改善に取り組んでおり、今回はその施策の中で行ったJavaScriptの非同期読み込み対応についてご紹介します?
アットコスメでのjs非同期読み込み対応
まずどういう対応を行ったかについてですが、アットコスメの商品ページで読み込んでいるJavaScriptに対してasync/deferをつける対応を行いました。
この方法はPageSpeed Insightsのルールのうちの「レンダリングを妨げるJavaScriptを削除する」の中のひとつとして推奨されている方法になります。
非同期読み込み対応を行ったことによる効果
下記はドキュメントインタラクティブの計測結果の表(抜粋)です。
リリースを境に平均ドキュメントインタラクティブ時間の値がおよそ20%~30%改善しました。
※ 平均ドキュメント インタラクティブ時間とは
ブラウザがドキュメントを解析(DOMInteractive)するのにかかった平均時間(秒数)。ユーザーの所在地からサーバーへのネットワーク通信時間も含まれます。なお、この処理の間はドキュメント オブジェクト モデル(DOM)が完全には読み込まれていませんが、ユーザー操作は可能です。
引用:サイトの速度レポートの分析(外部サイト)https://support.google.com/analytics/answer/2383341?hl=ja#zippy=%2C%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%86%85%E5%AE%B9
このように非同期で読み込むことは効果的であることが伺えます。
ではどのような時にasync/deferをつけるのか、なぜこの対応を行うことにより改善されるのかについて書いていきます。
パーサーブロックとは
なぜjsにasync/deferをつけると改善されるかの説明の前に「パーサーブロック」について触れます。
ブラウザがHTML・CSS・JavaScriptを読み込み画面上に描画するまでの工程のことをクリティカルレンダリングパスと呼び、このレンダリングパスにはLoading、Scripting、Rendering、Paintingの4つの工程を1つのフレームとして成り立っています。
このうちLoadingでは下記の処理を行います。
・HTMLやCSS等必要なファイルをダウンロード
・CSSを解析しCSSOMツリーに変換
・HTMLを解析しDOMツリーに変換
HTMLを解析しDOMツリーに変換する処理では、ダウンロードしたHTMLファイルを上から1行ずつ解析し、DOMに変換していきます。
しかしこの変換の処理の途中でscript
タグを見つけると解析処理は一時中断され、javascriptの読み込み・解析・実行の処理へと寄り道をします。
<!DOCTYPE html>
<html>
<head>
<title>hoge</title>
<script src="hoge.js"></script> <!-- ここで解析が止まる -->
</head>
<body>
<div id="hoge"><p>hoge</p></div>
<script>alert("hoge");</script> <!-- ここで解析が止まる -->
</body>
</html>
このHTMLの解析(パース)を中断してしまうことを「パーサーブロック」と言います。
解析が中断されるとその分DOMの構築が遅延してしまうため、より円滑にHTML解析を走らせるための方法のひとつがjavascriptの非同期読み込み対応になります。
javascriptの非同期読み込み方法とその種類
クラシックスクリプトの場合
type | code | gaiyo |
---|---|---|
default | <script src="/hoge.js"></script> |
何も指定しない形式。 HTMLパーサー(解析)がscript の記述に到達したときDOM構築は中断され、JSのダウンロード・実行が完了すると再開します。 |
async |
<script async src="/hoge.js"></script> |
HTMLパーサーがscript の記述に到達したときHTMLパースの処理と並行してjsのダウンロードを行い、ダウンロードが完了した時点でjsが実行されます。なお、パース中にjsが実行された場合、DOM構築は中断されます。 |
defer |
<script defer src="/hoge.js"></script> |
HTMLパーサーがscriptの記述に到達したときHTMLパースの処理と並行してjsのダウンロードを行い、HTML解析が完了したときjsが実行されます。async はjsの処理開始順をは担保していないため、jQueryやDOMに依存する処理が書かれたjsはdefer の記述を使用する必要があります。 |
モジュールスクリプトの場合
type | code | gaiyo |
---|---|---|
default | <script type="module" src="/hoge.js"></script> |
クラシックスクリプトのdeferと同等の動きになります |
async |
<script type="module" async type="module"></script> |
クラシックスクリプトのasyncと同等の動きになります |
defer |
– | モジュールスクリプトの場合はdeferを付けても効きません |
参照:html.spec.whatwg.org
https://html.spec.whatwg.org/multipage/scripting.html#:~:text=This%20is%20all%20summarized%20in%20the%20following%20schematic%20diagram:
src属性を持たないscriptタグ
下記のようなsrc属性を持たないscriptに対して、async/defer属性は効きません。
<script>
let element = document.getElementById('id');
element.classList.add('class');
</script>
これらのコードもパースをブロックしてしまうため、可能であれば別ファイルにしたうえでasync/deferをつけて読み込むことが改善に繋がります。
最後に
jsに対して「これはdeferか?いやasyncをつけるべきか?」「この<script>
は別ファイルにすべきか?」「このjsはそもそも今使われているのか??」といった調査と検証をひとつひとつ行っていくことは少し骨の折れる作業でしたが、数値としてしっかり反映されたことで意味のある作業だったと一安心しています。
地道な作業ではありますが、ユーザーの方にとってより良いサイトになるよう取り組んでいきたいと思います?