JavaScript 起動の最適化

JavaScript に依存するサイトを構築する際に、送信したコンテンツに対して、簡単には確認できない方法で料金が発生することがあります。この記事では、モバイル デバイスでサイトをすばやく読み込み、操作できるようにするために、少しの規律が役立つ理由について説明します。配信する JavaScript を減らすと、ネットワーク転送時間、コードの解凍時間、JavaScript の解析とコンパイル時間が短縮されます。

ネットワーク

ほとんどのデベロッパーは、JavaScript のコストについて考えるとき、ダウンロードと実行のコストについて考えます。ワイヤーを介して送信する JavaScript バイト数が多いほど、ユーザーの接続が遅くなると時間がかかるようになります。

ブラウザがリソースをリクエストすると、そのリソースを取得して解凍する必要があります。JavaScript などのリソースの場合は、実行前に解析してコンパイルする必要があります。

ユーザーの有効なネットワーク接続タイプが実際には 3G、4G、Wi-Fi ではない可能性があるため、これは問題になる可能性があります。コーヒーショップの Wi-Fi に接続していても、2G 速度のモバイル アクセス ポイントに接続している場合があります。

JavaScript のネットワーク転送費用を削減するには、次のことを行います。

  • お客様に必要なコードのみを送信する
    • コード分割を使用して、JavaScript を重要なものとそうでないものに分割します。webpack などのモジュール バンドラは、コード分割をサポートしています。
    • クリティカルでないコードの遅延読み込み。
  • 圧縮
  • 圧縮
    • 少なくとも、gzip を使用してテキストベースのリソースを圧縮します。
    • Brotli ~q11 の使用を検討してください。Brotli は圧縮率で gzip を上回ります。これにより、CertSimple では圧縮 JS バイトサイズが 17% 削減され、LinkedIn では読み込み時間が 4% 削減されました。
  • 未使用のコードの削除
  • ネットワーク トラフィックを最小限に抑えるためにコードをキャッシュに保存する。
    • HTTP キャッシュを使用して、ブラウザがレスポンスを効果的にキャッシュに保存できるようにします。スクリプトの最適な存続期間(max-age)を決定し、検証トークン(ETag)を指定して、変更されていないバイトの転送を回避します。
    • Service Worker のキャッシュを使用すると、アプリ ネットワークの復元力を高め、V8 のコードキャッシュなどの機能に積極的にアクセスできます。
    • 長期保存キャッシュを使用すると、変更されていないリソースを再取得する必要がなくなります。Webpack を使用している場合は、ファイル名ハッシュ化をご覧ください。

解析/コンパイル

ダウンロード後、JavaScript の最も負荷の高いコストの 1 つは、JS エンジンがこのコードを解析/コンパイルする時間です。Chrome DevTools では、解析とコンパイルは、[パフォーマンス] パネルの黄色の [スクリプト処理] 時間に含まれます。

ALT_TEXT_HERE

[Bottom-Up] タブと [Call Tree] タブには、正確な解析/コンパイル時間が表示されます。

ALT_TEXT_HERE
Chrome DevTools の [パフォーマンス] パネル > [ボトムアップ] を選択します。V8 のランタイム呼び出し統計情報を有効にすると、解析やコンパイルなどのフェーズで費やされた時間を確認できます。

なぜこれが重要なのか

ALT_TEXT_HERE

コードの解析やコンパイルに時間がかかると、ユーザーがサイトを操作できるようになるまでの時間が大幅に遅れる可能性があります。送信する JavaScript が多いほど、解析とコンパイルに時間がかかり、サイトがインタラクティブになるまでに時間がかかります。

バイト単位で見ると、JavaScript は同等のサイズの画像やウェブフォントよりもブラウザでの処理コストが高い - Tom Dale

JavaScript と比較すると、同等のサイズの画像を処理する際には多くのコストがかかります(画像はデコードする必要があります)。しかし、平均的なモバイル ハードウェアでは、JS の方がページのインタラクティビティに悪影響を及ぼす可能性があります。

ALT_TEXT_HERE
JavaScript と画像バイトの費用は大きく異なります。通常、画像はデコードとラスタライズ中にメインスレッドをブロックしたり、インターフェースがインタラクティブになるのを妨げたりしません。ただし、JS は解析、コンパイル、実行のコストが原因でインタラクティビティが遅れる可能性があります。

解析とコンパイルが遅い場合、コンテキストが重要です。ここでは平均的なスマートフォンについて説明しています。一般的なユーザーは、CPU と GPU が遅く、L2/L3 キャッシュがなく、メモリが制限されているスマートフォンを使用している可能性があります。

ネットワーク機能とデバイス機能が必ずしも一致するとは限りません。優れた Fiber 接続を使用しているユーザーが、デバイスに送信された JavaScript を解析して評価するのに最適な CPU を持っているとは限りません。これは逆の場合も同様です。ネットワーク接続は非常に遅いが、CPU は超高速です。- Kristofer Baxter、LinkedIn

以下は、低価格とハイエンドのハードウェアで約 1 MB の圧縮解除された(シンプルな)JavaScript を解析するコストを示しています。市場で最も高速なスマートフォンと平均的なスマートフォンでは、コードの解析/コンパイルにかかる時間が 2 ~ 5 倍の差があります

ALT_TEXT_HERE
このグラフは、さまざまなクラスのデスクトップ デバイスとモバイル デバイスで、1 MB の JavaScript バンドル(gzip 圧縮で約 250 KB)の解析時間を示しています。解析の費用を検討する場合は、解凍後の数値を考慮します。たとえば、250 KB の圧縮済み JS は約 1 MB のコードに解凍されます。

CNN.com などの実際のサイトはどうですか?

ハイエンドの iPhone 8 では、CNN の JS の解析とコンパイルに約 4 秒しかかかりませんが、平均的なスマートフォン(Moto G4)では約 13 秒かかります。これは、ユーザーがこのサイトを完全に操作できるまでの時間に大きな影響を与える可能性があります。

ALT_TEXT_HERE
上記の解析時間は、Apple の A11 Bionic チップのパフォーマンスと、より一般的な Android ハードウェアの Snapdragon 617 のパフォーマンスを比較したものです。

そのため、ポケットに入っているスマートフォンだけでなく、平均的なハードウェア(Moto G4 など)でテストすることが重要です。ただし、コンテキストは重要です。ユーザーのデバイスとネットワークの状況に合わせて最適化してください。

ALT_TEXT_HERE
Google アナリティクスでは、実際のユーザーがサイトにアクセスする際に使用しているモバイル デバイス クラスに関する分析情報を確認できます。これにより、実際に動作している CPU/GPU の制約を把握できます。

JavaScript の送信量が多すぎるのは本当か?恐らくはそうです。

HTTP Archive(上位 50 万サイト)を使用してモバイル版 JavaScript の状態を分析したところ、50% のサイトでインタラクティブになるまでに 14 秒以上かかりました。これらのサイトでは、JavaScript の解析とコンパイルに最大 4 秒かかります。

ALT_TEXT_HERE

JS などのリソースの取得と処理にかかる時間を考慮すると、ページが使用可能になるまでユーザーがしばらく待たされる可能性があるのは当然のことかもしれません。改善の余地は十分にあります。

ページから重要でない JavaScript を削除すると、転送時間、CPU 使用率の高い解析とコンパイル、潜在的なメモリ オーバーヘッドを削減できます。また、ページのインタラクティブ性をより早く実現できます。

実行時間

コストが発生するのは解析とコンパイルだけではありません。JavaScript の実行(コードを解析/コンパイルした後に実行)は、メインスレッドで実行する必要があるオペレーションの一つです。実行時間が長いと、ユーザーがサイトを操作できるまでの時間が長くなることもあります。

ALT_TEXT_HERE

スクリプトの実行時間が 50 ミリ秒を超えると、JS のダウンロード、コンパイル、実行に要する時間の合計時間だけ、インタラクティブになるまでの時間が遅れます。 - Alex Russell

この問題に対処するため、JavaScript は小さなチャンクで実行され、メインスレッドがロックされないようにします。実行中に処理される作業量を減らせないか検討します。

その他の費用

JavaScript は、次のような方法でページのパフォーマンスに影響する可能性があります。

  • メモリ。GC(ガベージ コレクション)が原因で、ページが頻繁にジャンクしたり一時停止したりすることがあります。ブラウザがメモリを再利用すると、JS の実行が一時停止します。そのため、ガベージを頻繁に収集するブラウザでは、望ましくない頻度で実行が一時停止する可能性があります。メモリリークや頻繁な GC の停止を回避して、ページのジャンクを回避します。
  • 実行中に長時間実行される JavaScript は、メインスレッドをブロックしてページが応答しなくなる可能性があります。処理を小さな部分に分割(スケジュールに requestAnimationFrame() または requestIdleCallback() を使用する)ことで、応答性の問題を最小限に抑え、Interaction to Next Paint(INP) を改善できます。

JavaScript 配信コストを削減するパターン

JavaScript の解析/コンパイルとネットワーク送信時間を遅くしたい場合は、ルートベースのチャンキングや PRPL などのパターンが役立ちます。

PRPL

PRPL(Push、Render、Pre-cache、Lazy-load)は、積極的なコード分割とキャッシュ保存によってインタラクティビティを最適化するパターンです。

ALT_TEXT_HERE

それがもたらす影響を視覚化しましょう。

Google では、V8 のランタイム コール統計情報を使用して、一般的なモバイルサイトとプログレッシブ ウェブアプリの読み込み時間を分析しています。ご覧のとおり、解析時間(オレンジ色)は、これらのサイトの多くで費やされる時間の大部分を占めています。

ALT_TEXT_HERE

PRPL を使用するサイトである Wego は、ルートの解析時間を短く抑え、非常に迅速にインタラクティブな状態になります。上記の他の多くのサイトでは、コード分割とパフォーマンス バジェットを採用して、JS 費用の削減を試みています。

プログレッシブ ブートストラップ

多くのサイトでは、インタラクティビティを犠牲にしてコンテンツの視認性を最適化しています。サイズの大きい JavaScript バンドルがある場合にファースト ペイントを高速化するために、デベロッパーはサーバーサイド レンダリングを採用し、JavaScript が最終的にフェッチされたときにイベント ハンドラをアタッチするように「アップグレード」することがあります。

ただし、この方法には独自の費用が発生します。1)通常、インタラクティビティを押し上げることができるより大きな HTML レスポンスを送信します。2)JavaScript の処理が完了するまで、エクスペリエンスの半分が実際にインタラクティブにならないという不気味の谷にユーザーを置く可能性があります。

プログレッシブ ブートストラップの方が適切な方法かもしれません。最小限の機能を持つページ(現在のルートに必要な HTML/JS/CSS のみで構成)を送信します。リソースが追加されると、アプリはレイジー ロードを行い、より多くの機能を利用できるようになります。

ALT_TEXT_HERE
プログレッシブ ブートストラップ(Paul Lewis 著)

表示内容に応じてコードを読み込むことが理想です。PRPL とプログレッシブ ブートストラップは、この実現に役立つパターンです。

まとめ

低価格帯のネットワークでは、転送サイズが重要です。解析時間は、CPU に負荷がかかるデバイスにとって重要です。これらの数値を低く抑えることが重要です。

チームは、JavaScript の送信と解析/コンパイル時間を短くするために、厳格なパフォーマンス バジェットを採用して成功を収めています。アレックス ラッセルの「Can You Afford It?: 」をご覧ください。

ALT_TEXT_HERE
アーキテクチャに関する決定によってアプリ ロジックに残せる JS の「ヘッドルーム」の量を検討すると役に立ちます。

モバイル デバイスをターゲットとするサイトを構築する場合は、代表的なハードウェアで開発し、JavaScript の解析/コンパイル時間を短く抑え、パフォーマンス予算を導入して、チームが JavaScript の費用を常に把握できるようにしてください。

詳細