브라우저의 렌더링 과정에 대해서 한번 알아보도록 하자.
브라우저는 다음과 같은 일련의 과정을 거쳐 렌더링을 수행한다.
브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
브라우저의 렌더링 엔진은 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이 때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더트리로 결합된다.
렌더트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
렌더링에 필요한 리소스는 모두 서버에 있으므로 서버에 요청을 하고 서버가 응답한 리소스를 파싱하여 렌더링해야 한다.
브라우저의 주소창에 URL을 입력하고 엔터를 누르면 서버에게 요청을 보내는데 URI는 다음과 같이 구성된다.
어떤 사이트에서 폰트나, css, js파일들은 요청하지 않았는데도 응답으로 받아지는 것을 확인할 수 있다.
브라우저의 렌더링 엔진이 HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그 link, img, script 태그 등을 만나면 HTML 파싱을 중단하고 해당 리소스 파일을 서버로 요청하기 때문이다.
HTML 자체는 순수한 텍스트인데 이를 시각적인 픽셀로 렌더링하려면 HTML 문서를 브라우저가 이해할 수 있도록 자료구조에 변환하여 메모리에 저장해야 한다.
서버에 HTML 파일을 요청하고 응답을 받는다.
서버가 응답한 HTML을 2진수 형태로 응답받고 이 HTML 문서는 charset
어트리뷰트 방식으로 문자열로 변환된다.
문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 최소 단위인 토큰으로 분해한다.
각 토큰들을 객체로 변환해서 노드를 생성한다.
HTML 문서의 요소들은 중첩 관계를 갖는다. 이러한 중첩 관계를 반영하여 트리 자료구조를 구성하는데 이걸 DOM 이라고 부른다.
렌더링 엔진은 HTML을 파싱하여 DOM을 생성해 나간다. 그러다 link나 style 태그를 만나면 DOM 생성을 일시 중단한다.
바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM 파싱 과정을 거치며 CSSOM을 생성하게 된다.
2번 3번에서 보았듯이 렌더링 엔진은 HTML과 CSS를 파싱해서 DOM과 CSSOM을 생성한다. 그리고 이 둘을 렌더 트리로 결합한다.
렌더 트리는 브라우저 화면에 렌더링되는 노드만으로 구성된다 (display : none) 이런 요소들을 제거된다
이런 동작들이 일어나면 브라우저는 렌더링 과정을 반복하게 된다.
width/height/padding
등 스타일 변경자바스크립트 코드에서 DOM API를 이용하면 이미 생성된 DOM을 동적으로 조작할 수 있다.
HTML을 파싱하다가 script 태그를 만나면 DOM 생성을 일시 중단한다. 이를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다.
JS엔진은 JS를 해석하여 AST를 생성한다. 그리고 AST를 기반으로 바이트코드를 생성하여 실행한다.
리플로우는 레이아웃을 다시 계산하는 것을 말하고 노드 추가/ 삭제, 요소 크기/ 위치 변경 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행된다.
리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트 하는 것을 말한다.
렌더링 엔진과 자바스크립트 엔진은 병렬적이지 않고 직렬적으로 파싱을 수행한다.
즉 위에서 부터 순차적으로 파싱하기 때문에 script
태그의 위치에 따라 HTML 파싱이 블로킹 되어 DOM 생성이 지연될 수 있다는 것을 의미한다.
이러한 이유로 script태그는 일반적으로 body 태그의 가장 하단에 위치시킨다.
앞에서 살펴본 DOM 생성 중단 문제를 근본적으로 해결하기 위해 HTML5 부터는 async와 defer가 추가되었다.
async
이 어트리뷰트를 사용하면 HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다.
자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되며 이 때 HTML 파싱이 중단된다.
여러 script 태그에 async를 달아두면 태그의 순서가 보장되지 않는다.
defer