実行負荷の高い JavaScript を特定する

JavaScript の実行が重いと、ページの読み込みパフォーマンスとインタラクティブ性の両方に悪影響を及ぼす可能性があります。現代のウェブ アプリケーションは、複数のソースから JavaScript を利用しており、これまで以上に JavaScript への依存度が高まっています。これには、アプリケーション コード自体(および依存するフレームワーク コード)や、チャット ウィジェットや動画プレーヤーなどの機能を追加するサードパーティ スクリプトが含まれます。バックグラウンドで動作する分析スクリプトやマーケティング スクリプトも、忘れられがちな要因です。

応答しないウェブページの根本原因を特定するのは、ウェブのパフォーマンスのトレースやプロファイリングを実行し、その結果を解釈する方法に関する専門知識が必要となるため、難しい場合があります。また、フィールド データはラボデータとは大きく異なることがよくあります。ラボデータは、実際のユーザー シナリオのごく一部を再現したものです。これにより、特にインタラクションにおいて、パフォーマンス低下の根本原因を特定することが難しくなる可能性があります。

Long Animation Frames API は、フィールドで実行負荷の高い JavaScript を特定するために使用できる軽量の API です。重いスクリプトとは、単一の長時間実行スクリプト、またはページのライフサイクル中に複数回実行されるスクリプトのことです。

導入方法

長いアニメーション フレームは、PerformanceObserver インターフェースを使用してモニタリングされます。アニメーション フレームのレンダリングに 50 ミリ秒以上かかると、long-animation-frame エントリが出力されます。このエントリには、フレームの長さやフレーム中に実行されたスクリプトなど、長いアニメーション フレームに関する情報が含まれます。

long-animation-frame エントリには、PerformanceScript オブジェクトの配列である scripts プロパティが含まれています。各 PerformanceScript オブジェクトには、長いアニメーション フレーム中に実行されたスクリプトに関する情報(スクリプトの sourceURL や duration など)が含まれます。

長いアニメーション フレームの原因となっている、実行時間が最も長いスクリプトを特定する例

// Accumulate all script entries across the page lifecycle so no
// data is lost between observer callbacks.
const allScripts = [];

const observer = new PerformanceObserver(list => {
  // Collect all script entries across frames to find the biggest offenders.
  allScripts.push(...list.getEntries().flatMap(entry => entry.scripts));

  // Group by sourceURL so you can identify which scripts contribute
  // the most total time, even if each individual invocation is short.
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource = scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));

  // Only include scripts above a certain threshold to reduce noise.
  const heavyScripts = processedScripts.filter(script => {
    return script.totalDuration > 100;
  });

  // Sort by total duration so the worst offenders appear first,
  // making it easier to prioritize optimization efforts.
  heavyScripts.sort((a, b) => b.totalDuration - a.totalDuration);

  // Log to the console for local debugging. In production, replace
  // this with a call to send the data to your analytics service.
  console.table(heavyScripts);
});

// Use buffered: true to capture any long frames that occurred before
// this observer was registered.
observer.observe({type: 'long-animation-frame', buffered: true});

ベスト プラクティス

  • 推奨: JS Self-Profiling API などの代替手段よりも、Long Animation Frames API を優先してください。JS Self-Profiling API は、ランタイム オーバーヘッドが高くなります。
  • 推奨: Long Animation Frames API には詳細な情報が多数含まれているため、重要な情報を要約する。
  • 推奨: 必要な情報を本番環境の分析サービスに送信する。

ブラウザのサポートとフォールバック戦略

Long Animation Frames API は、サポートしていないブラウザでは無視されるため、フォールバックなしで安全に使用できます。ほとんどの場合、特定されたパフォーマンス改善の機会は他のブラウザにも適用されます。