브라우저란?
웹 브라우저는 인터넷의 많은 정보(HTML)를 보여주고 전송하는 소프트웨어 프로그램이다.
프런트엔드 개발자는 브라우저에 대해 제대로 알고 있어야한다. 브라우저를 통해 개발, 배포, 디버깅등을 하고, 최종 사용자들도 브라우저를 통해 웹 애플리케이션에 접속하기 때문이다.
본 글은 사용자가 브라우저에 사이트 주소를 입력했을 때 페이지가 보여지는 그 원리를 브라우저 측면에서 가볍게 서술한다.
브라우저의 동작
사용자가 웹 브라우저에 유튜브 주소를 입력하면, 웹브라우저는 해당 주소(유튜브 서버)에게 요청을 하여 유튜브의 HTML, CSS, JS등의 리소스 파일을 전송받는다. 이때 응답받는 파일은 bit stream(010101011)으로 표현되어 있다.
브라우저는 받은 bit stream을 인코딩하고 렌더링하여 사용자가 보기 좋게 띄우는 일을 한다.
브라우저 구성요소
구체적인 렌더링 과정을 설명하기 전 브라우저는 어떤 구조를 알아보겠다.
브라우저는 위와 같은 그림으로 표현 할 수 있다. 각 요소에 대해서 살펴보겠다.
사용자 인터페이스(UI) : 브라우저에서 기본적으로 제공되고 페이지가 변해도 바뀌지 않는 부분이다.
ex) 주소 표시줄, 탭 관리, 뒤로가기/앞으로가기 버튼, 북마크, 확장 프로그램 등등의 기본적인 UI
렌더링 엔진 : 사용자의 선택에 따라 받아오는 HTML, CSS, JS를 화면에 그려내 보여준다.
- 대표적인 렌더링 엔진으로는 Chrome과 Edge의 Blink, Safari의 WebKit, Firefox의 Gecko가 있다.
브라우저 엔진 : 렌더링 엔진과 사용자 인터페이스의 중간 역할을 수행한다.
ex) UI에서 새로고침 키가 입력되면 브라우저 엔진이 이를 이해하고 새로고침 명령을 수행한다.
통신 : HTTP, HTTPS등의 프로토콜을 이용해 웹페이지의 리소스(HTML, CSS, JS)를 요청하고 응답받는다.
ex) 사용자가 유튜브 주소를 입력하면 HTTPS로 유튜브의 웹페이지 데이터를 요청하고 받아온다.
- 사용 프로토콜
- HTTP/HTTPS: 웹페이지 리소스를 요청하고 받아오는 기본 프로토콜
- WebSocket: 양방향 실시간 데이터 통신에 사용
자바스크립트 해석기 : 자바스크립트 코드를 동작하게 한다.
- 웹페이지의 동적인 요소를 구현하는 핵심 역할
- 대표적으로 크롬, NodeJS에 사용되는 V8 엔진이 있다.
- 전통적으로 인터프리터로 시작했지만, 최신 엔진들은 JIT (Just-In-Time) 컴파일러를 사용하여 코드를 미리 컴파일해 성능을 극대화한다.
UI 백엔드 : 브라우저가 동작하는 운영체제를 따르는 UI를 처리합니다.
ex) 경고창이나 선택박스가 운영체제 별로 다르게 동작하는 것으로 알 수 있다.
- Windows의 경우 기본 경고창은 Windows 스타일
- macOS에서는 macOS 스타일의 경고창 사용
자료저장소 : 브라우저도 필요한 데이터를 저장할 수 있는 공간이다.
ex) 로컬스토리지, 세션스토리지, 쿠키
렌더링 엔진의 동작 과정
여기서 접속 주소로 부터 리소스를 받아오는 과정에서 많은 통신 과정을 거치지만 이 부분은 따로 정리하고 리소스를 띄워내는 과정에 대해서 중점적으로 설명한다.
1. 리소스 요청
앞서 유튜브를 접속 예시로 들어 유튜브 서버로부터 웹페이지의 bit stream을 받아왔다. 이를 인코딩하면 이제 우리가 읽을 수 있는 문서(Document)가 만들어집니다.
예시
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
인코딩을 하면 위와 같은 HTML 문서가 만들어진다. 여기서 <meta charset="UTF-8"> UTF-8이라는 방식으로 인코딩을 하기 시작한다.
2. 파싱
HTML 문서는 사람이 쉽게 이해할 수 있지만, 브라우저는 HTML 코드를 해석하고 화면에 표시하기 위해 특정 과정을 거친다. 렌더링 엔진는 HTML 문서를 해석하면서 태그별로 구조를 나누고, 문법에 따라 문서를 쪼개어 이해할 수 있도록 파싱(parsing) 과정을 수한다.
예를 들어, 수식 9 - 3 + 4는 컴퓨터가 이해할 수 있도록 수식 트리(Expression Tree)로 파싱된다. 이렇게 수식이 트리 형태로 나뉘면 컴퓨터는 각 연산의 우선순위를 이해하고 정확한 계산을 수행할 수 있다.
이와 마찬가지로 브라우저의 렌더링 엔진도 HTML과 CSS 파일을 각각 파싱하여 트리 구조로 변환합니다.
2.1 HTML 파싱
HTML 파싱은 HTML 마크업을 파싱트리로 변환시키는 과정이다.
먼저 Bytes상태인 HTML 코드를 인코딩하여 토큰화 시킨다.
1. 토큰화(Tokenization)
인코딩된 HTML 문서는 HTML 파서를 통해 문법적 의미를 갖는 코드의 최소단위인 토큰으로 분해한다.
토큰화 진행 전:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
토큰화 결과:
1. DOCTYPE 토큰: { type: "doctype", name: "html" }
2. 시작 태그 토큰: { type: "startTag", name: "html", attributes: { lang: "en" } }
3. 시작 태그 토큰: { type: "startTag", name: "head" }
4. 시작 태그 토큰: { type: "startTag", name: "title" }
5. 텍스트 토큰: { type: "text", content: "Example" }
6. 종료 태그 토큰: { type: "endTag", name: "title" }
7. 종료 태그 토큰: { type: "endTag", name: "head" }
8. 시작 태그 토큰: { type: "startTag", name: "body" }
9. 시작 태그 토큰: { type: "startTag", name: "h1" }
10. 텍스트 토큰: { type: "text", content: "Hello World!" }
11. 종료 태그 토큰: { type: "endTag", name: "h1" }
12. 종료 태그 토큰: { type: "endTag", name: "body" }
13. 종료 태그 토큰: { type: "endTag", name: "html" }
https://html.spec.whatwg.org/multipage/parsing.html#overview-of-the-parsing-model -> 파싱 알고리즘
HTML 파서(HTML Parser)는 "상태 머신(State Machine)" 기반으로 동작한다
- 데이터 상태: 일반 텍스트 해석
- 태그 열림 상태: <를 만나면 태그 파싱 시작
- 태그 이름 상태: html, head, body 등 해석
- 속성 상태: lang="en" 같은 속성 해석
- 태그 닫힘 상태: >를 만나면 태그 종료
이 상태 머신을 통해 렌더링 엔진은 각 문자를 해석하며 토큰을 생성한다.
2. 노드 객체 생성
이렇게 만들어진 토큰들은 자바스크립트 객체로 변환시킨다. 이렇게 만들어진 객체를 노드라고 한다.
Q. HTML이 왜 자바스크립트(노드) 객체가 되나요?
A. 웹 페이지의 내용을 자바스크립트를 통해 동적으로 조작하고 렌더링하기 위해서다.
const htmlNode = {
nodeName: "html",
nodeType: 1,
attributes: { lang: "en" },
childNodes: [
{
nodeName: "head",
nodeType: 1,
childNodes: [
{
nodeName: "title",
nodeType: 1,
childNodes: [
{
nodeName: "#text",
nodeType: 3,
textContent: "Example"
}
]
}
]
},
{
nodeName: "body",
nodeType: 1,
childNodes: [
{
nodeName: "h1",
nodeType: 1,
childNodes: [
{
nodeName: "#text",
nodeType: 3,
textContent: "Hello World!"
}
]
}
]
}
]
};
3. DOM 트리 생성
이제 이 노드들에게 관계를 부여하여 DOM(Document Object Model) 트리를 새로 만들어 낸다.
HTML 문자열과 그 문자열에서 문법과 구조에 맞게 새로 만들어낸 DOM 트리는 다르다.
실제로 상호작용에 쓰일 수 있는 것은 HTML 관계를 표현하고 있는 DOM 트리이기 때문이다.
💡정리하면 HTML문서 파싱의 결과는 DOM이다.💡
이러한 HTML 파싱과정에도 몇 가지 특징이 존재 한다.
1. 오류에 너그럽다.
ex) 닫힘 태그가 생략된 코드도 브라우저에 동작시켜 보면 완성된 코드가 나온다.
2. 파싱 과정이 중단될 수 있다. <- 렌더링 블로킹 리소스
파싱과정에서 <script>, <link>와 같은 외부 태그를 만나면 HTML 파싱을 즉시 중단하고 해당 태그를 해석한다.
만약 그 태그가 외부 파일을 한다면 다운로드 한 뒤 해석을 시작한다.
- 이유는 <script>에 DOM을 직접 수정하는 내용이 있을 수도 있기 때문이다.
async, defer를 사용해 비동기 처리를 사용하면 이러한 문제를 해결할 수 있다.
<script src="script.js" async></script>
<script src="script.js" defer></script>
3. 재시작
HTML의 파싱이 외부 요인으로 방해받아 DOM이 추가, 변경, 삭제될 수 있다. 이런 경우 HTML은 처음부터 다시 파싱과정을 거친다.
2.2 CSS 파싱
css는 주로 link를 통해 HTML 문서에 삽입된다.
<link rel="stylesheet" href="style.css">
HTML 파싱에서 link 태그를 만나면 파싱이 중단된다고 위에 이야기했다. 이때 link를 만나고 사용자가 입력한 주소로 가서 css 파일을 요청하고 받아오게 된다.
받아온 CSS 파일을 HTML 파싱과 똑같은 과정을 거치며 CSSOM tree를 만들어낸다.
- bit stream => 캐릭터화 => 토큰화 => 노드 => CSSOM tree
2.3 Javascript 파싱
JS 파싱은 렌더링엔진이 관여하는 것이 아닌 자바스크립트 엔진이 처리한다.
<link> 태그와 마찬가지로 자바스크립트 파일이 들어가는 <script> 태그를 만나면 HTML 파싱이 중단된다.
= 렌더링 블로킹 리소스
1. 이때 렌더링 엔진이 자바스크립트 엔진에게 제어 권한을 넘긴다.
2. script 태그의 src 속성에 정의된 파일을 서버에 요청해서 받아온다.
3. 문법적 의미를 갖는 최소 단위인 토큰으로 나눈다.
4. 토큰들의 관계를 나타내는 AST(Abstract Syntax Tree)를 생성한다.
5. 이를 AST를 기반으로 컴퓨터 중심적인 바이트 코드로 변환된다.
6. 인터프리터에 의해 실행된다.
결론적으로 자바스크립트 엔진은 자바스크립트 코드를 파싱 하여 cpu가 이해할 수 있는 저수준 언어로 변환하고 실행한다.
3. 랜더 트리(Render Tree) 만들기
DOM과 CSSOM이 만들어지면 렌더링을 위한 렌더트리로 결합된다.
렌더트리는 <meta>, <script> 태그 등 실제 화면에 표현되지 않는 노드들은 제거되고 진짜 화면에 보이는 노드들로만 구성된다.
❌ 제외되는 요소 : display : none 속성으로 이루어진 노드는 어차피 보이지 않으니 렌더트리에서 빠진다.
✅ 포홤되는 요소 : visibility: hidden, opacity: 0 같은 경우는 공간은 유지하기에 렌더트리에 포함되나 보이지만 않는다.
4. Layout
Layout은 스타일과 속성에 맞게 정확환 화면 배치 위치를 계산해 내는 단계이다.
만들어진 최종 설계도인 렌더트리를 화면 어디에 정확히 위치시킬지를 정확히 계산해 낸다.
뷰포트(Viewport), 즉 기기와 미디어 쿼리 등에 따라 정확한 px가 달라진다.
body: (0, 0, 1024px, 768px)
h1: (10px, 20px, 500px, 40px)
p: (10px, 70px, 500px, 20px)
5. paint
렌더트리에 있는 요 요소의 색상, 그림자, 텍스트 스타일, 배경색 등 시각적 속성을 기반으로 픽셀을 생성한다.
레이어(Layers)를 사용하여 각 요소를 분리되며 단계별로 하나씩 paint된다.
6. Compoiste
이렇게 칠해진 레이어들을 GPU를 이용해 모두 합치면(composite) 매일 보는 웹페이지 화면을 볼 수 있는 것이다.
- 레이아웃을 나누어 칠하는 이유 : 하나의 실수가 발생했을 때 전체를 바꾸면 리스크가 크다. 그래서 레이아웃을 나누어 오류가 발생한 레이아웃만 리페인트하게 된다.
- z-index 순서에 따라 결합
7. Tiling
타일링(Tiling)은 화면을 작은 사각형의 조각(타일)으로 나누는 과정입니다.
이 과정은 GPU 최적화와 스크롤링 성능을 높이기 위해 사용됩니다.
[전체 페이지]
+----+----+----+
| T1 | T2 | T3 |
+----+----+----+
| T4 | T5 | T6 |
+----+----+----+
왜 타일링이 필요한가?
- 대용량 콘텐츠 최적화: 웹페이지가 클 경우 전체 페이지를 한 번에 렌더링하기 어렵기 때문.
- 빠른 스크롤링: 스크롤할 때 필요한 영역만 렌더링.
- GPU 사용: 타일 단위로 GPU를 활용해 렌더링 성능을 개선.
8. Rasterization
각 타일을 그래픽 라이브러리를 사용하여 비트맵 이미지를 생성하고 이를 GPU 메모리에 저장한다.
9. Display
타일 단위로 생성된 픽셀 데이터를 GPU 메모리에서 모니터에 출력하는 과정
10. Reflow, Repaint
DOM API를 통해 자바스크립트에서 DOM 트리를 제어할 수 있다.
이렇게 js 코드를 통해 DOM이나 CSSOM을 변경하면 이는 다시 렌더 트리로 결합되고 렌더트리를 기반으로 다시 Layout => Paint 과정을 거쳐 화면에 렌더링 된다.
Reflow(리플로우)
layout(레이아웃) 과정을 다시 거치는 것으로 성능 비용이 크다.
- width, height, position 변경 시 발생한다.
Repaint(리페인트)
paint과정을 다시 거치는 것으로 시각적 스타일만 변경하는 것으로 성능 비용 적다.
- color, background-color, box-shadow 변경 시 발생
최적화 요령
transform 과opacity는 Reflow와 Repaint를 발생시키지 않고, DOM 트리를 수정하지 않는다.
브라우저가 이를 GPU에서 직접 처리하기 때문에,
이러한 속성을 활용하면 레이아웃 재계산 없이 부드럽고 쾌적한 애니메이션을 구현할 수 있습니다
전체적인 렌더링 흐름도
위에서 설명한 렌더링 과정을 아래와 같이 나타낼 수 있다.
결록적으로 이 흐름이 계속 반복되며 우리는 화면을 볼 수 있는 것이다.
참고 자료
https://d2.naver.com/helloworld/5237120
https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko
https://developer.chrome.com/blog/inside-browser-part3?hl=ko
'👨💻 Programming > WEB' 카테고리의 다른 글
Stateless vs Stateful (0) | 2025.02.24 |
---|---|
모듈 번들러란? Webpack으로 알아보는 번들링 과정 (0) | 2025.01.20 |
Virtual DOM이란? React의 렌더링 방식과 CSR, SSR 비교 정리 (1) | 2025.01.13 |