브라우저가 HTML, CSS, 자바스크립트 파일로 작성된 텍스트 문서를 어떻게 파싱하는지 알아보자.
- 부라우저 포스팅을 참고해 브라우저의 구조 등을 미리 살펴보면 이해에 보움이 된다
- 파싱 parsing
파싱은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고, 파스 트리를 생성하는 과정을 말한다.- 렌더링 rendering
HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 시각적으로 출력하는 것.- 렌더 트리
DOM과 CSSDOM을 결합한 트리며 브라우저 화면에 렌더링 되는 노드만으로 구성된다.
- 브라우저가 서버에 리소스 요청 후 서버로부터 응답받기
- 브라우저 렌더링 엔진이 HTML 파싱 후 DOM 생성 + CSS 파싱 후 CSSDOM 생성
- 2번의 DOM, CSSDOM을 결합하여 렌더트리 생성
- 브라우저 자바스크립트 엔진이 JS 파싱 후 AST(추상 구문 트리) 생성.
(이때 자바스크립트는 DOM API를 통해 DOM, CSSDOM 변경 가능하며 변경되었다면 변경된 것을 다시 렌더 트리로 결합한다.)- 렌더 트리를 기반으로 HTML을 브라우저에 레이아웃 후 페인팅.
1. 브라우저가 서버에 리소스 요청 후 서버로부터 응답받기
브라우저는 URL을 입력받으면, DNS(도메인 서버)를 통해 IP주소로 변환받아 해당 IP 주소를 갖는 서버에 요청을 전달한다.
사용자가 웹 브라우저를 통해 찾고 싶은 웹 페이지의 URL 주소를 입력하면 DNS 서버에서 사용자가 입력한 URL 주소 중 도메인 네임을 검색한다.
그리고 해당 도메인 네임에 해당하는 IP 주소를 찾아 사용자가 입력한 URL 정보와 함께 전달한다.
이렇게 웹 페이지 URL 정보와 전달받은 IP 주소는 HTTP 프로토콜을 사용해 HTTP 요청 메시지를 생성하고 TCP 프로토콜을 사용해 인터넷을 거쳐 해당 IP 컴퓨터로 전송되고, 이 요청 메시지는 다시 HTTP 프로토콜을 통해 웹 페이지 URL 정보로 변환이 됩니다.
웹 서버는 이 변환 된 정보에 해당하는 데이터를 검색하여 찾아낸 뒤 HTTP 프로토콜을 통해 HTTP 응답 메시지를 생성하고, 이 메시지는 다시 TCP 프로토콜을 이용해 인터넷을 거쳐 사용자의 컴퓨터로 전송된다.
사용자의 컴퓨터에 도착한 HTTP 응답 메시지는 HTTP 프로토콜을 사용해 웹 페이지 데이터로 변환이 되고, 변환된 데이터는 웹 브라우저 상에 출력되어 사용자가 볼 수 있게 되는 것이다.
2. 브라우저 렌더링 엔진이 HTML 파싱 후 DOM 생성 + CSS 파싱 후 CSSDOM 생성
HTML 파싱 후 DOM 생성하기
HTML은 서버로부터 바이트 형태로 전달받게 되며, 이후 문자열로 변환한다.
문자열로 변환 된 HTML은 각각 토큰으로 분해된다.
토큰으로 분해 된 HTML은 각각 노드 객체로 분해되며, 각각의 노드 객체가 부모-자식 관계를 형성하며 트리 형태의 자료구조인 DOM을 형성한다.
CSS 파싱 후 CSSOM 생성하기
CSS 또한 파싱 과정이 바이트 ➡️ 문자열 ➡️ 토큰 ➡️ 노드로 이루어진다.
각각의 노드는 부모-자식 관계를 형성하며 트리 형태의 자료구조인 CSSOM을 형성한다.
HTML 파싱 중, CSS를 의미하는 link 태그나 style 태그를 만날 경우 블로킹되어 CSSOM 생성으로 넘어간다.
CSSOM 생성이 마무리되면, 블로킹 된 시점부터 다시 HTML 파싱을 진행한다.
3. 2번의 DOM, CSSDOM을 결합하여 렌더트리 생성
렌더 트리란? 렌더링에 필요한 트리 자료 구조이다.
DOM과 CSSOM으로 이루어져 있다.
화면에 렌더링되는 노드만으로 구성된다. 화면에 렌더링되지 않는 display: none이나 meta 태그 등은 렌더 트리를 구성하지 않는다.
4. 브라우저 자바스크립트 엔진이 JS 파싱 후 AST(추상 구문 트리) 생성
AST란? 추상 구문 트리를 말하는 것이며, 이를 기반으로 인터프리터가 바이트코드(가상 머신이 이해할 수 있는 중간 레벨의 코드)를 생성한다.
HTML 파싱 중, JS를 의미하는 script 태그를 만나게 되면 블로킹되어 제어권을 JS엔진에게 넘겨주어 JS 파싱으로 넘어간다.
5. 렌더 트리를 기반으로 HTML을 브라우저에 레이아웃 후 페인팅.
렌더 트리는 HTML 요소의 레이아웃 계산과 페인팅 처리에 이용된다.
JS에 의해 노드가 추가되거나 브라우저 창이 리사이징되었을 경우 리플로우 및 리페인팅 일어난다.
(레이아웃이 변경되는 사항이 없을 경우 리플로우는 생략)
(리플로우가 발생하면 리페인트는 필연적으로 일어난다.)
리플로우와 리페인트 참고
- 리플로우 렌더링을 다시 하는 것이기 때문에 배치를 위한 연산을 해야 해 실제로 CPU를 많이 차지한다.
- 리페인트는 페인트를 다시 하는 것이라 픽셀을 다시 화면에 찍어 그려야 하므로 GPU를 많이 차지한다. 그렇기 때문에 프레임 드랍(Frame Drop) 현상과 직접적인 연관이 있다.
리플로우와 리페인트 최적화
- CSSOM 트리의 CSS 속성 중에 레이아웃을 발생시키는 속성들이 존재하는데 해당 속성을 사용하게 되면 그때마다 변경이 되어 렌더 트리를 만들고, 레이아웃을 발생시키고, 페인트를 하는 과정이 연속적으로 발생하게 되므로 이런 속성들을 최대한 줄이자
즉, 레이아웃을 변경하는 리플로우를 최대한 줄이면 된다. 리플로우는 리페인트를 동반하기 때문이다.
그렇다고 리페인트는 신경쓰지 않으면 안되고 이도 줄여야 한다.
- 예를 들어 left 속성을 이용해 애니메이션을 만들면 프레임 유지가 힘들어 질 수 있다.
transform이라는 속성을 사용하여 transform에 있는 translate를 사용한다면 좌표 값을 사용해 위치를 이동하지만, 레이아웃을 발생시키지 않고 페인트만 다시 발생시키는 쪽으로 렌더링 과정이 일어나기 때문에 유지하고자 하는 프레임 수를 기대할 수 있다.
리페인트가 일어나지 않게 해주는 opacity라는 속성이 있다.
visibility/display 보다 opacity를 사용하는 것이 성능 개선에 도움이 된다.영향을 주는 노드를 줄이는 것도 최적화의 방법이다..
JavaScript + CSS를 조합한 애니메이션이 많거나, 레이아웃 변화가 많은 요소의 경우 position을 absolute 또는 fixed를 사용해주면 영향을 받는 주변 노드들을 줄여줄 수 있다.
fixed와 같이 영향을 받는 노드가 전혀 없는 경우 리플로우 과정이 필요 없기 때문에 리페인트 연산 비용만 들일 수 있다.
- 위에서 설명했다시피, HTML 파싱은 script 태그를 만나면 블로킹되어 렌더링 엔진에서 JS엔진에게 제어권을 넘겨주게 된다.
- 스크립트는 동기적 파싱(위에서 아래로 파싱)이 이루어지기 때문에, HTML 파싱이 script 태그의 위치에 따라 지연될 수 있다.
- 만약 JS 코드가 HTML 노드를 생성할 경우, HTML 파싱이 완료되어있어야 한다.
- 그러므로 script 태그는 되도록이면 body 태그 맨 아래에 위치하는 편이 좋다.
- script 태그를 body 태그 맨 아래에 위치시키더라도, JS 코드 파싱중에 사용자가 웹에 상호작용을 시도(버튼을 누르기, 텍스트 입력) 시 정상적으로 작동하지 않는다.
- async, defer 어트리뷰트를 통해 JS 코드를 비동기적으로 불러옴으로써 DOM 렌더링의 블로킹을 방지할 수 있다.