38장 : 브라우저의 렌더링 과정
by 담담이담브라우저가 HTML, CSS, 자바스크립트로 작성된 문서를
어떻게 파싱해서 브라우저에 렌더링하는지 살펴보자.
- 파싱이란?
텍스트 문서의 문자열을 토큰으로 분해하고,
토큰의 의미와 구조를 반영하여 트리를 만들어내는 일련의 과정을 의미한다.
- 렌더링이란?
HTML, CSS, JS로 작성된 문서를 파싱해서
브라우저에 시각적으로 출력하는 것을 의미한다.
간단히 말하면 다음의 과정을 거쳐 렌더링을 수행한다.
1. 브라우저가 렌더링에 필요한 리소스를 요청 후 서버로부터 응답을 받는다.
2. 브라우저의 렌더링 엔진은
HTML, CSS를 파싱해 DOM과 CSSOM을 만들고
이를 결합해서 렌더 트리도 만든다.
3. 브라우저의 JS 엔진은 JS를 파싱해서 AST를 생성하고,
바이트코드로 변환해 실행한다.
이때 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있으며,
변경 시 다시 렌더트리로 결합된다.
4. 렌더 트리를 기반으로 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
1. 요청과 응답
렌더링에 필요한 리소스는 서버에 존재하기에
필요한 리소스를 서버에 요청해서 받아야한다.
서버에 요청을 전송하기 위해서
브라우저는 주소창을 제공한다.
주소창에 URL을 입력하고 엔터를 치면
URL의 호스트 이름이 IP 주소로 변환되고,
이 IP 주소를 갖는 서버에 요청이 전송된다.
cf) https://20002100.tistory.com/
으로 요청을 보내면
서버는 루트 요청만 받았을지라도 암묵적으로
https://20002100.tistory.com/index.html
index.html을 응답한다.
이 때 네트워크 탭을 살펴보면
index.html 응답 뿐만 아니라
CSS, JS, 이미지, 폰트 파일도 응답이 된다.
왜 요청도 안한 리소스가 응답되었을까?
브라우저의 렌더링 엔진이 HTML을 파싱하면서
외부 리소스를 로드하는 태그인 link, img, script 태그를 만나면
HTML 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청하기 때문이다.
2. HTTP 1.1과 HTTP 2.0
HTTP는 웹 브라우저와 서버가 통신하기 위한 프로토콜이다.
HTTP 1.1은 커넥션 당 하나의 요청과 응답만 처리한다.
따라서 여러 개의 요청을 한 번에 전송하거나 여러 개의 응답을 한 번에 전송받을 수 없다.
따라서 리소스 개수에 비례해서 응답 시간도 증가한다.
HTTP 2.0은 이와 다르게 다중 요청, 다중 응답이 가능하다.
따라서 1.1보다 빠르다.
3. HTML 파싱과 DOM 생성
HTML 문서를 브라우저로 렌더링 하려면
브라우저가 이를 이해할 수 있게
DOM 트리로 변환해서 저장해야한다.
즉, DOM은 HTML을 파싱한 결과물이다.
4. CSS 파싱과 CSSOM 생성
브라우저 렌더링 엔진은 HTML을 순차적으로 파싱하며 DOM을 만들어가다가,
CSS를 로드하는 link태그나 style 태그를 만나면
DOM 생성을 일시 중단한다.
그리고 link 태그의 href에 지정된 CSS 파일을 서버에 요청해서
이를 파싱하여 CSSOM을 생성한다.
CSS 파싱이 중단되면, HTML을 파싱하기 시작하여
DOM 생성을 재개한다.
5. 렌더 트리 생성
DOM과 CSSOM은 렌더링을 위한 렌더트리로 결합된다.
렌더트리는 렌더링을 위한 트리이기에,
렌더링되지 않는 노드 (meta, script 태그)와
css에 의해 비표시(display : none)되는 노드들은 포함하지 않는다.
다시 말해 렌더 트리는
브라우저 화면에 렌더링 되는 노드만으로 구성된다.
이후 완성된 렌더 트리는 HTML 요소의 레이아웃(위치, 크기) 계산에 사용되며
브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.
브라우저의 렌더링 과정은 반복 실행 될 수 있다.
레이아웃 계산과 페인팅이 재차 실행되는 경우가 존재한다.
리렌더링은 비용이 많이드는, 성능에 악영향을 주는 작업이기에
빈번하게 발생하지 않게 주의할 필요가 있다.
6. 자바스크립트 파싱과 실행
브라우저 렌더링 엔진은
HTML을 한 줄씩 파싱하며 DOM을 만들어가다가,
CSS 관련 코드를 만났을 때와 마찬가지로
script 태그를 만나면 DOM 생성을 일시 중단한다.
script 태그의 src에 정의된 자바스크립트 파일을 서버에 요청하거나,
태그 내의 자바스크립트 코드를 파싱하기 위해
자바스크립트 엔진에게 제어권을 넘긴다.
자바스크립트 엔진은 자바스크립트를 해석해서
AST(추상적 구문 트리)를 생성한다.
그리고 이를 기반으로 인터프리터가 실행할 수 있는 중간코드인 바이트코드를 생성하여 실행한다.
자바스크립트 파싱과 실행이 종료되면
다시 브라우저의 렌더링 엔진이 제어권을 잡고
HTML 파싱과 DOM 생성을 이어간다.
7. 리플로우와 리페인트
자바스크립트 코드에 의해 DOM이나 CSSOM이 변경될 수 있다.
이 때 변경된 DOM과 CSSOM은 렌더 트리로 다시 결합되고
렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐
브라우저 화면에 재렌더링 한다.
이를 리플로우, 리페인트라고 한다.
리플로우 - 레이아웃(노드 추가나 삭제, 요소 크기나 위치 변경 등으로 인한) 재계산
리페인트 - 재결합된 렌더트리를 기반으로 다시 페인트
8. 자바스크립트 파싱에 의한 HTML 파싱 중단
브라우저는 동기적으로,
위에서 아래 방향으로 파싱을 진행한다.
이 때 script 태그 위치에 따라서
HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다.
script 태그를 body 맨 아래에 위치시켜야하는 이유는?
1) DOM이 완성되지 않은 상태에서 JS가 DOM을 조작하면 에러가 발생할 수 있다.
2) 자바스크립트 로딩, 파싱, 실행으로 인해
HTML 요소들의 렌더링에 지장받는 일이 생기지 않는다.
따라서 페이지 로딩 시간이 줄어든다.
9. script 태그의 async/defer 어트리뷰트
JS 파싱을 위해 DOM 생성이 중단되는 문제를 해결하기 위해 나타났다.
이는 src 속성으로 외부에서 JS 파일을 가져오는 경우에만 사용할 수 있다.
이를 사용하면 HTML 파싱과 외부 JS 코드가 비동기적으로 동시에 진행된다.
하지만 JS 실행 시점에 차이가 있다.
블로그의 정보
유명한 담벼락
담담이담