JavaScript に深く依存するサイトを構築する際に、送信した内容に対して、簡単にはわからない方法で費用を支払っていることがあります。 この記事では、モバイル端末でサイトをすばやく読み込んでインタラクティブにさせたい場合に、ちょっとした 抑制 が役立つ理由を説明します。 JavaScript の量を減らすことで、ネットワーク伝送時間、コードの展開時間、JavaScript の解析やコンパイルに必要な時間が少なくなります。
ネットワーク
ほとんどのデベロッパーが JavaScript のコストについて考えるとき、ダウンロードと実行コスト の観点から考えます。 ネットワークを経由する JavaScript のバイト数が増えると、ユーザーの接続がより長く、より遅くなります。
先進国においてさえ、ユーザーが保持する 効果的なネットワーク接続の種類 が 3G や 4G、Wi-Fi ではないことがあるため、こうしたことが問題になる可能性があります。 コーヒーショップで利用する Wi-Fi の接続が、2G の速度の携帯アクセス ポイントの場合もあります。
以下の方法で、JavaScript のネットワーク転送コストを 削減 できます。
- ユーザーが必要とするコードだけを送信する。
- 縮小化
- ES5 コードを縮小化するには UglifyJS を使用します。
- ES2015+ を縮小化するには babel-minify または uglify-es を使用します。
- 圧縮
- 未使用のコードの削除。
- DevTools のコード カバレッジで削除できるまたは遅延読み込みできるコードを特定します。
- 最近のブラウザに既存のトランスパイル機能を避けるために、babel-preset-env および browserlist を使用します。 上級デベロッパーであれば、慎重な Webpack バンドルの分析によって不要な依存関係を削除できるかもしれません。
- コードを削除する方法の詳細については、tree-shaking、Closure Compiler の高度な最適化、または Moment.js のようなライブラリのための lodash-babel-plugin や webpack の ContextReplacementPlugin のようなライブラリ トリミング プラグインを参照してください。
- ネットワーク トリップを最小限に抑えるためのコード キャッシング。
- ブラウザが応答を効率的にキャッシュするように HTTP キャッシュを使用します。 変更されていないバイトが転送されないように、スクリプトの最適な有効期間(max-age)を決定し、検証トークン(ETag)を指定します。
- Service Worker のキャッシング機能はアプリケーションのネットワーク回復力を高め、V8 のコード キャッシュなどの機能に積極的にアクセスできるようになります。
- 変更されていないリソースを再獲得する必要がないように、長期キャッシングを使用します。 Webpack を使用している場合は、ファイル名のハッシュを参照してください。
解析とコンパイル
ダウンロード後、JavaScript で最もコストが かかる ことの 1 つは、JS エンジンがこのコードを 解析しコンパイルする 時間です。 Chrome DevTools では、解析とコンパイルはパフォーマンス パネルの黄色い「スクリプト」時間の一部です。
[Bottom-Up] タブと [Call Tree] タブには、解析とコンパイルの正確な時間が表示されます。

注: Runtime Call Stats に関する Performance パネルのサポートは現在試験運用版です。
有効にするには、chrome://flags/#enable-devtools-experiments -> Chrome の再起動 -> DevTools -> Settings -> Experiments -> shift を 6 回押す -> Timeline: V8 Runtime Call Stats on Timeline
というオプションにチェックを入れて DevTools を閉じて再度開きます。
しかし、なぜこれが重要なのでしょうか。
コードの解析とコンパイルに長い時間を費やすと、ユーザーがサイトとやり取りできるようになるまでの時間が大幅に遅れる場合があります。 送信する JavaScript が多いほど、サイトがインタラクティブになる前にその JavaScript の解析とコンパイルにかかる時間が長くなります。
バイト単位で比較すると、JavaScript は同等サイズのイメージや Web フォントよりもブラウザ処理の負荷がかかります — Tom Dale
JavaScript と比較すると同等サイズのイメージの処理には多くのコスト(イメージにはデコードも必要です!)がかかりますが、平均的なモバイル ハードウェアでは、JavaScript はページの対話性に悪影響を及ぼす可能性が高くなります。

解析やコンパイルが低速になる問題について考える場合、コンテキストが重要です。ここでは、平均的な スマートフォンについて考えています。 平均的なユーザーは、低速の CPU と GPU を搭載し、L2/L3 キャッシュがなく、メモリの制約を受ける可能性のあるスマートフォンを使用しているかもしれません。
ネットワーク機能とデバイスの機能は必ずしも同等とは限りません。 > 素晴らしい光ファイバー接続を使用するユーザーが、自分のデバイスに送信された JavaScript を解析して評価するのに最適な CPU を持っているとは限りません。
一方で、劣悪なネットワーク接続を使用していても、CPU は極めて高速である場合もあり得ます。 — Kristofer Baxter, LinkedIn
以下では、1MB 以下の解凍された(シンプルな)JavaScript をローエンドおよびハイエンドのハードウェアで解析するコストを確認できます。 マーケットで最速のスマートフォンと平均的なスマートフォンでは、コードの解析やコンパイルの時間差は 2〜5 倍になります。

では、CNN.com のような実際のサイトではどうなるでしょうか?
ハイエンドの iPhone 8 では、CNN の JS を解析してコンパイルするのに要する時間は 4 秒以下です。一方、平均的なスマートフォン(Moto G4)では 13 秒以下でした。 これは、ユーザーがこのサイトを完全に操作可能になるまでの速度に大きく影響する可能性があります。

これによって、あなたのポケットの中にあるスマートフォンではなく、平均的な ハードウェア(Moto G4 のような)でテストすることの重要性が強調されます。 環境は重要です。ユーザーが持つデバイスやネットワークの状況に合わせて最適化してください。

JavaScript を送りすぎていませんか?そう、多分。
HTTP Archive (上位 500K のサイト)を使用してモバイル上の JavaScript の状態を分析すると、50% のサイトでインタラクティブになるまでに 14 秒以上かかることがわかります。 これらのサイトでは、JS の解析とコンパイルだけでも最大 4 秒かかります。
JS やその他のリソースを取得して処理するのにかかる時間を考慮に入れてください。ページが使用可能になるまでユーザーがしばらく待たされることは珍しくありません。
ここでは、もっと良い方法があるはずです。
ページから重要ではない JavaScript を削除すると、転送時間、CPU に負荷がかかる解析とコンパイル、および潜在的なメモリオーバーヘッドを削減できます。 そうすることで、ページがインタラクティブになるまでの時間も短縮されます。
実行時間
コストがかかるのは、解析やコンパイルだけではありません。 JavaScript の実行 (解析しコンパイルされたコードの実行)は、メインスレッドで行わなければならないオペレーションの 1 つです。 実行時間が長いと、ユーザーがサイトとやり取りできるようになるまでの時間も長くなる可能性があります 。
スクリプトの実行に 50 ミリ秒以上かかる場合、Time-to-Interactive(操作可能になるまでの時間)は、JS のダウンロード、コンパイル、および実行にかかる全体の時間だけ遅れる — Alex Russell
これに対処するため、JavaScript を小さな塊に分けて、メインスレッドがロックされることを回避できます。 実行中の作業量を減らすことができるかどうかを調べてください。
その他のコスト
JavaScript はページのパフォーマンスに他の方法で影響を与える可能性があります。
- メモリ。 GC (ガベージ コレクション)により、ページの表示が乱れたり頻繁に一時停止したりすることがあります。 ブラウザがメモリを取り戻すと JS の実行が一時停止されるため、ブラウザが頻繁にガベージ コレクションを実行すると、実行が一時停止する頻度が上がりすぎる可能性があります。 メモリリーク や GC による頻繁な一時停止を避けて、ページの乱れを防ぎます。
- ランタイムに、長時間実行されている JavaScript がメインスレッドをブロックして、ページが応答しなくなることがあります。
作業を細かく分割(スケジューリングに
requestAnimationFrame()
またはrequestIdleCallback()
を使用)して、応答に関連する問題を最小限に抑えることができます。
JavaScript 配信コストを削減するためのパターン
JavaScript の解析やコンパイル時間とネットワーク送信時間を低速に抑えようとしている場合は、ルートベース チャンキングや PRPL のようなパターンが利用できます。
PRPL
PRPL(プッシュ、レンダリング、プリキャッシュ、遅延読み込み(レイジーロード))は、積極的なコード分割とキャッシングによって対話性を最適化するパターンです。
その影響を視覚化してみましょう。
V8 の Runtime Call Stats を使用して、人気のあるモバイルサイトとプログレッシブ ウェブアプリの読み込み時間を分析します。 ご覧のとおり、解析時間(オレンジ色で表示)は、これらのサイトのほとんどが多くの時間を費やす部分です。
PRPL を使用するサイトの Wego は、ルートの解析時間を短くすることができ、すばやくインタラクティブになっています。 上記の他のサイトの多くは、JS のコストを低く抑えるためにコード分割と Performance Budgets を採用していました。
プログレッシブ ブートストラップ
多くのサイトでは、対話性を犠牲にしてコンテンツの可視性を最適化しています。 大規模な JavaScript バンドルがある場合、最初の描画を早くするために、デベロッパーはサーバーサイド レンダリングを採用することがあります。JavaScript が最終的に取得できたときに、イベント ハンドラを添付してそれを「アップグレード」します。
これにはコストがかかるため、注意が必要です。 1)一般的に 大きな HTML 応答を送信しますが、これはインタラクションを阻害します。2)JavaScript が処理を終えるまで、操作の半分が実質的には対話的ではない「不気味の谷」にユーザーを置き去りにすることになります。
プログレッシブ ブートストラップはそれよりも優れたアプローチになるかもしれません。 最低限の機能しかないページ(現在のルートに必要な HTML/JS/CSS のみで構成されているページ)を送ります。 より多くのリソースが到着すると、アプリはより多くの機能を遅延読み込みしてロック解除することができます。

表示されているものに対応したコードを読み込むことがカギです。 PRPL とプログレッシブ ブートストラップは、これを実現するのに役立つパターンです。
まとめ
送信サイズは、ローエンド ネットワークにとって重要。 解析時間は CPU バウンドのデバイスにとって重要。 大切なのは、これらを低く抑えることです。
チームは、JavaScript の送信時間と解析やコンパイルの時間を短く抑えるために、厳しい Performance Budgets を採用することに成功しました。 モバイル向け Budgets については、Alex Russell の Can You Afford It?:Real-world Web Performance Budgets を参照してください。

モバイル端末をターゲットとするサイトを構築する場合は、代表的なハードウェアで開発し、JavaScript の解析やコンパイル時間を短く抑え、チームが JavaScript のコストを監視できるように Performance Budget を採用します。
詳細を見る
- Chrome Dev Summit 2017 - Modern Loading Best Practices
- JavaScript Start-up Performance
- Solving the web performance crisis — Nolan Lawson
- Can you afford it? Real-world performance budgets — Alex Russell
- Evaluating web frameworks and libraries — Kristofer Baxter
- Cloudflare’s Results of experimenting with Brotli (圧縮に関して。高品質の動的 Brotli は最初のページの表示を遅らせる可能性があるため、慎重に評価してください。 代わりに静的に圧縮することをお勧めします)。
- Performance Futures — Sam Saccone