게시일: 2014년 3월 31일
브라우저가 페이지를 렌더링하려면 먼저 DOM 및 CSSOM 트리를 생성해야 합니다. 따라서 HTML 및 CSS를 가능한 한 빨리 브라우저에 제공해야 합니다.
요약
- 바이트 → 문자 → 토큰 → 노드 → 객체 모델.
- HTML 마크업은 DOM (문서 객체 모델)으로 변환되고, CSS 마크업은 CSSOM (CSS 객체 모델)으로 변환됩니다.
- 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(Document Object Model)이며, 브라우저는 이후 모든 페이지 처리에 이 DOM을 사용합니다.
브라우저는 HTML 마크업을 처리할 때마다 이전에 정의된 모든 단계를 거칩니다. 즉, 바이트를 문자로 변환하고, 토큰을 식별한 후 노드로 변환하고 DOM 트리를 빌드합니다. 전체 프로세스를 완료하는 데 어느 정도 시간이 걸릴 수 있으며, 처리해야 할 HTML이 많은 경우에는 더욱 그렇습니다.
Chrome DevTools를 열고 페이지가 로드되는 동안 타임라인을 기록하면 이 단계를 실행하는 데 걸린 실제 시간을 확인할 수 있습니다. 이전 예에서는 HTML 청크를 DOM 트리로 변환하는 데 약 5밀리초가 걸렸습니다. 큰 페이지의 경우 이 프로세스가 훨씬 더 오래 걸릴 수 있습니다. 매끄러운 애니메이션을 만드는 경우, 브라우저가 대량의 HTML을 처리해야 한다면 쉽게 병목 현상이 발생할 수 있습니다.
DOM 트리는 문서 마크업의 속성 및 관계를 포함하지만 요소가 렌더링될 때 어떻게 표시될지에 대해서는 알려주지 않습니다. 이는 CSSOM의 책임입니다.
CSS 객체 모델 (CSSOM)
브라우저는 기본 페이지의 DOM을 생성하는 동안 외부 CSS 스타일시트인 style.css
를 참조하는 문서의 <head>
에서 <link>
요소를 발견했습니다. 페이지는 페이지를 렌더링하는 데 이 리소스가 필요하다고 판단한 후 즉시 이 리소스에 대한 요청을 보내고, 그 결과 다음과 같은 내용이 반환됩니다.
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 Object Model'(CSSOM)이라는 트리 구조에 링크됩니다.
CSSOM이 트리 구조를 가지는 이유는 무엇일까요? 페이지의 객체에 대한 최종 스타일 집합을 계산할 때 브라우저는 해당 노드에 적용할 수 있는 가장 일반적인 규칙으로 시작합니다(예: 본문 요소의 하위 요소인 경우 모든 본문 스타일이 적용됨). 그런 다음 더 구체적인 규칙을 적용하여 계산된 스타일을 재귀적으로 미세 조정합니다. 즉, 규칙이 '하위로 캐스케이드'됩니다.
더욱 구체화하기 위해 앞에서 설명한 CSSOM 트리를 살펴봅시다. 본문 요소 내에 있는 <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을 함께 연결하는 렌더링 트리를 살펴봅니다.