파싱 Parsing 과정
- HTML 파싱 : 받은 텍스트(string)를 해석해서 브라우저가 이해할 수 있는 노드(node) 단위로 변환하는 것
HTML 파서(Parser) 작동
- HTML스트림을 받으면 브라우저는 HTML parser를 실행한다.
- 스트림으로 받은 텍스트를 토큰(token) 으로 쪼갠 뒤
- 토큰을 노드(node)로 바꿔서 트리(Tree) 구조를 만듦
1. Tokenizing (토크나이징)
- 스트링을 읽으면서 문법 단위(Token)로 나누는 것
- HTML파서에는 State Machine(상태 기계)가 있다.
상태(state)를 바꿔가면서 1글자씩 문자(character)를 읽고, 토큰(token)을 만든다
<div class="container">Hello</div>
<div class="container"> ⭢ 시작 태그 토큰 (start tag token)
Hello ⭢ 텍스트 토큰 (text token)
</div> ⭢ 종료 태그 토큰 (end tag token)
1-1) HTML Tokenizing의 규칙
< 를 만나면 태그 시작
> 를 만나면 태그 종료
<script>, <style> 등은 별도의 특별한 규칙 적용
상태 | 설명 | 전환 조건 |
Data State | 기본 상태. 그냥 텍스트 읽음 | < 만나면 Tag Open으로 이동 |
Tag Open State | < 를 만난 후. 어떤 태그인지 알아내야 함 |
/면 End Tag 알파벳이면 Start Tag |
Tag Name State | 태그 이름 읽는 중 (div, p, 등) | 스페이스나 > 만나면 끝 |
Attribute State | 속성 읽는 중 (class="x") | > 만나면 끝 |
End Tag State | </ 이후. 닫는 태그 이름 읽음 (</div>) | > 만나면 종료 |
// <div class="greeting"> 를 읽는다면
1. Data State → '<' → Tag Open State
2. Tag Open State → 알파벳 d → Tag Name State
3. Tag Name State → 읽기: d → i → v
4. 스페이스 → Attribute State
5. 읽기: class → '=' → "greeting"
6. '>' → Data State 복귀
이때, img, video 같은 태그는 병렬처리되는 리소스로 분리 된다.
<img> 자체는 DOM에 들어가지만, 이미지 파일(.png, .jpg)은 백그라운드 다운로드가 시작됨.
2. Tree Construction (트리 구성)
생성된 토큰을 사용해서 노드(Node)를 만든 후, 이를 DOM 트리(DOM Tree)로 구성함
시작 태그 ⭢ 새 노드를 만들고 현재 부모 노드에 추가 ⭢ 새 노드를 현재 노드로 설정
텍스트 ⭢ 현재 노드에 텍스트 노드로 추가.
종료 태그 ⭢ 현재 노드를 닫고 상위 노드로 이동.
2-1) DOM 트리 (Document Object Model Tree) 구성
DOM : HTML 문서를 트리 형태(Tree Structure)로 모델링한 것
DOM 트리는 각 요소가 부모(parent) - 자식(children)관계로 연결
[DOM의 기본 구성요소]
• Document 노드 (전체 문서)
• Element 노드 (<div>, <span>, <a> 같은 태그들)
• Text 노드 (태그 안에 있는 글자)
• Attribute 노드 (class="...", id="...")
• Comment 노드 (<!-- 주석 -->)
<div class="box">
Hello
<span>World</span>
</div>
위 같은 HTML 문서가 DOM으로 만들어지면 다음과 같은 구조가 된다.
Document
└── html
└── body
└── div (class="box")
├── Text("Hello")
└── span
└── Text("World")
중첩(Nesting) 구조의 HTML 문서의 구조는 어떨까?
<div>
오늘도
<p>안녕</p>
<div>
<p>막내야</p>
</div>
<img/>
</div>
1 - 여는 태그(<div>, <p>)를 만나면 새 노드 생성
2 - 닫는 태그(</p>, </div>)를 만나면 현재 노드 스택에서 pop(빠져나오기)
즉, 구조적으로 스택(Stack)처럼 작동함
div
├── Text("오늘도")
├── p
│ └── Text("안녕")
├── div
│ └── p
│ └── Text("막내야")
└── img
2-1) a. 트리구조라면서 스택으로 처리한다는 게 무슨 말일까?
- 트리(DOM Tree)는 최종 구조
- 파싱 중에는 스택(stack)을
트리 만들 때, "현재 어디에 추가해야 할지"를 기억하려고 스택을 쓴다.
// 파싱 중 처리 흐름
(초기 상태)
스택: [document]
1. <div> 만남
→ div 노드 생성
→ document의 자식으로 div 추가
→ 스택에 div push
스택: [document, div]
2. "오늘도" 텍스트
→ 현재 스택 최상단(div)에 텍스트 노드 추가
3. <p> 만남
→ p 노드 생성
→ div 안에 p 추가
→ 스택에 p push
스택: [document, div, p]
4. "안녕" 텍스트
→ 현재 스택 최상단(p)에 텍스트 노드 추가
5. </p> 닫힘
→ 스택 pop (p 빠짐)
스택: [document, div]
6. <div> 만남
→ div 생성
→ div(첫번째 div)의 자식으로 추가
→ 스택에 새 div push
스택: [document, div, div]
7. <p> 만남
→ p 생성
→ 현재 div에 추가
→ 스택에 p push
스택: [document, div, div, p]
8. "막내야" 텍스트
→ p에 추가
9. </p> 닫힘
→ 스택 pop
스택: [document, div, div]
10. </div> 닫힘
→ 스택 pop
스택: [document, div]
11. <img/> 만남 (빈 요소)
→ img 노드 div에 추가
(스택 변경 없음)
12. </div> 닫힘
→ 스택 pop
스택: [document]
위 흐름을 거쳐 아래와 같은 최종 트리 구조가 만들어지는 것
div
├── Text("오늘도")
├── p
│ └── Text("안녕")
├── div
│ └── p
│ └── Text("막내야")
└── img
2-1) b. HTML 파서의 Insertion Mode (Before Head, In Body 등)
※ Insertion Mode = 현재 어디에 어떤 노드를 추가할지 결정하는 규칙
Insertion Mode | 의미 |
Initial | 문서 시작. Doctype 읽는 중. |
Before Html | <html> 요소를 기다리는 중. |
Before Head | <head> 요소를 기다리는 중. |
In Head | <title>, <meta>, <style> 같은 걸 삽입. |
After Head | <body> 기다리는 중. |
In Body | 본문 내용 삽입 (대부분 여기서 작동) |
After Body | </body> 이후 처리 |
After After Body | 정말 예외적 에러 복구 (ex: body 밖에 요소 있을 때) |
파서는 위 모드를 실시간으로 바꿔가면서 "지금 들어온 토큰을 어디 추가할지", "지금 정상적인 위치에 있는지" 등을 체크한다.
2-1) c. DOM 완성 전, 병렬 처리되는 것
브라우저는 HTML 파싱 중에도 다른 작업을 병렬로 처리한다.
CSS 파싱 | <link>, <style> 만나는 순간 별도로 CSSOM(CSS Object Model) 파싱 시작 |
JS 실행 | <script> 만나는 순간 파싱 중단하고, 스크립트 다운로드 → 실행. (기본은 'blocking') |
리소스 다운로드 | <img>, <video>, <audio> 등은 비동기적으로 다운로드 시작 |
이때 CSSOM파싱은 HTML파싱과 다르다.
단계 | 설명 |
HTML 파싱 | HTML 문자열 → 토큰 → DOM 트리 구성 |
CSS 파싱 | CSS 파일/코드 → 토큰 → CSSOM 트리 구성 |
DOM + CSSOM 통합 | 두 트리를 합쳐서 Render Tree 생성 |
레이아웃(Layout) | Render Tree를 기반으로 요소 크기/위치 계산 |
페인팅(Paint) | 계산된 레이아웃을 기반으로 픽셀 단위로 그리기 |
DOM, CSSOM은 둘 다 "트리"지만, 목적과 과정이 다르며 통합은 별도 단계이다.
❗️HTML 파싱 도중 <link rel="stylesheet">, <style> 같은 CSS을 만나면, 그때부터 브라우저가 CSS 파싱도 같이 시작한다
그래서 "HTML파싱 중"에 CSSOM 만드는 것처럼 보이기도 하지만 "병렬"로 처리되고 있는 별도의 단계.
[HTML 파싱] + [CSS 파싱]
↓
[DOM 트리] + [CSSOM 트리]
↓
[Render Tree 생성]
↓
[Layout 계산 (Reflow)]
↓
[Paint (그리기)]
↓
[사용자가 보는 화면]
2-1) d. blocking : "파서를 멈춘다"는 의미
- 기본 (nothing special) : <script> 태그를 만나면 무조건 파싱 멈춤. 다운로드 완료 즉시 실행.
- 예외
- async(파싱과 다운로드 병렬, 파싱은 안 멈추지만 렌더링 블로킹 위험)
- defer(파싱 끝나고 실행, 렌더링에 최적화)
defer 스크립트는 파싱 끝나고, 렌더링 직전이나 중간에 실행.
DOMContentLoaded 이벤트 직전에 스크립트 실행
- 실무 tip : 외부 스크립트는 defer를 최대한 쓰고
페이지 구조를 진짜 동적으로 바꿔야 하는 경우만 blocking <script>를 사용
2-1) e. 그럼에도 HTML파싱 ➝ CSS파싱 같아 보이는 이유?
CSS 파싱자체는 HTML 파싱의 이벤트를 트리거로 해서 시작되기 때문.
HTML 파싱은 순차적(linear)이라 <html>, <head>, <meta>, <title> 순서대로 파싱해 나간다.
HTML이 <link>를 파싱 할 때까지는 CSS 파싱이 아예 존재하지 않는다.
즉 <html>, <head> 등의 선행이 일어나야 <link rel="stylesheet">가 실행되는 것.
HTML 파서:
<html> → <head> → <meta> → <title> → <link> (여기서 CSS 요청 발생!)
↓
계속 <body> 파싱 진행 중
CSS 파서:
<------ (link 태그를 만난 순간부터 별도 스레드로 시작)
→ 이후 둘 다 병렬로 진행
2-1) f. <img>를 빨리 로딩하려면 부모 가까이에 두는 게 유리한가?
맞기도, 아니기도 함.
- 브라우저는 HTML 파싱 중 <img> 태그를 만나야 이미지를 다운로드 요청을 시작한다.
<img>를 빨리 만나야 리퀘스트를 빨리 보낼 수 있다.
즉, <img>를 문서 초반부 (ex: body 상단) 배치하면 다운로드 시작이 빨라져서 더 빨리 화면에 뜨는 건 맞다. - 하지만 가장 좋은 방법은
<link rel="preload" as="image" href="..."> 같은 걸 <head>에 사용.
body태그까지 가기도 전에 미리 다운로드 트리거 걸어버리는 것
이 문제는 게임원화와 같은 고화질, 고용량의 이미지 사용 시 마주했던 문제였다.
"웅장하게 보여야 할 배경이 늦게 렌더링 된다는 것"
당시 해결법은 이러했다.
1. css의 background로 호출했던 이미지를 html의 <img> 태그로 빼고 가장 최상단(body 시작 바로 밑)에 작성
2. 시각적 착시를 주기 위해 css로 opacity transition을 줌
그중 1의 방법을 수행하면서 그 원리가 궁금했는데, 파싱과정을 알게 되니 이해가 됐다.
+ 이미지 소스를 용량을 일정 이상 저하시키면 그라데이션 영역에서 미세한 노이즈가 발생해서 컴플레인이 들어왔와서 용량을 낮추는게 방법은 아니었음.
.
'D.evelop > Web' 카테고리의 다른 글
[1] HTML의 렌더링에 대해 궁금했던 것들 - stream (0) | 2025.04.26 |
---|---|
[NFT] 기본 개념 (0) | 2022.05.26 |
[Chrome 개발자 도구] 네트워크 패널 DevTools - Network (0) | 2022.03.16 |
인증Authentication/인가Authorization (0) | 2021.12.20 |
[WEB] 웹 저장소 - 쿠키/로컬스토리지/세션스토리지 (0) | 2021.12.20 |
댓글