JavaScript への依存度が高いサイトを作るようになると、送信したデータに対して料金を支払っても、簡単には確認できないことがあります。この記事では、モバイル デバイスでサイトをすばやく読み込んでインタラクティブにするために、ちょっとした規律が役立つ理由について説明します。配信する JavaScript の量を減らすと、ネットワーク伝送時間が短縮され、コードの解凍時間が短縮され、この JavaScript の解析とコンパイルにかかる時間が短くなります。
ネットワーク
ほとんどのデベロッパーが JavaScript のコストについて考えるとき、ダウンロードと実行のコストについて考えてみます。転送する JavaScript のバイト数が増えると、ユーザーの接続速度が遅くなります。
ユーザーが使用している有効なネットワーク接続タイプが実際には 3G、4G、Wi-Fi ではない可能性があるため、これは問題になることがあります。コーヒーショップの Wi-Fi を利用しても、2G の速度のモバイル アクセス ポイントに接続している場合があります。
JavaScript のネットワーク転送コストは、次の方法で削減できます。
- ユーザーが必要とするコードのみを送信する。
- 圧縮
- ES5 コードを圧縮するには、UglifyJS を使用します。
- babel-minify または uglify-es を使用して ES2015+ を圧縮します。
- 圧縮
- 未使用のコードを削除する。
- DevTools のコード カバレッジを使用して、削除または遅延読み込みが可能なコードの機会を特定します。
- 最新のブラウザですでにトランスパイルされる機能を回避するには、babel-preset-env と browserlist を使用します。上級開発者は、Webpack バンドルを慎重に分析することで、不要な依存関係を削除する機会を特定できます。
- コードの削除については、ツリー シェイキング、Closure Compiler の高度な最適化、lodash-babel-plugin などのライブラリ トリミング プラグイン、Moment.js などのライブラリ用の webpack の ContextReplacementPlugin をご覧ください。
- コードをキャッシュに保存してネットワーク トリップを最小限に抑える。
- HTTP キャッシュを使用して、ブラウザがレスポンスを効果的にキャッシュに保存できるようにします。スクリプトの最適な存続期間(max-age)を決定し、検証トークン(ETag)を提供して、変更されていないバイトが転送されないようにします。
- Service Worker のキャッシュ保存により、アプリのネットワークの復元性が向上し、V8 のコード キャッシュなどの機能に積極的にアクセスできるようになります。
- 変更されていないリソースを再取得する必要がないように、長期キャッシュを使用します。Webpack を使用している場合は、ファイル名のハッシュをご覧ください。
解析/コンパイル
ダウンロード後、JavaScript で最もコストがかかるのは、JS エンジンがこのコードを解析/コンパイルする時間です。Chrome DevTools では、解析とコンパイルは [パフォーマンス] パネルの黄色の [スクリプト] 時間の一部です。
[Bottom-Up] タブと [Call Tree] タブには、解析とコンパイルの正確なタイミングが表示されます。
なぜこれが重要なのでしょうか
コードの解析やコンパイルに長い時間を費やすと、ユーザーがサイトを操作するまでの時間が大幅に長くなる可能性があります。送信する JavaScript が多いほど、サイトがインタラクティブになるまでの解析とコンパイルにかかる時間が長くなります。
バイト単位では、JavaScript は同等のサイズの画像やウェブフォントよりもブラウザでの処理コストが高い - Tom Dale
JavaScript と比較して、同等サイズの画像の処理には多くの費用がかかります(デコードが必要です)。平均的なモバイル ハードウェアでは、JS はページのインタラクティビティに悪影響を与える可能性が高くなります。
解析とコンパイルが遅いことについて話すときは、コンテキストが重要です。ここでは平均的なスマートフォンについて説明しています。平均的なユーザーは、低速の CPU と GPU を搭載し、L2/L3 キャッシュがなく、メモリに制約があるスマートフォンも使用している可能性があります。
ネットワーク機能とデバイスの機能は必ずしも一致しません。優れた Fiber 接続を利用しているユーザーが、デバイスに送信された JavaScript を解析し、評価するのに必ずしも最適な CPU を持っているとは限りません。これは逆に、ネットワーク接続はひどいが、CPU は非常に高速です。- Kristofer Baxter 氏 LinkedIn
ローエンドおよびハイエンドのハードウェアで解凍した(シンプルな)JavaScript を約 1 MB 解析する場合のコストを以下に示します。市場で最も高速なスマートフォンと平均的なスマートフォンでは、コードの解析/コンパイルにかかる時間が 2 ~ 5 倍あります。
CNN.com のような実際のサイトの場合、
ハイエンドの iPhone 8 では、CNN の JS の解析とコンパイルにわずか 4 秒しかかかりませんが、平均的なスマートフォン(Moto G4)では約 13 秒しかかかりません。これは、ユーザーがこのサイトを完全に操作するまでの時間に大きな影響を与える可能性があります。
このことから、ポケットに収まるスマートフォンだけではなく、平均的なハードウェア(Moto G4 など)でテストすることが重要であることがわかります。ただし、コンテキストは重要です。ユーザーのデバイスやネットワークの状態に合わせて最適化できます。
JavaScript を送りすぎていないかその可能性はあります。
HTTP アーカイブ(上位 50 万以上のサイト)を使用してモバイルの JavaScript の状態を分析すると、50% のサイトが操作可能になるのに 14 秒以上かかっていることがわかります。これらのサイトでは、JS の解析とコンパイルに最大 4 秒かかります。
JS やその他のリソースを取得して処理するのに要する時間を考慮してください。ページが使用可能になったと感じるまでユーザーがしばらく待たされることは、驚くことではないでしょう。改善の余地は十分にあるはずです。
重要性の低い JavaScript をページから削除すると、転送時間、CPU 使用率の高い解析とコンパイル、メモリのオーバーヘッドを削減できます。また、ページをインタラクティブにすばやく開くこともできます。
実行時間
解析とコンパイルだけではなく、コストもかかります。JavaScript の実行(解析/コンパイル後のコードの実行)は、メインスレッドで実行する必要があるオペレーションの 1 つです。実行時間が長くなると、ユーザーがサイトを操作できるまでの時間も長くなる可能性があります。
スクリプトが 50 ミリ秒を超えて実行されると、JS のダウンロード、コンパイル、実行に要する時間全体の分だけインタラクティブまでの時間が長くなります(Alex Russell)
これに対処するために、JavaScript では、メインスレッドのロックアップを回避するために、小さなチャンクを使用するという利点があります。実行中の作業量を減らすことができるかどうかを調べる。
その他の費用
JavaScript はページのパフォーマンスに次のような影響を与える可能性があります。
- メモリ。GC(ガベージ コレクション)が原因で、ページが頻繁にジャンクしたり一時停止になったりすることがあります。ブラウザがメモリを再利用すると、JS の実行が一時停止するため、ガベージ コレクションを頻繁に行うブラウザは、実行を一時停止する頻度が想定より高くなる可能性があります。メモリリークや gc の頻繁な一時停止を回避して、ページでジャンクが発生しないようにしてください。
- ランタイムに長時間実行される JavaScript によってメインスレッドがブロックされ、ページが応答しなくなることがあります。作業を小さく分割(スケジュール設定に
requestAnimationFrame()
またはrequestIdleCallback()
を使用)すると、応答性の問題を最小限に抑えることができ、Interaction to Next Paint(INP)を改善できます。
JavaScript 配信費用を削減するためのパターン
JavaScript の解析/コンパイル時間とネットワーク送信時間を遅くしたい場合、ルートベースのチャンクや PRPL などのパターンが役立ちます。
PRPL
PRPL(プッシュ、レンダリング、事前キャッシュ、遅延読み込み)は、積極的なコード分割とキャッシュによってインタラクティビティを最適化するパターンです。
その効果を可視化してみましょう。
Google では V8 の Runtime Call Stats を使用して、人気の高いモバイルサイトとプログレッシブ ウェブアプリの読み込み時間を分析しています。ご覧のとおり、解析時間(オレンジ色で表示)は、これらのサイトの多くが時間を費やしている部分の大部分を占めています。
PRPL を使用するサイトの Wego は、ルートの解析時間を短く保ち、非常に迅速にインタラクティブになっています。上記の他のサイトの多くは、JS 費用を削減するために、コード分割とパフォーマンス バジェットを採用しました。
プログレッシブ ブートストラップ
多くのサイトでは、インタラクティビティを犠牲にして、コンテンツの視認性を最適化しています。サイズの大きい JavaScript バンドルがある場合にファースト ペイントを高速化するために、デベロッパーはサーバーサイド レンダリングを採用することがあります。これは、JavaScript が最終的に取得されたときにイベント ハンドラをアタッチするように「アップグレード」します。
これには費用がかかりますので、ご注意ください。通常は、1)大きな HTML レスポンスを送信するため、インタラクティビティが促進されます。2)JavaScript の処理が完了するまで、エクスペリエンスの半分が実際にはインタラクティブにならない不気味な谷にユーザーが留まる可能性があります。
プログレッシブ ブートストラップのほうが良いアプローチかもしれません。最小限の機能のページ(現在のルートに必要な HTML/JS/CSS のみで構成)を送ります。より多くのリソースが到着すると、アプリはより多くの機能を遅延読み込みしてロック解除します。
表示内容に合わせてコードを読み込むことが、究極の目標です。PRPL とプログレッシブ ブートストラップは、これを行うために役立つパターンです。
まとめ
送信サイズはローエンド ネットワークにとって重要です。解析時間は CPU バウンドのデバイスにとって重要です。これを低く抑えることが重要になります。
チームは、JavaScript の転送時間と解析/コンパイル時間を短く抑えるために、厳格なパフォーマンス バジェットの導入に成功しています。Alex Russell の「Can You Afford It?: Real World Web Performance Budgets」をご覧ください。
モバイル デバイスをターゲットとするサイトを構築する場合は、代表的なハードウェアで開発するように最善を尽くし、JavaScript の解析/コンパイル時間を短く抑え、チームが JavaScript の費用を把握できるようにパフォーマンス バジェットを採用します。
詳細
- Chrome Dev Summit 2017 - 最新の読み込みのベスト プラクティス
- JavaScript 起動時のパフォーマンス
- ウェブ パフォーマンスの危機を解決する - Nolan Lawson
- 予算の確保は可能ですか?実際のパフォーマンス予算 - Alex Russell
- ウェブ フレームワークとライブラリの評価 - Kristofer Baxter
- 圧縮に関して Cloudflare が Brotli を使用して実験した結果(高品質で動的な Brotli を使用すると、最初のページ レンダリングが遅れる可能性があるため、慎重に評価してください)。代わりに静的に圧縮することもできます)。
- Performance Futures - Sam Saccone