38. 브라우저의 렌더링 과정
브라우저는 다음과 같은 과정을 거쳐 렌더링을 수행한다.
1. 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
2. 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
3. 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 ASTAbstract Syntax Tree를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
4. 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
38.1 요청과 응답
브라우저의 핵심 기능은 필요한 리소스(HTML, CSS, 자바스크립트, 이미지, 폰트 등의 정적 파일 또는 서버가 동적으로 생성한 데이터)를 서버에 요청request하고 서버로부터 응답response받아 브라우저에 시각적으로 렌더링하는 것이다. 즉, 렌더링에 필요한 리소스는 모두 서버에 존재하므로 필요한 리소스를 서버에 요청하고 서버가 응답한 리소스를 파싱하여 렌더링하는 것이다.
서버에 요청을 전송하기 위해 브라우저는 주소창을 제공한다. 브라우저의 주소창에 URL을 입력하고 엔터 키를 누르면 URL의 호스트 이름 이 DNS 를 통해 IP 주소로 변환되고 이 IP 주소를 갖는 서버에게 요청을 전송한다.
서버로부터 리소스를 받은 후 브라우저 동작 원리
- 브라우저는 서버로부터 HTML, CSS, Javascript, 이미지 파일 등을 응답받는다.
- HTML, CSS 파일은 렌더링 엔진의 HTML 파서와 CSS 파서에 의해 파싱(Parsing)되어 DOM, CSSOM 트리로 변환되고 렌더 트리로 결합된다. 이 렌더 트리를 기반으로 브라우저는 웹페이지를 표시한다.
- 자바스크립트는 자바스크립트 엔진이 처리하는데, HTML 파서는 script 태그를 만나면 자바스크립트 코드를 실행하기 위해 DOM 생성 프로세스를 중지하고 자바스크립트 엔진으로 제어 권한을 넘긴다. 제어 권한을 넘겨 받은 자바스크립트 엔진은 script 태그 내의 스크립트 코드 또는 script 태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 로드하고 파싱하여 실행한다. 자바스크립트의 실행이 완료되면 다시 HTML 파서로 제어 권한을 넘겨서 브라우저가 중지했던 시점부터 DOM 생성을 재개한다.
HTML 파싱과 DOM 생성
서버로부터 응답 받은 HTML 문서는 문자열로 이루어진 순수한 텍스트이다. 이를 브라우저에 시각적인 픽셀로 렌더링하려면 HTML 문서를 브라우저가 이해할 수 있는 자료구조(객체) 로 변환하여 메모리에 저장해야 한다. 아래 그림의 각 토큰을 객체로 변환하여 노드들을 생성한다. 즉, DOM 은 HTML 문서를 파싱한 결과물이다.
CSS 파싱과 CSSOM 생성
link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS 를 HTML 과 동일한 파싱 과정(바이트-> 문자 -> 토큰 -> 노드 -> CSSOM) 을 통해 CSS Object Model 을 생성한다.
렌더 트리 생성
렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기) 을 계산하는 데 사용되고 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.
이후 완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅_painting 처리에 입력된다.
자바스크립트 파싱과 실행
자바스크립트 엔진은 자바스크립트 코드를 파싱하여 CPU 가 이해할 수 있는 low-level language 로 변환하고 실행한다. 이 엔진은 자바스크립트를 해석하여 추상적 구문 트리를 생성한다. 그리고 이를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트코드를 생성하여 실행한다.
토크나이징_tokenizing
단순한 문자열인 자바스크립트 소스코드를 어휘 분석_lexical analysis 하여 문법적 의미를 갖는 코드의 최소 단위인 토큰_token들로 분해한다. 이 과정을 렉싱_lexing이라고 부르기도 하지만 토크나이징과 미묘한 차이가 있다.
파싱_parsing
토큰들의 집합을 구문 분석_syntactic analysis하여 ASTAbstract Syntax Tree(추상적 구문 트리)를 생성한다. AST는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조다.
바이트코드 생성과 실행
파싱의 결과물로서 생성된 AST는 인터프리터가 실행할 수 있는 중간 코드인 바이트코드로 변환되고 인터프리터에 의해 실행된다.
리플로우와 리페인트
리플로우는 레이아웃 계산을 다시하는 것으로 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우 실행된다. 리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 것을 말한다.
38.8 자바스크립트 파싱에 의한 HTML 파싱 중단
렌더링 엔진과 자바스크립트 엔진은 병렬적으로 파싱을 실행하지 않고 직렬적으로 파싱을 수행한다.
이처럼 브라우저는 동기적_synchronous으로, 즉 위에서 아래 방향으로 순차적으로 HTML, CSS, 자바스크립트를 파싱하고 실행한다. 이것은 script 태그의 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다는 것을 의미한다. 따라서 script 태그의 위치는 중요한 의미를 갖는다.
위 예제의 경우 app.js의 파싱과 실행 이전까지는 DOM의 생성이 일시 중단된다. 이때 자바스크립트 코드(app.js)에서 DOM이나 CSSOM을 변경하는 DOM API를 사용할 경우 DOM이나 CSSOM이 이미 생성되어 있어야 한다. 만약 DOM을 변경하는 DOM API를 사용할 때 DOM의 생성이 완료되지 않은 상태라면 문제가 발생할 수 있다.
문제
해결
자바스크립트가 실행될 시점에는 이미 렌더링 엔진이 HTML 요소를 모두 파싱하여 DOM 생성을 완료한 이후다. 따라서 DOM이 완성되지 않은 상태에서 자바스크립트가 DOM을 조작하는 에러가 발생할 우려도 없다. 또한 자바스크립트가 실행되기 이전에 DOM 생성이 완료되어 렌더링되므로 페이지 로딩 시간이 단축되는 이점도 있다.
-알라딘 eBook <모던 자바스크립트 Deep Dive> (이웅모 지음) 중에서