브라우저에서 페이지를 렌더링하려면 먼저 DOM 및 CSSOM 트리를 구성해야 합니다. 따라서 HTML과 CSS를 가능한 한 빨리 브라우저에 제공해야 합니다.
요약
- 바이트 → 문자 → 토큰 → 노드 → 객체 모델.
- HTML 마크업은 DOM (문서 객체 모델)으로 변환되고, CSS 마크업은 CSSOM (CSS Object Model)으로 변환됩니다.
- DOM 및 CSSOM은 독립적인 데이터 구조입니다.
- Chrome DevTools의 Performance 패널을 사용하면 DOM 및 CSSOM의 생성 및 처리 비용을 캡처하고 검사할 수 있습니다.
DOM (문서 객체 모델)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
가장 단순한 경우인 몇 개의 텍스트와 단일 이미지가 포함된 일반 HTML 페이지부터 살펴보겠습니다. 브라우저는 이 페이지를 어떻게 처리하나요?
- 변환: 브라우저가 디스크 또는 네트워크에서 HTML의 원시 바이트를 읽고 지정된 파일 인코딩 (예: UTF-8)에 따라 개별 문자로 변환합니다.
- 토큰화: 브라우저는 W3C HTML5 표준(예: '<html>', '<body>') 및 꺾쇠괄호로 묶인 기타 문자열로 지정된 문자열을 고유한 토큰으로 변환합니다. 각 토큰에는 특별한 의미와 자체 규칙 세트가 있습니다.
- 렉싱: 방출된 토큰은 해당 속성과 규칙을 정의하는 '객체'로 변환됩니다.
- DOM 생성: 마지막으로, HTML 마크업이 여러 태그 (일부 태그는 다른 태그 내에 포함됨) 간의 관계를 정의하기 때문에 생성된 객체는 원래 마크업에 정의된 상위-하위 관계도 캡처하는 트리 데이터 구조로 연결됩니다. 즉, HTML 객체는 body 객체의 상위 요소이고 body는 paragraph 객체의 상위 요소입니다.
이 전체 프로세스의 최종 출력은 간단한 페이지의 문서 객체 모델 (DOM)이며, 브라우저는 이후 모든 페이지 처리에 이 DOM을 사용합니다.
브라우저는 HTML 마크업을 처리할 때마다 위의 모든 단계를 거칩니다. 바이트를 문자로 변환하고, 토큰을 식별하고, 토큰을 노드로 변환하고, DOM 트리를 빌드합니다. 특히 처리할 HTML이 많은 경우에는 전체 프로세스에 다소 시간이 걸릴 수 있습니다.
Chrome DevTools를 열고 페이지가 로드되는 동안 타임라인을 기록하면 이 단계를 실행하는 데 소요된 실제 시간을 확인할 수 있습니다. 위의 예에서는 HTML 청크를 DOM 트리로 변환하는 데 약 5ms가 걸렸습니다. 크기가 큰 페이지의 경우 이 프로세스가 훨씬 더 오래 걸릴 수 있습니다. 원활한 애니메이션을 만들 때 브라우저에서 대량의 HTML을 처리해야 한다면 병목 현상이 쉽게 발생할 수 있습니다.
DOM 트리는 문서 마크업의 속성과 관계를 캡처하지만 요소가 렌더링될 때 어떻게 보이는지는 알려주지 않습니다. 이는 CSSOM의 책임입니다.
CSS 개체 모델 (CSSOM)
브라우저가 간단한 페이지의 DOM을 구성하는 동안 외부 CSS 스타일시트 style.css
를 참조하는 문서의 헤드 섹션에서 링크 태그를 발견했습니다. 페이지를 렌더링하는 데 이 리소스가 필요하다고 판단한 후 즉시 이 리소스에 대한 요청을 전달하고, 요청은 다음 콘텐츠와 함께 반환됩니다.
body {
font-size: 16px;
}
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
HTML 마크업 내에서 직접 (인라인) 스타일을 선언할 수도 있지만, CSS를 HTML과 별도로 유지하면 콘텐츠와 디자인을 별도의 문제로 처리할 수 있습니다. 즉, 디자이너는 CSS 작업을 할 수 있고, 개발자는 HTML에 집중할 수 있습니다.
HTML과 마찬가지로 수신된 CSS 규칙을 브라우저가 이해하고 처리할 수 있는 형식으로 변환해야 합니다. 따라서 HTML 대신 CSS에 대해 HTML 프로세스를 반복합니다.
CSS 바이트는 문자, 토큰, 노드로 변환되며 마지막으로 'CSS 객체 모델'(CSSOM)이라는 트리 구조에 연결됩니다.
CSSOM이 트리 구조를 사용하는 이유는 무엇인가요? 페이지에 있는 객체의 최종 스타일 집합을 계산할 때 브라우저는 해당 노드에 적용되는 가장 일반적인 규칙으로 시작하고 (예: 본문 요소의 하위 요소인 경우 모든 본문 스타일이 적용됨) 계산된 스타일을 재귀적으로 세분화하여 더 구체적인 규칙을 적용합니다. 즉, 규칙이 '하향식'됩니다.
좀 더 구체적으로 표현하기 위해 위의 CSSOM 트리를 살펴보겠습니다. body 요소 내에 배치된 <span>
태그 내에 포함된 모든 텍스트는 글꼴 크기가 16픽셀이고 빨간색 텍스트가 있습니다. font-size
지시문은 body
에서 span
로 하향식으로 적용됩니다. 그러나 span
태그가 단락 (p
) 태그의 하위 요소이면 콘텐츠가 표시되지 않습니다.
또한 위 트리는 전체 CSSOM 트리가 아니며 스타일시트에서 재정의하기로 결정한 스타일만 표시합니다. 모든 브라우저는 '사용자 에이전트 스타일'이라고도 하는 기본 스타일 집합을 제공합니다. 이 스타일은 개발자가 직접 제공하지 않을 때 표시되는 스타일로, 이러한 기본 스타일을 재정의합니다.
CSS 처리에 걸리는 시간을 확인하려면 DevTools에서 타임라인을 기록하고 'Recalculate Style' 이벤트를 찾으면 됩니다. DOM 파싱과 달리 타임라인에 별도의 'Parse CSS' 항목이 표시되지 않고 대신 파싱 및 CSSOM 트리 생성과 함께 계산된 스타일의 재귀적 계산을 이 이벤트에서 캡처합니다.
이 간단한 스타일시트는 처리하는 데 약 0.6ms가 걸리며 페이지의 8개 요소에 영향을 미칩니다. 많지는 않지만 비용이 들지 않습니다. 그런데 8가지 요소는 어디서 왔을까요? CSSOM과 DOM은 서로 독립적인 데이터 구조입니다. 알고 보니, 브라우저에서 중요한 단계를 숨기고 있습니다. 다음으로 DOM 및 CSSOM을 함께 연결하는 렌더링 트리에 관해 알아보겠습니다.