JavaScript 시작 최적화

자바스크립트에 더 많이 의존하는 사이트를 구축할 때, 우리는 때때로 우리가 전송하는 것에 대해 쉽게 볼 수 없는 방식으로 비용을 지불합니다. 이 도움말에서는 모바일 기기에서 사이트를 빠르게 로드하고 상호작용할 수 있도록 하려면 약간의 규율이 필요한 이유를 설명합니다. JavaScript를 적게 전송하면 네트워크 전송 시간이 줄어들고, 코드 압축 해제에 소요되는 시간이 줄어들며, 이 JavaScript를 파싱하고 컴파일하는 시간이 줄어듭니다.

네트워크

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

브라우저가 리소스를 요청하면 해당 리소스를 가져온 후 압축 해제해야 합니다. JavaScript와 같은 리소스의 경우 실행 전에 파싱하고 컴파일해야 합니다.

사용자가 보유한 유효 네트워크 연결 유형이 실제로 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을 사용하는 경우 파일 이름 해싱을 참조하세요.

파싱/컴파일

다운로드 후 JavaScript의 가장 큰 비용 중 하나는 JS 엔진이 이 코드를 파싱/컴파일하는 데 걸리는 시간입니다. Chrome DevTools에서 파싱 및 컴파일은 Performance 패널의 노란색 '스크립팅' 시간의 일부입니다.

ALT_TEXT_HERE

Bottom-Up 및 Call Tree 탭에는 정확한 파싱/컴파일 타이밍이 표시됩니다.

ALT_TEXT_HERE
Chrome DevTools Performance 패널 > Bottom-Up V8의 런타임 호출 통계를 사용 설정하면 파싱 및 컴파일과 같은 단계에 소요된 시간을 확인할 수 있습니다.

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

ALT_TEXT_HERE

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

바이트 단위로 보면 브라우저에서 JavaScript를 처리하는 데 드는 비용이 동일한 크기의 이미지나 웹 글꼴보다 더 많이 듭니다. - 톰 데일

JavaScript와 비교하면 비슷한 크기의 이미지를 처리하는 데 드는 비용이 많지만 (여전히 디코딩해야 함) 평균적인 모바일 하드웨어에서는 JS가 페이지의 상호작용에 부정적인 영향을 미칠 가능성이 더 높습니다.

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

파싱과 컴파일이 느리다고 말할 때 맥락이 중요합니다. 여기서는 평균적인 휴대전화를 말합니다. 일반 사용자는 CPU와 GPU가 느리고 L2/L3 캐시가 없으며 메모리가 제한된 휴대전화를 사용할 수 있습니다.

네트워크 기능과 기기 기능이 항상 일치하는 것은 아닙니다. Fiber 연결이 우수한 사용자라고 해서 기기로 전송된 JavaScript를 파싱하고 평가하는 데 가장 적합한 CPU를 가지고 있는 것은 아닙니다. 반대의 경우도 마찬가지입니다. 네트워크 연결은 끔찍하지만 CPU는 엄청나게 빠릅니다. — 크리스토퍼 백스터, 링크드인

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

ALT_TEXT_HERE
이 그래프는 다양한 등급의 데스크톱 및 휴대기기에서 1MB JavaScript 번들 (gzip으로 압축 시 약 250KB)의 파싱 시간을 보여줍니다. 파싱 비용을 살펴볼 때는 압축 해제된 수치를 고려해야 합니다.예를 들어 약 250KB의 gzip 압축 JS는 약 1MB의 코드로 압축 해제됩니다.

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

고급형 iPhone 8에서는 CNN의 JS를 파싱/컴파일하는 데 약 4초가 걸리는 반면, 평균적인 휴대전화 (Moto G4)에서는 약 13초가 걸립니다. 이는 사용자가 이 사이트와 완전히 상호작용할 수 있는 속도에 큰 영향을 미칠 수 있습니다.

ALT_TEXT_HERE
위에서는 Apple의 A11 Bionic 칩의 성능을 더 일반적인 Android 하드웨어의 Snapdragon 617과 비교하는 파싱 시간을 보여줍니다.

이는 주머니에 있는 휴대전화뿐만 아니라 평균적인 하드웨어 (예: Moto G4)에서 테스트하는 것이 중요하다는 점을 보여줍니다. 하지만 맥락이 중요합니다. 사용자의 기기 및 네트워크 상태에 맞게 최적화하세요.

ALT_TEXT_HERE
Google 애널리틱스는 실제 사용자가 사이트에 액세스하는 데 사용하는 휴대기기 클래스에 대한 유용한 정보를 제공할 수 있습니다. 이를 통해 운영 중인 실제 CPU/GPU 제약 조건을 파악할 수 있습니다.

정말 자바스크립트를 너무 많이 전송하고 있나요? 어, 그럴 수도 있겠네요 :)

HTTP Archive (상위 약 50만 개 사이트)를 사용하여 모바일에서 JavaScript 상태를 분석하면 사이트의 50% 가 상호작용하는 데 14초 이상 걸리는 것을 확인할 수 있습니다. 이러한 사이트는 JS를 파싱하고 컴파일하는 데만 최대 4초를 소비합니다.

ALT_TEXT_HERE

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

페이지에서 중요하지 않은 JavaScript를 삭제하면 전송 시간, CPU 집약적인 파싱 및 컴파일, 잠재적인 메모리 오버헤드를 줄일 수 있습니다. 이렇게 하면 페이지가 더 빠르게 상호작용할 수 있습니다.

실행 시간

비용이 발생하는 것은 파싱과 컴파일뿐만이 아닙니다. JavaScript 실행(파싱/컴파일된 코드 실행)은 기본 스레드에서 실행되어야 하는 작업 중 하나입니다. 실행 시간이 길면 사용자가 사이트와 상호작용할 수 있는 시점이 지연될 수도 있습니다.

ALT_TEXT_HERE

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

이 문제를 해결하기 위해 JavaScript는 기본 스레드가 잠기지 않도록 작은 청크로 구성하는 것이 좋습니다. 실행 중에 수행되는 작업량을 줄일 수 있는지 살펴봅니다.

기타 비용

자바스크립트는 다음과 같은 다른 방식으로 페이지 성능에 영향을 미칠 수 있습니다.

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

JavaScript 전송 비용을 줄이는 패턴

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

PRPL

PRPL (푸시, 렌더링, 사전 캐시, 지연 로드)은 적극적인 코드 분할 및 캐싱을 통해 상호작용을 최적화하는 패턴입니다.

ALT_TEXT_HERE

이러한 영향력을 시각화해 보겠습니다.

V8의 런타임 호출 통계를 사용하여 인기 있는 모바일 사이트와 프로그레시브 웹 앱의 로드 시간을 분석합니다. 보시다시피 파싱 시간 (주황색으로 표시)은 이러한 사이트에서 많은 시간을 소비하는 부분입니다.

ALT_TEXT_HERE

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

점진적 부트스트랩

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

이 방법에는 비용이 발생하므로 주의해야 합니다. 1) 일반적으로 상호작용을 푸시할 수 있는 더 큰 HTML 응답을 전송하고, 2) JavaScript가 처리를 완료할 때까지 환경의 절반이 실제로 상호작용할 수 없는 불쾌한 골짜기에 사용자를 남겨둘 수 있습니다.

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

ALT_TEXT_HERE
Progressive Bootstrapping(폴 루이스)

뷰에 표시되는 내용에 비례하여 코드를 로드하는 것이 가장 이상적입니다. PRPL 및 점진적 부트스트래핑은 이를 달성하는 데 도움이 되는 패턴입니다.

결론

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

팀은 JavaScript 전송 및 파싱/컴파일 시간을 낮게 유지하기 위해 엄격한 성능 예산을 채택하여 성공을 거두었습니다. Alex Russell의 'Can You Afford It?: 실제 웹 성능 예산'을 참고하세요.

ALT_TEXT_HERE
아키텍처 결정이 앱 로직에 남길 수 있는 JS '헤드룸'의 양을 고려하는 것이 유용합니다.

휴대기기를 타겟팅하는 사이트를 구축하는 경우 대표적인 하드웨어에서 개발하고, JavaScript 파싱/컴파일 시간을 짧게 유지하고, 팀이 JavaScript 비용을 주시할 수 있도록 성능 예산을 채택하세요.

자세히 알아보기