자바스크립트 시작 최적화

Addy Osmani
Addy Osmani

자바스크립트에 더욱 의존하는 사이트를 구축함에 따라 Google은 때때로 쉽게 볼 수 없는 방식으로 전송된 항목에 대한 비용을 지불하기도 합니다. 이 도움말에서는 사이트가 휴대기기에서 빠르게 로드되고 상호작용이 가능하도록 하는 데 간단한 규칙이 도움이 되는 이유를 알아보겠습니다. 더 적은 자바스크립트를 제공하면 네트워크 전송에 드는 시간이 줄어들고, 코드 압축 해제에 드는 시간과 자바스크립트 파싱 및 컴파일에 드는 시간이 줄어듭니다.

네트워크

대부분의 개발자는 자바스크립트의 비용을 다운로드 및 실행 비용의 관점에서 생각합니다. 유선으로 더 많은 바이트의 자바스크립트를 전송하면 사용자의 연결이 느릴수록 시간이 오래 걸립니다.

브라우저가 리소스를 요청할 때 리소스를 가져온 다음 압축을 해제해야 합니다. 자바스크립트와 같은 리소스의 경우 실행 전에 파싱 및 컴파일해야 합니다.

사용자의 효과적인 네트워크 연결 유형이 실제로 3G, 4G 또는 Wi-Fi가 아닐 수 있으므로 이는 문제가 될 수 있습니다. 카페의 Wi-Fi를 사용하지만 2G 속도의 모바일 핫스팟에 연결되어 있을 수 있습니다.

다음을 통해 자바스크립트의 네트워크 전송 비용을 줄일 수 있습니다.

  • 사용자에게 필요한 코드만 전송합니다.
    • 코드 분할을 사용하여 JavaScript를 중요한 부분과 그렇지 않은 부분으로 나눕니다. webpack과 같은 모듈 번들러는 코드 분할을 지원합니다.
    • 중요하지 않은 코드의 지연 로드
  • 축소
  • 압축
    • 최소한 gzip을 사용하여 텍스트 기반 리소스를 압축하세요.
    • Brotli를 사용하는 것이 좋습니다(q11). Brotli는 압축률에서 gzip을 능가합니다. 그 결과 CertSimple은 압축된 JS 바이트 크기를 17%, LinkedIn은 로드 시간을 4% 절약할 수 있었습니다.
  • 사용하지 않는 코드 삭제
  • 코드 캐싱으로 네트워크 트립 최소화
    • 브라우저가 응답을 효과적으로 캐시하도록 하려면 HTTP 캐싱을 사용합니다. 스크립트의 최적 수명 (max-age)을 결정하고 유효성 검사 토큰 (ETag)을 제공하여 변경되지 않은 바이트를 전송하지 않도록 합니다.
    • 서비스 워커 캐싱을 사용하면 앱 네트워크의 복원력이 우수하고 V8의 코드 캐시와 같은 기능에 손쉽게 액세스할 수 있습니다.
    • 장기 캐싱을 사용하면 변경되지 않은 리소스를 다시 가져올 필요가 없습니다. Webpack을 사용하는 경우 파일 이름 해싱을 참고하세요.

파싱/컴파일

다운로드가 완료되면 자바스크립트의 가장 큰 비용 중 하나는 JS 엔진이 이 코드를 파싱/컴파일하는 시간입니다. Chrome DevTools에서 파싱 및 컴파일은 성능 패널의 노란색 '스크립팅' 시간에 포함됩니다.

ALT_TEXT_HERE

Bottom-Up 및 Call Tree 탭은 정확한 파싱/컴파일 타이밍을 보여줍니다.

ALT_TEXT_HERE
Chrome DevTools Performance 패널 > Bottom-Up. V8의 Runtime Call Stats를 사용 설정하면 Parse and Compile 등의 단계에서 소요된 시간을 확인할 수 있습니다.

그런데 이것이 왜 중요할까요?

ALT_TEXT_HERE

코드를 파싱/컴파일하는 데 오랜 시간을 소비하면 사용자가 사이트와 상호작용할 수 있는 시간이 크게 지연될 수 있습니다. 더 많은 자바스크립트를 전송할수록 사이트에서 상호작용하기 전에 파싱 및 컴파일하는 데 시간이 더 오래 걸립니다.

바이트 단위, 자바스크립트는 동일한 크기의 이미지나 웹 글꼴보다 브라우저에서 처리하는 비용이 더 높음 — Tom Dale

자바스크립트와 비교하면 동일한 크기의 이미지 (여전히 디코딩 필요)를 처리하는 데는 많은 비용이 들지만 평균적인 모바일 하드웨어에서 JS는 페이지 상호작용에 부정적인 영향을 미칠 가능성이 더 큽니다.

ALT_TEXT_HERE
JavaScript와 이미지 바이트는 비용이 매우 다릅니다. 이미지는 일반적으로 기본 스레드를 차단하거나 디코딩 및 래스터화 중에 인터페이스가 상호작용하는 것을 방지하지 않습니다. 하지만 자바스크립트는 파싱, 컴파일, 실행 비용으로 인해 상호작용을 지연시킬 수 있습니다.

파싱과 컴파일이 느리다고 말할 때는 컨텍스트가 중요합니다. 여기서는 평균적인 휴대전화에 관해 이야기하고 있습니다. 평균적인 사용자는 CPU와 GPU가 느리고 L2/L3 캐시가 없으며 메모리 제약이 있는 휴대전화를 사용할 수 있습니다.

네트워크 성능과 장치 성능이 항상 일치하지는 않습니다. 우수한 광섬유 연결이 있는 사용자라고 해서 기기에 전송된 자바스크립트를 파싱하고 평가하기 위한 CPU가 반드시 최고의 것은 아닙니다. 그 반대의 경우도 마찬가지입니다. 네트워크 연결은 열악하지만 CPU는 엄청나게 빠릅니다. — Kristofer Baxter, LinkedIn

아래에서 저사양 하드웨어와 고급 하드웨어에서 약 1MB의 압축 해제된 (단순) JavaScript를 파싱하는 비용을 확인할 수 있습니다. 시중에서 판매 중인 가장 빠른 휴대전화와 평균 휴대전화 간에 코드를 파싱/컴파일하는 데 소요되는 시간은 2~5배 정도 다릅니다.

ALT_TEXT_HERE
이 그래프는 데스크톱과 클래스가 다른 휴대기기에서 JavaScript 1MB 번들 (~250KB gzip됨)의 파싱 시간을 강조 표시합니다. 파싱 비용을 볼 때 압축 해제된 크기를 고려해야 합니다.예를 들어 약 250KB의 gzip으로 압축된 JS는 ~1MB의 코드로 압축 해제됩니다.

CNN.com과 같은 실제 사이트는 어떨까요?

CNN의 자바스크립트 파싱/컴파일이 평균적인 휴대전화 (Moto G4)에서 최대 13초인 반면 고급형 iPhone 8에서는 최대 4초밖에 걸리지 않습니다. 이는 사용자가 얼마나 빠르게 사이트와 완전히 상호작용할 수 있는지에 상당한 영향을 줄 수 있습니다.

ALT_TEXT_HERE
위에는 Apple의 A11 Bionic 칩의 성능을 평균적인 Android 하드웨어의 Snapdragon 617과 비교하는 파싱 시간이 나와 있습니다.

이는 주머니에 있는 휴대전화뿐만 아니라 일반적인 하드웨어 (예: Moto G4)에서 테스트하는 것의 중요성을 강조합니다. 컨텍스트도 중요하지만, 사용자의 기기 및 네트워크 조건에 맞게 최적화해야 합니다.

ALT_TEXT_HERE
Google 애널리틱스는 실제 사용자가 사이트에 액세스하는 데 사용하는 휴대기기 등급에 대한 통계를 제공할 수 있습니다. 이를 통해 애플리케이션이 작동하는 실제 CPU/GPU 제약 조건을 이해할 수 있습니다.

너무 많은 자바스크립트를 전송하고 있지는 않나요? 그럴 수도 있겠네요 :)

HTTP 보관 파일 (상위 50만 개 사이트)을 사용하여 모바일의 자바스크립트 상태를 분석하면 사이트의 50% 가 상호작용을 하는 데 14초 이상 걸리는 것을 확인할 수 있습니다. 이러한 사이트는 JavaScript를 파싱하고 컴파일하는 데 최대 4초가 걸립니다.

ALT_TEXT_HERE

JS 및 기타 리소스를 가져오고 처리하는 데 걸리는 시간을 고려하면 사용자가 페이지를 사용할 준비가 되었다고 느낄 때까지 한동안 기다려야 하는 것은 당연합니다. 이 부분은 확실히 개선할 수 있습니다.

페이지에서 중요하지 않은 자바스크립트를 삭제하면 전송 시간, CPU를 많이 사용하는 파싱 및 컴파일, 잠재적인 메모리 오버헤드를 줄일 수 있습니다. 이렇게 하면 페이지에서 상호작용이 더 빠르게 진행되도록 할 수도 있습니다.

실행 시간

파싱하고 컴파일하는 것뿐만 아니라 비용이 발생할 수 있습니다. 자바스크립트 실행(파싱/컴파일 후 코드 실행)은 기본 스레드에서 발생해야 하는 작업 중 하나입니다. 또한 실행 시간이 길면 사용자가 사이트와 상호작용할 수 있는 시간이 길어질 수 있습니다.

ALT_TEXT_HERE

스크립트가 50ms 이상 실행되면 상호작용까지의 시간이 JS를 다운로드, 컴파일, 실행하는 데 걸리는 전체 시간만큼 지연됩니다. - Alex Russell

이 문제를 해결하기 위해 JavaScript는 작은 크기의 이점을 활용하여 기본 스레드에 고정되는 것을 방지합니다. 실행 중에 수행 중인 작업의 양을 줄일 수 있는지 살펴봅니다.

기타 비용

JavaScript는 다음과 같은 다른 방식으로 페이지 성능에 영향을 미칠 수 있습니다.

  • 메모리. GC (가비지 컬렉션)로 인해 페이지에서 버벅거림이나 일시중지가 자주 발생하는 것처럼 보일 수 있습니다. 브라우저가 메모리를 회수하면 JS 실행이 일시중지되므로 가비지를 자주 수집하는 브라우저가 원하는 것보다 더 자주 실행을 일시중지할 수 있습니다. 메모리 누수와 빈번한 gc 중지를 방지하여 페이지에 버벅거림이 없도록 합니다.
  • 런타임 시 장기 실행 자바스크립트는 기본 스레드를 차단하여 페이지가 응답하지 않게 할 수 있습니다. 작업을 더 작은 조각으로 나누면 (예약에 requestAnimationFrame() 또는 requestIdleCallback() 사용) 응답성 문제를 최소화하여 다음 페인트에 대한 상호작용 (INP)을 개선할 수 있습니다.

자바스크립트 전송 비용 감소 패턴

JavaScript의 파싱/컴파일 및 네트워크 전송 시간을 느리게 유지하려는 경우 경로 기반 청크 또는 PRPL과 같은 도움이 될 수 있는 패턴이 있습니다.

사전 등록

PRPL (Push, Render, Pre-cache, Lazy-load)은 적극적인 코드 분할 및 캐싱을 통해 상호작용에 최적화된 패턴입니다.

ALT_TEXT_HERE

어떤 영향을 줄 수 있는지 시각화해 봅시다.

V8의 Runtime Call Stats를 사용하여 인기 모바일 사이트 및 프로그레시브 웹 앱의 로드 시간을 분석합니다. 이와 같이 파싱 시간 (주황색)은 이러한 사이트 중 상당수가 소비하는 시간의 상당 부분을 차지합니다.

ALT_TEXT_HERE

PRPL을 사용하는 사이트인 Wego는 경로의 파싱 시간을 낮게 유지하여 매우 빠르게 상호작용합니다. 위의 다른 많은 사이트에서는 자바스크립트 비용을 낮추기 위해 코드 분할 및 성능 예산을 채택했습니다.

점진적 부트스트랩

많은 사이트가 상호작용에 많은 비용을 들여 콘텐츠 가시성을 최적화합니다. 개발자는 대규모 JavaScript 번들이 있을 때 빠른 첫 페인트를 가져오기 위해 서버 측 렌더링을 사용하는 경우가 있습니다. 그런 다음 JavaScript를 최종적으로 가져올 때 이를 '업그레이드'하여 이벤트 핸들러를 연결합니다.

비용이 발생하므로 주의하세요. 1) 일반적으로 상호작용을 푸시할 수 있는 더 큰 HTML 응답을 전송하게 되고, 2) 사용자를 자바스크립트 처리가 완료될 때까지 경험의 절반이 실제로 상호작용할 수 없는 불명확한 계곡에 빠질 수 있습니다.

점진적 부트스트랩이 더 나은 접근 방식일 수 있습니다. 현재 경로에 필요한 HTML/JS/CSS로만 구성된 최소한의 기능 페이지를 전송합니다. 더 많은 리소스가 도착하면 앱이 더 많은 기능을 지연 로드하고 잠금 해제할 수 있습니다.

ALT_TEXT_HERE
폴 루이스의 점진적 부트스트랩

뷰에 보이는 내용에 비례하여 코드를 로드하는 것이 바로 가능합니다. PRPL 및 점진적 부트스트랩은 이를 달성하는 데 도움이 되는 패턴입니다.

결론

전송 크기는 저사양 네트워크에 매우 중요합니다. 파싱 시간은 CPU에 바인딩된 기기에 중요합니다. 낮게 유지하는 것이 중요합니다.

팀에서 엄격한 성능 예산을 채택하여 JavaScript 전송 및 파싱/컴파일 시간을 낮게 유지하는 데 성공했습니다. 자세한 내용은 Alex Russell의 'Can You Afford It?: 실제 웹 성능 예산'을 참조하세요.

ALT_TEXT_HERE
아키텍처에 따른 결정으로 인해 앱 로직에 JS '헤드룸'이 얼마나 남을 수 있는지 고려하는 것이 좋습니다.

휴대기기를 타겟팅하는 사이트를 빌드하는 경우 대표적인 하드웨어에서 개발하기 위해 최선을 다하고, 자바스크립트 파싱/컴파일 시간을 낮게 유지하고, 팀이 자바스크립트 비용을 확인할 수 있도록 성능 예산을 채택하세요.

자세히 알아보기