サードパーティの JavaScript を最適化する

サードパーティ スクリプトはパフォーマンスに影響します。そのため、定期的に監査し、効率的な読み込み手法を使用することが重要です。この Codelab では、サードパーティ リソースの読み込みを最適化する方法について説明します。次の手法について説明します。

  • スクリプトの読み込みを遅らせる

  • 重要でないリソースの遅延読み込み

  • 必要なオリジンへの事前接続

付属のサンプルアプリは、サードパーティ ソースから取得した次の 3 つの機能を含むシンプルなウェブページです。

  • 動画の埋め込み

  • 折れ線グラフをレンダリングするデータ可視化ライブラリ

  • ソーシャル メディア共有ウィジェット

サードパーティ リソースがハイライト表示されたページのスクリーンショット。
サンプルアプリのサードパーティ リソース。

まずアプリのパフォーマンスを測定し、次に各手法を適用してアプリのパフォーマンスのさまざまな側面を改善します。

パフォーマンスの測定

まず、サンプルアプリを全画面表示で開きます。

  1. [Remix to Edit] をクリックして、プロジェクトを編集可能にします。
  2. サイトをプレビューするには、[アプリを表示] を押してから、[全画面表示] 全画面表示 を押します。

ページに対して Lighthouseパフォーマンス チェックを実行して、ベースライン パフォーマンスを設定します。

  1. Ctrl+Shift+J(Mac の場合は Command+Option+J)キーを押して DevTools を開きます。
  2. [Lighthouse] タブをクリックします。
  3. [モバイル] をクリックします。
  4. [パフォーマンス] チェックボックスをオンにします。([監査] セクションで残りのチェックボックスをオフにできます)。
  5. [高速 3G シミュレーション、CPU 4 倍減速] をクリックします。
  6. [ストレージを消去] チェックボックスをオンにします。
  7. [監査を実行] をクリックします。

マシンで監査を実行すると、正確な結果は異なる場合がありますが、最初のコンテンツの描画(FCP)時間がかなり長いことに気づくはずです。Lighthouse では、レンダリングをブロックするリソースを排除する必要なオリジンに事前接続するという 2 つの改善案が提案されます。(指標がすべて緑色の場合でも、最適化によって改善が見込めます)。

2.4 秒の FCP と 2 つの改善案(レンダリングをブロックするリソースを排除、必要なオリジンに事前接続)を示す Lighthouse 監査のスクリーンショット。

サードパーティの JavaScript を遅らせる

レンダリングをブロックするリソースを除去する監査で、d3js.org からのスクリプトを遅らせることで時間を節約できることが判明しました。

d3.v3.min.js スクリプトがハイライト表示された「レンダリングをブロックするリソースを除去」監査のスクリーンショット。

D3.js は、データ ビジュアリゼーションを作成するための JavaScript ライブラリです。サンプルアプリの script.js ファイルでは、D3 ユーティリティ関数を使用して SVG 折れ線グラフを作成し、ページに追加しています。ここでは処理の順序が重要です。script.js は、ドキュメントが解析され、D3 ライブラリが読み込まれた後に実行する必要があります。そのため、index.html の終了タグ </body> の直前に含まれています。

ただし、D3 スクリプトはページの <head> に含まれているため、残りのドキュメントの解析がブロックされます。

ヘッダー内のスクリプト タグがハイライト表示された index.html のスクリーンショット。

2 つのマジック属性を script タグに追加すると、パーサーをブロックしないようにできます。

  • async を使用すると、スクリプトはバックグラウンドでダウンロードされ、ダウンロードが完了すると最初の機会に実行されます。

  • defer を使用すると、スクリプトがバックグラウンドでダウンロードされ、パースが完了した後に実行されます。

このグラフはページ全体にとって重要ではなく、ほとんどの場合折り返しの下にあるため、defer を使用して、パーサーのブロックがないことを確認します。

ステップ 1: defer 属性を使用してスクリプトを非同期で読み込む

index.html の 17 行目に、<script> 要素に defer 属性を追加します。

<script src="https://d3js.org/d3.v3.min.js" defer></script>

ステップ 2: 手順が正しいことを確認する

D3 が延期されたため、D3 の準備が整う前に script.js が実行され、エラーが発生します。

defer 属性を持つスクリプトは、指定された順序で実行されます。D3 の準備が整った後に script.js が実行されるようにするには、defer を追加して、D3 <script> 要素の直後にあるドキュメントの <head> に移動します。パーサーがブロックされなくなり、ダウンロードがより早く開始されるようになりました。

<script src="https://d3js.org/d3.v3.min.js" defer></script>
<script src="./script.js" defer></script>

サードパーティ リソースを遅延読み込みする

スクロールしなければ見えない範囲のリソースはすべて、遅延読み込みの候補となります。

サンプルアプリでは、iframe に YouTube 動画が埋め込まれています。ページが実行するリクエストの数と、埋め込まれた YouTube iframe から送信されたリクエストを確認するには:

  1. サイトをプレビューするには、[アプリを表示] を押してから、[全画面表示] 全画面表示 を押します。
  2. Ctrl+Shift+J(Mac の場合は Command+Option+J)キーを押して DevTools を開きます。
  3. [ネットワーク] タブをクリックします。
  4. [キャッシュを無効にする] チェックボックスをオンにします。
  5. [スロットリング] プルダウン メニューで [高速 3G] を選択します。
  6. ページを再読み込みする。

DevTools の [Network] パネルのスクリーンショット。

[ネットワーク] パネルには、ページが合計 28 件のリクエストを行い、圧縮されたリソースを約 1 MB 転送したことが示されています。

YouTube iframe が行ったリクエストを特定するには、[開始元] 列で動画 ID 6lfaiXM6waw を探します。すべてのリクエストをドメイン別にグループ化するには:

  • [ネットワーク] パネルで、列タイトルを右クリックします。

  • プルダウン メニューで [Domains] 列を選択します。

  • リクエストをドメイン別に並べ替えるには、[ドメイン] 列のタイトルをクリックします。

新しい並べ替えでは、Google ドメインへのリクエストがさらにあることがわかります。YouTube iframe は、スクリプト、スタイルシート、画像、フォントに対して合計 14 件のリクエストを行います。ただし、ユーザーが実際に下にスクロールして動画を再生しない限り、それらのアセットはすべて必要ありません。

ユーザーがページのそのセクションまで下にスクロールするまで動画を遅延読み込みすることで、ページが最初に行うリクエストの数を削減できます。このアプローチでは、ユーザーデータが保存され、初回読み込みが高速化されます。

遅延読み込みを実装する方法の一つは、Intersection Observer を使用することです。これは、要素がブラウザのビューポートに表示されたり、ビューポートから消えたりした場合を通知するブラウザ API です。

ステップ 1: 動画の初期読み込みを防ぐ

動画の iframe を遅延読み込みするには、まず通常の方法で読み込まれないようにする必要があります。そのためには、src 属性を data-src 属性に置き換えて、動画 URL を指定します。

<iframe width="560" height="315" data-src="https://www.youtube.com/embed/lS9D6w1GzGY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

data-srcデータ属性で、標準の HTML 要素に追加情報を格納できます。データ属性には、先頭が「data-」であれば任意の名前を付けることができます。

src のない iframe は読み込まれません。

ステップ 2: Intersection Observer を使用して動画を遅延読み込みする

ユーザーが動画までスクロールしたときに動画を読み込むには、そのタイミングを把握する必要があります。このような場合に役立つのが Intersection Observer API です。Intersection Observer API を使用すると、トラッキングする要素がビューポートに移動したときやビューポートから移動したときに実行されるコールバック関数を登録できます。

まず、新しいファイルを作成して lazy-load.js という名前を付けます。

  • [新しいファイル] をクリックして名前を付けます。
  • [Add This File] をクリックします。

ドキュメントのヘッダーにスクリプトタグを追加します。

 <script src="/lazy-load.js" defer></script>

lazy-load.js で新しい IntersectionObserver を作成し、実行するコールバック関数を渡します。

// create a new Intersection Observer
let observer = new IntersectionObserver(callback);

次に、observe メソッドの引数として渡すことで、observer に視聴するターゲット要素(この場合は動画 iframe)を指定します。

// the element that you want to watch
const element = document.querySelector('iframe');

// register the element with the observe method
observer.observe(element);

callback は、IntersectionObserverEntry オブジェクトのリストと IntersectionObserver オブジェクト自体を受け取ります。各エントリには、target 要素と、そのサイズ、位置、ビューポートに表示された時間などを記述するプロパティが含まれています。IntersectionObserverEntry のプロパティの 1 つが isIntersecting です。これは、要素がビューポートに入ったときに true に等しいブール値です。

この例では、targetiframe です。target がビューポートに入ると、isIntersectingtrue になります。この動作を確認するには、callback を次の関数に置き換えます。

let observer = new IntersectionObserver(callback);
let observer = new IntersectionObserver(function(entries, observer) {
    entries.forEach(entry => {
      console.log(entry.target);
      console.log(entry.isIntersecting);
    });
  });
  1. サイトをプレビューするには、[アプリを表示] を押してから、[全画面表示] 全画面表示 を押します。
  2. Ctrl+Shift+J(Mac の場合は Command+Option+J)キーを押して DevTools を開きます。
  3. [コンソール] タブをクリックします。

上下にスクロールしてみます。isIntersecting の値が変更され、ターゲット要素がコンソールに記録されます。

ユーザーがスクロールして動画の位置に移動したときに動画を読み込むには、isIntersecting を条件として loadElement 関数を実行します。この関数は、iframe 要素の data-src から値を取得し、iframe 要素の src 属性として設定します。この置換により、動画の読み込みがトリガーされます。次に、動画が読み込まれたら、observerunobserve メソッドを呼び出して、ターゲット要素の視聴を停止します。

let observer = new IntersectionObserver(function (entries, observer) {
  entries.forEach(entry => {
    console.log(entry.target);
    console.log(entry.isIntersecting);
  });
});
    if (entry.isIntersecting) {
      // do this when the element enters the viewport
      loadElement(entry.target);
      // stop watching
      observer.unobserve(entry.target);
    }
  });
});

function loadElement(element) {
  const src = element.getAttribute('data-src');
  element.src = src;
}

ステップ 3: パフォーマンスを再評価する

リソースのサイズと数にどのような変化があったかを確認するには、DevTools の [ネットワーク] パネルを開き、ページを再読み込みします。[ネットワーク] パネルには、ページが 14 件のリクエストを送信し、260 KB しか使用していないことが示されています。大きな改善です。

ページを下にスクロールし、[ネットワーク] パネルに注目します。動画にアクセスすると、ページで追加のリクエストがトリガーされます。

必須のドメインへの事前接続

重要でない JavaScript を遅らせ、YouTube リクエストを遅延読み込みしたので、残りのサードパーティ コンテンツを最適化する準備が整いました。

リンクに rel=preconnect 属性を追加すると、そのリソースのリクエストが行われる前に、ドメインへの接続を確立するようブラウザに指示します。この属性は、ページに必要なリソースを提供するオリジンに最適です。

最初のステップで実行した Lighthouse 監査の必要なオリジンへの事前接続で、staticxx.facebook.com と youtube.com に早期接続を確立することで、約 400 ミリ秒の時間を節約できることが示唆されています。

必須のドメインへの事前接続の監査。staticxx.facebook.com ドメインがハイライト表示されています。

YouTube 動画が遅延読み込みされるようになったため、残るのは、ソーシャル メディアの共有ウィジェットのソースである staticxx.facebook.com のみです。このドメインへの早期接続を確立するには、ドキュメントの <head><link> タグを追加するだけです。

  <link rel="preconnect" href="https://staticxx.facebook.com">

パフォーマンスを再評価する

最適化後のページの状態は次のとおりです。Codelab のパフォーマンスを測定するセクションの手順に沿って、別の Lighthouse 監査を実行します。

1 秒の FCP と 99 のパフォーマンス スコアが表示された Lighthouse 監査。