본문 바로가기

JavaScript

101 Javascript Critical Rendering Path (한글) (1)

이 글은 아래 원문을 번역한 글로 의역이 있을 수 있습니다. 정확한 의미를 파악하고 싶으신 분은 원문을 참고해주시기 바랍니다.

 

원문: https://indepth.dev/posts/1498/101-javascript-critical-rendering-path

저작권 정보: https://indepth.dev/

 


이 글에서는 주요 렌더링 경로의 과정을 살펴보겠습니다.

 

정의상, 주요 렌더링 경로는 브라우저가 HTML 페이지를 가져와서 사용자에게 웹페이지를 시각화하는 동안 경로를 만드는 몇 가지 단계의 모음에 불과합니다. 이 과정에서, 우리는 브라우저가 수행하는 단계를 최적화해야 합니다.

The Document Object Model

우선, 모든 웹페이지에는 문서 객체 모델(DOM)이 있습니다. 이는 구문 분석된 HTML 페이지의 객체 기반 표현입니다. HTML의 구분이 분석되면(parsed), DOM 트리가 만들어집니다. 이 DOM 드리는 객체를 포함합니다.

간단한 HTML 코드와 함께 살펴보겠습니다. 아래 코드는 헤더, 메인 섹션, 푸터, 세 부분으로 이루어져 있습니다. 이것은 브라우저에서 렌더링 할 수 있는 가장 간단한 HTML 조각입니다. 스타일시트, "style.css"는 HTML 페이지 포맷을 위해 로드한 외부 파일입니다.

<head>
  <link rel="stylesheet" href="style.css">
  <title>101 Javascript Critical Rendering Path</title>
  <body>
    <header>
      <h1>The Rendering Path</h1>
      <p>Every step during the rendering of a HTML page, forms the path.</p>
    </header>
    <main>
         <h1>You need a dom tree</h1>
         <p>Have you ever come across the concepts behind a DOM Tree?</p>
    </main>
    <footer>
         <small>Thank you for reading!</small>
    </footer>
  </body> 
  </head>
</html>

위의 HTML 코드를 브라우저에서 DOM 트리 구조로 구문 분석하면 다음과 같이 나타납니다.

DOM Tree

모든 브라우저는 HTML 코드를 분석하는 데 시간이 걸립니다. 깔끔하고(clean) 의미론적인(semantic) 마크업은 브라우저가 HTML을 구문 분석하는 데 필요한 시간을 줄이는 데 도움을 줍니다.

CSSOM Tree

다음은 CSSOM 트리입니다. 이는 CSS 객체 모델에 불과합니다. 마찬가지로 CSS  객체 모델은 객체 기반 트리입니다. 이는 DOM 트리를 나타내는 것과 관련된 스타일을 처리합니다. 일반적으로 CSS 스타일은 상속되거나 명시적으로 선언될 수 있습니다.

background-color: white;
   color: black;
}
p{
   font-weight:400;
}
h1{
   font-size:72px;
}
small{
   text-align:left
}

위의 CSS 선언은 아래와 같은 CSSOM 트리로 나타날 것입니다.

CSSOM Tree

일반적으로 CSS는 렌더 차단 자원(render-blocking resource)으로 간주됩니다.

렌더 차단이란 무엇일까요?  렌더링 차단 리소스는 해당 리소스가 완전히 로드될 때까지 브라우저가 전체 DOM 트리를 렌더링 할 수 없도록 하는 구성 요소입니다. CSS가 완전히 로드될 때까지 렌더링을 할 수 없기 때문에 CSS는 렌더링 차단 리소스입니다. 초기에, CSS는 단일 소스로부터 제공되었습니다. 현재는 CSS 파일을 분할해서 렌더링 초기 단계에 꼭 필요한 스타일만 제공받도록 다양한 기술이 고안되었습니다.

Executing JavaScript

다음으로 자바스크립트는 DOM(문서 객체 모델)을 조작하는 데 사용되는 언어입니다. 팝업이나 carousel와 같이 DOM과 상호 작용하는 다양한 사용 사례를 생각해볼 수 있습니다. 문제는 이러한 상호 작용에 시간이 걸리기 시작하고 웹사이트의 전체 로딩 시간이 단축될 때입니다. 이것이 자바스크립트 코드가 "파서 차단(Parser Blocking)" 리소스로 알려진 이유입니다.

 

파서 차단이란 무엇일까요? 브라우저는 자바스크립트 코드를 다운로드하고 실행할 때, DOM 트리 구성을 일시 중단합니다. 자바스크립트 코드가 실행되는 순간, DOM 트리 구성이 이어집니다.

 

"이것이 자바스크립트가 비싼 자원인 이유입니다."

실제 예시와 함께 알아보기

글자와 그림을 나타내는 간단한 HTML 코드의 데모입니다. 보시다시피 전체 페이지를 나타내는 데 40ms 정도밖에 걸리지 않았습니다. 이미지가 있음에도, 페이지가 표시되는 데 걸리는 시간은 짧습니다. 이는 처음 화면을 그릴 때, 이미지가 중요한 자원으로 다뤄지지 않았기 때문입니다. 기억하세요. 주요 렌더링 경로(the critical rendering path)는 HTML, CSS 그리고 자바스크립트에 관한 모든 것입니다. 가능한 한 빨리 이미지를 표시하도록 노력해야 하지만, 이미지가 초기 렌더링을 차단하지는 않을 것입니다.

이제 코드에 css를 추가해봅시다.

보시다시피 추가 요청이 발생하고 잇습니다. html 파일을 로드하는 데 걸리는 시간은 적음에도 불구하고 전체 페이지를 처리하고 나타내는 데 걸리는 시간은 거의 10배 정도 증가했습니다. 왜일까요?

  1. 일반 HTML은 가져오기(fetching) 및 구문 분석(parsing)을 많이 포함하지 않습니다. 그러나 CSS 파일은, (위에서 설명한) CSSOM을 구성해야 합니다. HTML DOM과 CSS CSSOM 둘 다 구축해야 하기 때문에 시간이 많이 걸리게 됩니다.
  2. 또한, 스크립트에 자바스크립트가 포함된 경우, domContentLoaded 이벤트가 발생하지 않습니다. 이런 경우 자바스크립트에서 CSSOM을 사용할(query)할 가능성이 높기 때문입니다. 따라서, 자바스크립트를 실행하기 전에 CSS 파일은 완전히 다운로드하고 구문 분석을 끝내야 합니다.

참고: domContentLoaded는 HTML DOM을 완전히 구문 분석하고 로드했을 때 이벤트가 발생합니다. 이 이벤트는 이미지나 서브프레임, 또는 스타일시트가 완전히 로드될 때까지 기다리지 않습니다. 유일한 기준은 문서(Document)의 로드입니다. DOM의 구문 분석과 로드가 완료됐는지 여부를 확인하기 위해 window에 이벤트를 추가할 수 있습니다. 이벤트 리스너는 아래처럼 만들 수 있습니다.

window.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM Content Loaded Event');
});
  1. 외부 자바스크립트 파일을 인라인 스크립트로 대체해도, 성능이 드라마틱하게 변하지는 않습니다. 결국은 CSSOM이 구성되어야 하기 때문입니다. 외부 스크립트를 고려하고 있다면, 제안하는 방법이 있습니다. "async"를 추가하는 것입니다. 그러면 파서를 차단하지 않습니다. async에 대한 자세한 내용은 다음에 설명하겠습니다.

용어 바로잡기

문제를 해결하기 전에, 주요 렌더링 경로(the Critical Rendering Path)를 설명하는 올바를 용어를 알아보겠습니다.

  1. 주요 리소스(Critical Resource): 페이지 렌더링을 차단할 수 있는 모든 리소스
  2. 주요 경로 길이(Critical Path Length): 페이지를 빌드하기 위해 필요한 모든 주요 리소스를 가져오는 데 소요되는 총 왕복 횟수
  3. 주요 바이트(Critical Bytes): 페이지를 빌드하고 완성하기 위해 전송되는 총 바이트 수

첫 번째 예제인 일반 HTML 스크립트의 경우, 위 값들은 다음과 같습니다.

  • 1 Critical Resource
  • 1 Round Trip
  • 192 Bytes of data

두 번째 예제인 일반 HTML과 외부 CSS 스크립트의 경우, 위 값들은 다음과 같습니다.

  • 2 Critical Resource
  • 2 Round Trip
  • 400 Bytes of data

만약 프레임워크나 일반 HTML + CSS + Javascript 코드에서 Critical Rendering Path를 최적화하고자 한다면, 위에 있는 측정 항목들을 사용하고 개선해야 합니다.

  • 가능한 한 적은 수의 주요 리소스(Critical Resource)를 갖는 것이 중요합니다. 이는 CPU와 브라우저에 작업을 줄입니다.
  • 다음으로, 다운로드에 걸리는 시간과 리소스의 크기 간의 종속성은 깨지지 않습니다. 리소스가 크면 주요 경로 길이(Critical Path Length)가 늘어납니다. 리소스를 가져오기 위한 왕복 이동(Round Trip) 횟수도 더 많을 것입니다.
  • 마지막으로 가장 중요한 것은, 주요 바이트(Critical Bytes)는 최대한 작아야 합니다. 가능하면 다운로드할 바이트를 주요하지 않은 리소스(non-critical resource)로 변환하거나 완전히 생략해야 합니다. 그럼에도 렌더링을 차단하는 주요 리소스로 다운로드되는 바이트가 있다면, 압축을 통해 전송을 최적화해야 합니다.