HTML 문서가 어떤 과정을 거쳐 사용자에게 시각적으로 보여지게 될까?
우선, HTML은 문서이며 문자열
로 이루어진 순수한 텍스트다. 따라서 이러한 문자열 자체를 브라우저가 이해할 수 있게 변환해야만 비로소 시각적으로 보여질 수 있게 된다.
이때 문자열을 브라우저가 이해할 수 있는 것으로 변환하는 과정을 파싱
이라고 한다.
HTML 파싱은 브라우저 렌더링 엔진
에 의해 다음 순서대로 이루어진다.
바이트
형태로 응답 받는다.문자열
로 변환한다.meta
태그의 charset
어트리뷰트에 선언된 인코딩 방식을 이용한다.토큰
들로 분해한다.노드
를 생성한다.렌더링 엔진은 HTML을 첫 번째 라인부터 순차적으로 파싱한다. 따라서 위에서부터 차례대로 읽어 나가다가 CSS
를 로드하는 link
태그나 style
태그를 만나면 DOM 생성을 잠시 중단하고 CSS를 파싱한다.
렌더링 엔진은 CSS 파일을 서버에 요청해 받은 다음 HTML과 동일한 파싱 과정(바이트 → 문자 → 토큰 → 노드 → CSSOM)을 거쳐 CSSOM
을 생성한다. (style 태그의 경우 서버에게 요청하는 과정이 없고 바로 파싱)
CSSOM
란 CSS 객체 모델이다.
CSSOM
은 CSS의 상속을 반영하여 생성된다. 상속을 반영하기 때문에, 예시로 부모 태그인 body에 font-size 속성을 지정한 경우 body의 자식 요소도 body와 동일한 font-size 속성을 갖게 된다.
렌더링 엔진이 HTML과 CSS를 각각 파싱하면 DOM과 CSSOM이 생성된다고 했다.
이 다음으로는, 이때 생성된 객체 모델들(DOM과 CSSOM)을 렌더 트리
로 결합한다.
렌더 트리란 렌더링을 위한 트리 구조의 자료구조다. 브라우저 화면에 표시할 노드들을 담고 있으며 CSS에 비표시되는 노드들은 포함하지 않는다.
렌더 트리
는 브라우저 화면에 렌더링될 노드들로만 구성되어 있다.
이 렌더 트리
는 각 요소의 위치나 크기 등 레이아웃을 계산하는데 사용되고, 브라우저 화면에 픽셀을 렌더링하는 페인팅
처리에 입력된다.
페인팅
이란, 브라우저 화면에 픽셀을 렌더링하는 작업이다.
렌더링 과정을 요약하자면,
HTML
과 CSS
를 파싱하여 DOM 트리
와 CSS 트리
를 생성하고,
두 트리를 하나로 합쳐 렌더 트리
를 생성한 다음,
렌더 트리
를 바탕으로 레이아웃을 계산해 브라우저에 페인팅한다.
이때, DOM이나 CSS에 조작(변경)이 발생한다면 어떻게 될까?
노드를 추가/삭제하거나 요소의 크기/위치를 변경하거나 윈도우의 사이즈를 변경하는 등 레이아웃에 영향을 주는 변경이 생기면 reflow
가 발생한다.
reflow
란 레이아웃을 다시 계산하는 것을 말한다.
reflow가 발생하면 변경한대로 다시 브라우저에 그리는 repaint
가 발생한다.
repaint
란 재결합된 렌더 트리를 기반으로 다시 페인트를 칠하는 것을 말한다.
만약 레이아웃에 영향이 없는 변경이라면 repaint
만 발생하지만, 어찌됐든 reflow
와 repaint
즉, 리렌더링은 비용이 많이 드는 작업이다. 즉, 성능에 악영향을 줄 수 있다.
구글에서는 웹페이지의 reflow를 최소화하기 위한 가이드라인을 제시하고 있다.
HTML 파일에는 CSS 이외에도 JavaScript 파일을 로드할 수 있다. 그렇다면 JavaScript는 어떻게 로드될까?
마찬가지로 렌더링 엔진이 순차적으로 파싱하다가 script를 만나면 DOM 생성을 일시 중단한다. 이때 CSS와 다른 점은 렌더링 엔진이 아닌 자바스크립트 엔진
이 자바스크립트를 파싱한다는 점이다.
자바스크립트 엔진은 자바스크립트 코드를 파싱하여 AST
를 생성한다.
AST
란 추상적 구문 트리(Abstract Syntax Tree)를 말한다.
이 AST를 기반으로 인터프리터가 실행할 수 있는 바이트 코드를 생성해 실행한다.
즉, 자바스크립트 엔진은 자바스크립트 코드를 파싱해 저수준 언어로 변환하고 실행한다.
렌더링 엔진과 자바스크립트 엔진은 병렬적으로 파싱하지 않고 직렬적으로 파싱한다.
즉, 렌더링 엔진의 파싱과 자바스크립트 엔진의 파싱은 동시에 일어날 수 없다.
렌더링 엔진은 DOM을 생성하다가 script 태그를 만나면 HTML 파싱은 블로킹되고 자바스크립트 코드를 파싱하기 시작한다.
자바스크립트 코드에서 DOM을 조작하려면 HTML이 파싱되어 DOM이 생성되어 있어야만 한다. 자바스크립트 코드가 아직 생성되지 않은 DOM을 조작하려 한다면 에러가 발생한다.
따라서 script 태그는 DOM이 모두 생성된 시점일 body의 마지막에 위치시키는 것이 바람직하다.
뿐만 아니라 script 태그를 body의 마지막에 위치시킨다면 자바스크립트 코드의 로딩/파싱/실행 즉, 블로킹으로 인해 HTML 요소들의 렌더링에 지장받는 일이 발생하지 않아 페이지 로딩 시간이 단축될 수 있다.