개발여행의 블로그

[browser] 브라우저 렌더링 과정 본문

개발/javaScript

[browser] 브라우저 렌더링 과정

개발 여행 2021. 11. 27. 19:11
728x90

모던 자바스크립트 deep dive를 공부하면서 정리한 글입니다.

Reference - 모던 자바스크립트 deep dive

 

인터넷 주소창에 URL을 입력했을 때 사용자가 web 페이지를 보기까지 어떤 과정을 거치는가?

먼저, 간단하게 렌더링 과정을 정리해보면 아래와 같다.

사진 - developers.google 문서

 

1. 브라우저가 HTML, JavaScript, CSS, image, font file 등의 리소스를 서버로 요청하고 응답받기

 

2. 브라우저의 렌더링을 담당하는 엔진은 응답 받은 HTML과 CSS를 parsing하여 DOM(document obeject model)과 CSSOM(css object model)을 생성

 

3. DOM와 CSSOM을 결합하여 Render Tree 생성

 

4. 브라우저의 자바스크립트 엔진은 응답 받은 JavaScript를 parsing하여 Abstract Syntax Tree 즉, AST를 생성

 

5. AST를 바이트코드로 변환하여 실행. 여기서 JavaScript는 DOM API를 통해 DOM과 CSSOM을 변경 가능

 

6. 5번에서 변경된 DOM과 CSSOM은 다시 Render Tree로 결합

 

7. Render Tree를 기반으로 HTML element의 layout 즉 위치와 크기를 계산한 후 브라우저 화면에 HTML element를 페인팅

 


요청과 응답은 어떻게 이뤄지는가?

브라우저는 필요한 리소스를 서버에 요청한 후 응답받은 내용을 화면에 시각적으로 렌더링한다.

다시 말해, 렌더링에 필요한 리소스는 서버로부터 응답 받아 각 리소스를 파싱하여 렌더링한다.

 

그럼 또 의문이 들 것이다.

 

서버에 요청을 어떻게 하는가?

브라우저에는 서버에 요청을 전송하기 위한 주소창이 있다. 주소창에 URL을 입력하면 아래와 같은 순서로 서버에 요청이 전해진다.

URL이 무엇인지는 사진으로 대체한다.

 

사진 출처 - https://mwoo526.tistory.com/17

1. URL의 도메인이 DNS(Domain Name System)를 거쳐 IP 주소로 변환되면, 해당 주소를 가진 서버에 요청을 전송한다.

*DNS : 호스트의 도메인 이름을 호스트의 네트워크 주소로 바꾸거나 그 반대의 변환을 수행할 수 있도록 하기 위해 개발

 

2. 서버는 요청에 따라 루트 폴더에 존재하는 index.html을 브라우저로 응답한다.

루트 요청일 경우 어떤 리소스를 요청하는지 명시되어 있지 않지만 일반적으로 루트 요청에 대해 암묵적으로 index.html을 응답하도록 설정되어 있다고 한다. 또한 요청과 응답은 개발자도구 Network 패널에서 확인할 수 있다.

 

3. 만약 index.html에 CSS 파일을 로드하는 link나 JS파일을 로드하는 Script 태그 등을 만나면 html 파싱을 중단하고 해당 리소스를 서버에 요청하기 때문에 여러 파일을 응답받을 수 있다.

 

 

이제 응답받은 파일로

HTML을 파싱하고 DOM을 생성해보자!

서버가 response한 HTML 문서는 문자열로 이루어져 있다. 순수 텍스트를 브라우저에서 픽셀로 렌더링하려면 HTML을 브라우저가 알 수 있는 자료구조인 객체로 변환하여 메모리에 저장시켜야 한다.

 

브라우저의 렌더링 엔진은 아래의 과정을 거쳐 HTML 문서를 파싱하여 DOM(Document Object Model)을 생성하는데 DOM이 바로 브라우저가 이해할 수 있는 자료구조가 되는 것이다.

출처 - 모던 자바스크립트 deep dive

 

1. 서버에 존재하는 HTML 파일이 응답되는데, 이 때 서버는 HTML 파일을 읽어 먼저 메모리에 저장하고 메모리에 저장된 바이트를 응답한다.

 

2. 서버로부터 받은 HTML 문서(바이트 형태)는 meta 태그의 charset attribute에 의해 지정된 인코딩 방식의 문자열로 변환된다.

meta 태그의 charset attribute에 의해 지정된 인코딩 방식은 content-type: text/html; charset=utf-8 의 형식으로 respose header에 담겨온다. 브라우저는 헤더의 내용을 확인하고 문자열로 변환하는 것이다.

 

3. 문자열로 변환된 HTML 문서를 읽으면서 문법적 의미를 갖는 코드의 최소 단위인 token으로 분해한다.

 

4. 각 token은 객체로 변환하여 node를 생성한다.

이 때, token 내용에 따라 document node / element node / attribute node / text node가 생성된다. 생성된 node는 DOM을 구성하는 기본 요소가 된다.

 

5. HTML은 element의 집합으로 이루어지는데, element는 중첩 관계를 갖는다. element간의 중첩 관계에 따라 부모와 자식 관계가 형성되고, 이러한 관계를 반영하여 모든 node를 tree 자료구조로 구성한다. 마침내 node로 구성된 트리 자료구인 DOM(Document Object Model)이 완성되었다.

 

 

CSS 파싱과 CSSOM 생성에 대해서도 알아보자!

1. 브라우저의 렌더링 엔진은 HTML을 순차적으로 파싱하면서 DOM을 생성해나가는데, CSS를 로드하는 link 태그나 style 태그가 등장하면 DOM 생성을 일시 중단한다.

 

2. link 태그의 href attribute에 지정되어 있는 CSS 파일을 서버에 요청한다. 응답받은 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 파싱 과정(바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM)을 거친다.

여기서 다른 점은 CSSOM(CSS Object Model)을 생성한다는 것이다. CSSOM은 CSS의 상속을 반영하여 생성되는데 만약 부모 요소에 적용한 프로퍼티가 있다면 모두 자식 요소에 상속되어 CSSOM에 나타낸다.

 

3. CSS 파싱이 완료되면 중단된 지점부터 다시 파싱하면서 DOM을 생성한다.

 

 

 

이제 위에서 나열한 렌더링 과정 중 3번이 진행될 차례이다.

DOM와 CSSOM을 결합하여 Render Tree 생성하기

파싱 과정을 끝낸 DOM과 CSSOM은 렌더링을 위해 Render Tree로 결합된다.

 

Render Tree는 렌더링을 위한 Tree 자료 구조이다. 브라우저 화면에 렌더링 되는 노드만으로 구성된다.

이 말은 즉, 브라우저 화면에 렌더링 되지 않는 meta 태그나 script 태그 등의 노드나 CSS의 속성(display: none)에 의해 표시되지 않는 노드들은 제외된다는 것을 뜻한다.

 

완성된 Render Tree는 각 HTML 요소의 위치와 크기를 나타내는 레이아웃을 계산하는데 사용된다.

또한 Render Tree는 브라우저 화면에 픽셀을 렌더링하는 painting 처리에 의해 입력된다.

 

여기까지 살펴본 브라우저의 렌더링 과정은 반복해서 실행될 수 있다. 

 

그럼 언제 렌더링 과정이 반복되는가?

  • JavaScript에 의한 노드 추가 또는 삭제
  • 브라우저 창의 resizing에 의한 viewport 크기 변경
  • HTML 요소의 레이아웃 변경을 발생시키는 스타일의 변경
  • (width, height, margin, padding, border, display, position, top, right, left, bottom)

레이아웃 계산과 페인팅을 다시 실행하는 re-rendering은 성능에 악영향을 주는 작업이므로 re-rendering이 빈번하게 발생하지 않도록 주의해야 한다.

 

JavaScript 파싱과 실행

HTML 문서를 파싱한 결과물로 생성된 DOM은 HTML 문서의 정보뿐만 아니라 HTML 요소와 스타일 등을 변경할 수 있는 DOM API(프로그래밍 인터페이스로서)를 제공한다. DOM API를 사용하여 생성된 DOM을 동적으로 조작할 수 있다.

 

렌더링 엔진은 HTML을 한 줄씩 순차적으로 파싱하여 DOM을 생성하다가 script태그를 만나면 DOM 생성을 일시 중단한다.

 

script 태그의 src attribute에 정의된 js 파일을 서버에 요청하여 응답받고, JavaScript 파일을 파싱하기 위해 JavaScript 엔진에 제어권을 넘긴다. (JavaScript 파싱이 종료되면 렌더링 엔진으로 다시 제어권을 넘긴다.)

 

JavaScript 엔진은 JavaScript 코드를 파싱하여 CPU가 이해할 수 있는 low-level language로 변환하고 실행하는 역할을 수행한다.

(참고 - JavaScript 엔진 : 구글 크롬 / Node.js V8, 파이어폭스 SpiderMonkey, 사파리 JavaScriptCore 등)

 

제어권을 넘겨받은 JavaScript엔진은 JavaScript 코드를 파싱한다. DOM과 CSSOM을 생성하듯 JavaScript를 해석하여 AST(Abstract Syntax Tree, 추상적 구문 트리)를 생성한다.

 

AST를 기반으로 인터프리터가 실행할 수 있는 intermediate code인 바이트코드를 생성하여 실행한다.

 

출처 - 모던 자바스크립트 deep dive

 

Tokenizing

단순한 문자열 JavaScript 소스코드를 lexical analysis하여 token(문법적 의미를 갖는 코드의 최소 단위)들로 분해한다.

 

Parsing

token의 집합을 구문 분석하여 AST(Abstract Syntax Tree, 추상적 구문 트리)를 생성한다. AST는 token의 문법적 의미와 구조를 반영한 Tree 구조의 자료구조이다.

(참고 - AST는 인터프리터나 컴파일러만이 사용하는 것은 아니다. AST를 사용하면 TypeScript, Babel, Prettier와 같은 transpiler를 구현할 수도 있다. AST Explorer)

 

byte code 생성 및 실행

파싱의 결과물인 AST는 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드로 변환되고 인터프리터에 의해 실행된다. 

728x90
Comments