자바스크립트 시작 최적화

JavaScript를 더 많이 사용하는 사이트를 빌드할 때는 항상 쉽게 확인할 수 없는 방식으로 전송하는 비용을 지불해야 하는 경우가 있습니다. 이 도움말에서는 휴대기기에서 사이트가 빠르게 로드되고 상호작용이 가능하도록 하려면 약간의 규율이 필요한 이유를 설명합니다. JavaScript를 적게 전송하면 네트워크 전송 시간이 줄고, 코드 압축 해제에 드는 시간이 줄고, 이 JavaScript를 파싱하고 컴파일하는 데 드는 시간이 줄어듭니다.

네트워크

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

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

이는 사용자가 사용하는 유효한 네트워크 연결 유형이 실제로는 3G, 4G 또는 Wi-Fi가 아닐 수 있으므로 문제가 될 수 있습니다. 커피숍 Wi-Fi를 사용 중이지만 2G 속도의 셀룰러 핫스팟에 연결되어 있을 수 있습니다.

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

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

파싱/컴파일

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

ALT_TEXT_HERE

Bottom-Up(하향식) 및 Call Tree(호출 트리) 탭에는 정확한 파싱/컴파일 시간이 표시됩니다.

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

하지만 이 차이점이 왜 중요한가요?

ALT_TEXT_HERE

코드 파싱/컴파일에 오랜 시간이 걸리면 사용자가 사이트와 상호작용할 수 있는 속도가 크게 지연될 수 있습니다. 전송하는 JavaScript가 많을수록 사이트가 대화형이 되기 전에 파싱하고 컴파일하는 데 걸리는 시간이 길어집니다.

JavaScript는 브라우저에서 처리하는 데 크기가 비슷한 이미지나 웹 글꼴보다 비용이 더 많이 듭니다. - 톰 데이얼

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

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

파싱 및 컴파일이 느린 경우 맥락이 중요합니다. 여기서는 평균 휴대전화를 이야기하고 있습니다. 평균적인 사용자는 느린 CPU 및 GPU, L2/L3 캐시가 없고 메모리 제약이 있을 수 있는 휴대전화를 사용할 수 있습니다.

네트워크 기능과 기기 기능이 항상 일치하는 것은 아닙니다. 우수한 Fiber 연결을 사용하는 사용자에게 기기로 전송된 JavaScript를 파싱하고 평가할 최적의 CPU가 반드시 있는 것은 아닙니다. 반대의 경우도 마찬가지입니다. 네트워크 연결은 좋지 않지만 CPU는 매우 빠릅니다. — 크리스토퍼 벡스터, LinkedIn

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

ALT_TEXT_HERE
이 그래프는 다양한 클래스의 데스크톱 및 휴대기기에서 JavaScript 1MB 번들 (gzipped 약 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 제약 조건을 파악할 수 있습니다.

JavaScript를 너무 많이 전송하고 있나요? 아마도요 :)

HTTP 보관 파일 (상위 50만 개 사이트)을 사용하여 모바일의 JavaScript 상태를 분석한 결과, 사이트의 50% 가 상호작용이 가능해지는 데 14초가 넘게 걸리는 것으로 나타났습니다. 이러한 사이트는 JS 파싱 및 컴파일에 최대 4초를 소비합니다.

ALT_TEXT_HERE

JS 및 기타 리소스를 가져오고 처리하는 데 걸리는 시간을 고려하면 사용자가 페이지를 사용할 준비가 되었다고 느끼기까지 잠시 기다려야 할 수 있습니다. 더 나은 서비스를 제공해 드릴 수 있도록 노력하겠습니다.

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

실행 시간

파싱 및 컴파일뿐만 아니라 다른 작업에도 비용이 발생할 수 있습니다. JavaScript 실행(파싱/컴파일된 후 코드 실행)은 기본 스레드에서 실행되어야 하는 작업 중 하나입니다. 실행 시간이 길면 사용자가 사이트와 상호작용할 수 있는 속도도 느려질 수 있습니다.

ALT_TEXT_HERE

스크립트가 50밀리초 이상 실행되면 JS를 다운로드, 컴파일, 실행하는 데 걸리는 전체 시간만큼 대화형 시간까지가 지연됩니다. — Alex Russell

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

기타 비용

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

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

JavaScript 전송 비용 절감 패턴

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

PRPL

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

ALT_TEXT_HERE

이 변화가 미칠 수 있는 영향을 시각화해 보겠습니다.

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

ALT_TEXT_HERE

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

점진적 부트스트랩

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

주의하세요. 이 방법에는 자체 비용이 있습니다. 1) 일반적으로 상호작용을 유도할 수 있는 더 큰 HTML 응답을 전송하고 2) JavaScript 처리가 완료될 때까지 환경의 절반이 실제로 상호작용이 불가능한 기이한 상황에 사용자를 놓칠 수 있습니다.

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

ALT_TEXT_HERE
폴 루이스 작성 진보적 부트스트랩

표시되는 내용에 비례하여 코드를 로드하는 것이 이상적인 목표입니다. PRPL 및 프로그레시브 부트스트랩은 이를 달성하는 데 도움이 되는 패턴입니다.

결론

전송 크기는 저가형 네트워크에 매우 중요합니다. 파싱 시간은 CPU 바운드 기기에서 중요합니다. 이러한 비율을 낮게 유지하는 것이 중요합니다.

팀에서는 JavaScript 전송 및 파싱/컴파일 시간을 짧게 유지하기 위해 엄격한 성능 예산을 채택하여 성공을 거두었습니다. 앨릭스 러셀의 'Can You Afford It?: 실시간 웹 성능 예산'을 참고하세요.

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

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

자세히 알아보기