JavaScriptの非同期読み込みとその効果について

フロントエンドエンジニアの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を読み込み画面上に描画するまでの工程のことをクリティカルレンダリングパスと呼び、このレンダリングパスにはLoadingScriptingRenderingPaintingの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をつけて読み込むことが改善に繋がります。

参照:html.spec.whatwg.org
https://html.spec.whatwg.org/multipage/scripting.html#:~:text=Classic%20scripts%20are%20affected%20by%20the%20async%20and%20defer%20attributes,%20but%20only%20when%20the%20src%20attribute%20is%20set.%20Authors%20should%20omit%20the%20type%20attribute%20instead%20of%20redundantly%20setting%20it.


最後に

jsに対して「これはdeferか?いやasyncをつけるべきか?」「この<script>は別ファイルにすべきか?」「このjsはそもそも今使われているのか??」といった調査と検証をひとつひとつ行っていくことは少し骨の折れる作業でしたが、数値としてしっかり反映されたことで意味のある作業だったと一安心しています。
地道な作業ではありますが、ユーザーの方にとってより良いサイトになるよう取り組んでいきたいと思います💪

デザイン部所属 / フロントエンドエンジニア