실행량이 많은 JavaScript 식별

과도하게 실행되는 JavaScript는 페이지 로드 성능과 상호작용에 모두 부정적인 영향을 미칠 수 있습니다. 최신 웹 애플리케이션은 그 어느 때보다도 여러 소스의 JavaScript에 크게 의존합니다. 여기에는 애플리케이션 코드 자체 (및 애플리케이션 코드가 사용하는 프레임워크 코드)와 채팅 위젯 및 동영상 플레이어와 같은 기능을 추가하는 서드 파티 스크립트가 포함됩니다. 백그라운드 분석 및 마케팅 스크립트도 흔히 사용되지만 잊어버리기 쉽습니다.

응답하지 않는 웹페이지의 근본 원인을 파악하는 것은 웹 성능 추적 또는 프로파일링을 실행하고 결과를 해석하는 데 필요한 특정 전문 지식이 필요하므로 까다로울 수 있습니다. 또한 필드 데이터는 실제 사용자 시나리오의 일부만 복제하는 실험실 데이터와 매우 다른 경우가 많습니다. 이로 인해 특히 상호작용의 경우 성능 저하의 근본 원인을 파악하기 어려울 수 있습니다.

Long Animation Frames API는 현장에서 실행 중인 무거운 JavaScript를 식별하는 데 사용할 수 있는 가벼운 API입니다. 실행량이 많은 스크립트는 장기 실행 스크립트 하나일 수도 있고 페이지 수명 주기 동안 여러 번 실행되는 스크립트일 수도 있습니다.

구현 방법

긴 애니메이션 프레임은 PerformanceObserver 인터페이스를 사용하여 모니터링됩니다. 애니메이션 프레임을 렌더링하는 데 50ms를 초과하면 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를 사용하세요.
  • 권장사항: Long Animation Frames API에는 많은 세부정보가 포함되어 있으므로 핵심 정보를 요약합니다.
  • 권장사항: 프로덕션 환경에서 분석 서비스에 필수 정보를 전송합니다.

브라우저 지원 및 대체 전략

Long Animation Frames API는 이를 지원하지 않는 브라우저에서 무시되므로 대체 없이 안전하게 사용할 수 있습니다. 대부분의 경우 Lighthouse에서 식별하는 성능 개선 기회는 다른 브라우저에도 적용됩니다.