JavaScript に大きく依存するサイトを構築すると、送信するデータに対して、必ずしも簡単に確認できるとは限らない方法でコストを支払うことがあります。この記事では、モバイル デバイスでサイトをすばやく読み込み、インタラクティブにするために、少しの規律が役立つ理由について説明します。JavaScript の配信量を減らすことで、ネットワーク送信にかかる時間を短縮し、コードの解凍にかかる時間を短縮し、この JavaScript の解析とコンパイルにかかる時間を短縮できます。
ネットワーク
ほとんどのデベロッパーは、JavaScript のコストを考えるときに、ダウンロードと実行のコストの観点から考えます。ユーザーの接続速度が遅いほど、ネットワーク経由で送信する JavaScript のバイト数が増えるため、時間がかかります。

これは問題になる可能性があります。ユーザーの有効なネットワーク接続タイプが、実際には 3G、4G、Wi-Fi ではない可能性があるためです。たとえば、コーヒーショップの Wi-Fi に接続しているものの、2G 速度のモバイル アクセス ポイントに接続している場合などです。
JavaScript のネットワーク転送コストを削減するには、次の方法があります。
- ユーザーが必要とするコードのみを送信する。
- 縮小
- UglifyJS を使用して ES5 コードを圧縮します。
- 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 では、解析とコンパイルは [Performance] パネルの黄色の [Scripting] 時間の一部です。

[Bottom-Up] タブと [Call Tree] タブには、正確な解析 / コンパイルのタイミングが表示されます。
しかし、なぜこれが重要なのでしょうか。

コードの解析やコンパイルに時間がかかると、ユーザーがサイトを操作できるようになるまでの時間が大幅に遅れる可能性があります。送信する JavaScript が多いほど、サイトがインタラクティブになる前に解析とコンパイルに時間がかかります。
バイト単位で比較すると、ブラウザが JavaScript を処理するコストは、同じサイズの画像やウェブフォントを処理するコストよりも高くなります。- Tom Dale
JavaScript と比較すると、同等のサイズの画像を処理するには多くのコストがかかります(デコードする必要があります)が、平均的なモバイル ハードウェアでは、JS の方がページのインタラクティビティに悪影響を及ぼす可能性が高くなります。
解析とコンパイルが遅いという場合、コンテキストが重要です。ここでは、平均的なスマートフォンについて説明しています。平均的なユーザーは、CPU と GPU が遅く、L2 / L3 キャッシュがなく、メモリが制限されている可能性のあるスマートフォンを使用している可能性があります。
ネットワーク機能とデバイス機能は必ずしも一致しません。優れたファイバー接続を使用しているユーザーが、デバイスに送信された 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 Archive(上位約 50 万サイト)を使用してモバイルでの JavaScript の状態を分析すると、50% のサイトでインタラクティブになるまでに 14 秒以上かかっていることがわかります。これらのサイトでは、JS の解析とコンパイルだけで最大 4 秒かかります。

JS やその他のリソースの取得と処理にかかる時間を考慮すると、ユーザーがページを使用できるようになるまでしばらく待たされることは、おそらく驚くことではありません。この点は、間違いなく改善の余地があります。
ページから重要でない JavaScript を削除すると、送信時間、CPU 負荷の高い解析とコンパイル、潜在的なメモリ オーバーヘッドを削減できます。これにより、ページのインタラクティブ性を高めることもできます。
実行時間
コストがかかるのは、解析とコンパイルだけではありません。JavaScript の実行(解析 / コンパイルされたコードの実行)は、メインスレッドで行う必要があるオペレーションの一つです。また、実行時間が長いと、ユーザーがサイトを操作できるようになるまでの時間が長くなる可能性があります。

スクリプトの実行時間が 50 ミリ秒を超えると、JS のダウンロード、コンパイル、実行に要する全体の時間によって、Time to Interactive が遅延します。
これに対処するため、JavaScript は小さなチャンクに分割して、メインスレッドがロックされないようにします。実行中に実行される作業量を減らすことができるかどうかを検討します。
その他の費用
JavaScript は、次のような方法でページのパフォーマンスに影響を与える可能性があります。
- メモリ。GC(ガベージ コレクション)が原因で、ページが頻繁にジャンクしたり一時停止したりすることがあります。ブラウザがメモリを再利用すると、JS の実行が一時停止されるため、ガベージ コレクションを頻繁に行うブラウザでは、実行が頻繁に一時停止される可能性があります。メモリリークや頻繁な GC の一時停止を回避して、ページがジャンクフリーになるようにします。
- 実行時に、長時間実行される JavaScript がメインスレッドをブロックし、ページが応答しなくなることがあります。作業を細かく分割する(スケジューリングに
requestAnimationFrame()またはrequestIdleCallback()を使用する)ことで、応答性の問題を最小限に抑え、Interaction to Next Paint(INP)を改善できます。
JavaScript の配信コストを削減するパターン
JavaScript の解析 / コンパイル時間とネットワーク送信時間を短縮しようとする場合、ルートベースのチャンク化や PRPL などのパターンが役立ちます。
PRPL
PRPL(Push、Render、Pre-cache、Lazy-load)は、積極的なコード分割とキャッシュ保存によってインタラクティビティを最適化するパターンです。

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

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 では、最初のページ レンダリングが遅れる可能性があるため、慎重に評価してください。代わりに、静的に圧縮することをおすすめします)。
- パフォーマンスの未来 - Sam Saccone