브라우저의 렌더링 과정

silverj-kim·2023년 7월 30일
0
post-custom-banner

모던 자바스크립트 딥 다이브 스터디중!

웹 애플레이션의 클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행한다. 따라서 브라우저 환경을 고려할 때 보다 더 효율적인 클라이언트 사이드 자바스크립트 프로그래밍이 가능하다.

자바스크립트의 기본 개념과 동작원리

브라우저는 HTML, CSS, 자바스크립트로 작성된 텍스트 문서를 어떻게 파싱해서 브라우저에 렌더링 하는 걸까?

  1. 브라우저는 HTML, CSS, Javascript, Image, Font 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
  2. 브라우저 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이를 결합하여 Render Tree를 생성한다.
  3. 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다.
  4. 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.

💡 AST Abstract Syntax Tree
컴퓨터 과학에서 추상 구문 트리(abstract syntax tree, AST), 또는 간단히 구문 트리(syntax tree)는 프로그래밍 언어로 작성된 소스 코드의 추상 구문 구조의 트리이다. 이 트리의 각 노드는 소스 코드에서 발생되는 구조를 나타낸다. 구문이 추상적이라는 의미는 실제 구문에서 나타나는 모든 세세한 정보를 나타내지는 않는다는 것을 의미한다.

코드 텍스트에서 트리구조의 데이터 스트럭처를 만들어낸다. 코드에 있는 아이템이 각 트리에 있는 노드와 매치된다. 먼저, 렉시컬 분석을 통해 코드의 문자들을 읽어서 정해진 룰에 따라 토큰으로 만들어야 한다. 그런 후 syntax 분석에서는 앞서 나온 토큰 목록을 트리구조로 만든다.


요청과 응답

by wiki

  1. 브라우저 주소창에 위와 같은 URL을 입력한다. 이 때 http://www.example.com 와 같이 루트 요청을 하게 되면 일반적으로 index.html을 응답하도록 기본 설정이 되어 있다.
  2. URL의 host 이름이 DNS를 통해 IP 주소로 변환되고 이 IP 주소를 갖는 서버에게 요청을 전송한다.

💡DNS
DNS는 IP 주소 및 기타 데이터를 저장하고 이름별로 쿼리할 수 있게 해주는 계층형 분산 데이터베이스입니다. 즉, DNS는 컴퓨터가 서로 통신하는 데 사용하는 숫자 IP 주소로 변환되는, 쉽게 읽을 수 있는 도메인 이름의 디렉터리입니다.


HTTP 1.1과 HTTP 2.0

HTTP

HTTP(HyperText Transfer Protocol)는 웹에서 브라우저와 서버가 통신하기 위한 요청/응답 프로토콜이다. 1990년대 초에 설계된 HTTP는 거듭하여 진화해온 확장 가능한 프로토콜이다. HTTP는 애플리케이션 계층의 프로노콜로 신뢰 가능한 전송 프로토콜이라면 이론상이로는 무엇이든 사용할 수 있으나 TCP 혹은 TLS를 통해 전송된다.

  • 확장 가능하다.
  • 무상태성(Stateless)
  • 비연결성(Connectionless) , 연결은 전송 계층에서 제어되므로 근본적으로 HTTP 영역 밖이다.

HTTP 1.1

HTTP1.0과 1.1의 차이

  1. 커넥션 유지 (Persistent Connection)
    HTTP 프로토콜은 응용 계층의 프로토콜이다. HTTP를 통한 데이터 전송은 TCP 세션 기반에서 이루어진다.

    1.0은 매번 데이터를 요청하고 수신할 때 마다 TCP 세션을 맺어야한다.
    반면, 1.1은 한번의 TCP 세션에 여러 개의 요청을 보내고 응답을 수신할 수 있다.
    TCP 세션 처리 부하를 줄이고 응답속도를 개선할 수 있다.

  2. 파이프라이닝 (Pipelining)
    명령어를 순차적으로 실행하는 프로세서에 적용되는 기술로 여러개의 명령어를 실행하는 기법이다.

    HTTP 요청은 순차적으로 이루어진다. 파이프라이닝 기능이 없는 경우에는 요청에 대한 응답이 오고 나서 다음 요청을 할 수 있다. 만약 첫번째 요청이 실패한다면 2,3번째 요청은 진행하지 못한다는 단점이 있다.
    파이프라이닝을 통하면 동시에 요청 1,2,3을 보내고 이에 대한 각각의 응답을 받아 처리할 수 있다.

  1. 호스트 헤더 (Host Header)
    1.0 환경에서는 하나의 IP에 여러 개의 도메인 운영이 불가능 해 도메인 만큼 서버의 개수도 늘어날 수 밖에 없는 구조이다.

    1.1에서부턴 Host 헤더의 추가를 통해 가상 호스팅(Virtual Hosting)이 가능해졌다.
  1. 강력한 인증 절차 (Improved Authentication Procedure)
    1.1에서 proxy-authenticationproxy-authorization 2개의 헤더가 추가되었다.
    서버-클라이언트 사이의 인증을 요구하는 헤더는 1.0에도 있었으나 서버-클라 사이에 프록시가 있는 경우 프록시가 사용자의 인증을 요구할 수 있는 방법이 없었다.

HTTP 2.0

15년만에 등장한 HTTP 2.0
1.1의 문제점을 개선한 2.0의 차이에 대해 알아보자.

  1. Multiplexed Streams
    1.0에서 TCP 세션을 맺는 것을 중복해서 수행하는 성능 이슈가 있었고 1.1에서 파이프라이닝과 커넥션 유지로 해당 문제를 일부 해결했었다.

2.0에서는 더 나아가 multiplexed 라는 기술을 도입해 1개의 세션으로 여러 개의 요청을 순서 상관없이 Stream으로 받아서 동시다발적으로 처리하고 응답할 수 있다. 이는 1.1의 HOL 블로킹 (Head-Of-Line Blocking) 문제를 해결한다.

  1. Stream Priorityzation
    HTTP 2.0은 요청을 Stream 형식으로 처리하게 된다. 그리고 각 요청에 대한 Priority를 부여한다.
  1. Server Push
    기존 1.1에서는 HTML 문서를 요청한 후 해당 문서에 필요한 CSS, assets를 추가 요청했었다.
    2.0 부터는 HTML 문서를 요청하면 클라이언트가 추가로 요청을 하지 않아도 된다.
  1. Header Compression
    클라와 서버는 각 Header Table을 관리하고, 이전 요청과 동일한 필드는 table의 index만 보내고 변경되는 값은 Huffman 인코딩 후 보냄으로 Header의 크기를 경량화 하였다.

HTML 파싱과 DOM 생성

서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트이다. 그 문서를 브라우저에서 시각적인 픽셀로 렌더링하려면 HTML 문서를 브라우저가 이해할 수 있는 객체로 변환하여 메모리에 저장해야 한다.

업로드중..

  1. 서버로부터 HTML 파일을 응답받는다. 응답된 바이트 형태의 문서는 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식(UTF-8)을 기준으로 문자열로 변환된다. 브라우저는 이를 확인하고 문자열로 변환한다.
  2. 문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 코드의 최소단위인 토큰으로 분해(lexical analysis) 한다.
  3. 각 토큰들을 객체로 변환한여 node를 생성한다. 노드는 이후 DOM을 구성하는 기본 요소가 된다.
  4. HTML 문서는 HTML 요소들의 집합으로 이루어지며 HTML 요소는 중첩관계를 갖는다. 트리 자료구조를 구성하여 DOM을 만든다. (syntax analysis)

CSS 파싱과 CSSOM 생성

렌더링 엔진은 DOM을 한 줄씩 파싱하여 DOM을 생성해가다 CSS를 로드하는 코드를 만나면 DOM생성을 일시 중단한다. CSS를 HTML과 동일한 파싱과정을 거쳐 CSSOM(css object model)을 생성한다.


렌더 트리 생성

HTML, CSS을 각각 파싱하여 DOM과 CSSOM을 생성한 후 렌더링을 위한 렌더 트리 render tree로 결합된다.
렌더 트리는 렌더링을 위한 트리구조의 자료구조로, 화면에 렌더링되지 않는 노드(ex. script tag) 나 css에 의해 비표시 (ex. display:none) 되는 노드들은 포함하지 않는다.

렌더트리는 각 HTML 요소의 레이아웃을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.

- 자바스크립트에 의한 노드 추가 또는 삭제
- 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
- HTML 요소의 레이아웃 변경을 발생시키는 스타일 변경

위와 같은 변경은 리플로우와 리페인팅을 발생시켜 성능에 악영향을 주니 주의해야 한다.


자바스크립트 파싱과 실행

HTML 문서를 파싱한 결과물로 생성된 DOM은 문서의 구조와 정보 뿐 아니라 요소를 조작할 수 있는 DOM API도 제공한다. 자바스크립트 코드에서 DOM API를 사용하면 이미 생성된 DOM을 동적으로 조작할 수 있다.
CSS 파싱과 마찬가지로 DOM을 생성해다가 자바스크립트 파일 로드 코드를 마주하면 DOM 생성을 일시 중지한다. script 태그 내 코드를 파싱하기 위해서는 렌더링 엔진 -> 자바스크립트 엔진으로 제어권을 넘긴다. 따라서 다시 렌더링 엔진으로 제어권이 돌아왔을 때 DOM을 이어 생성한다.

자바스크립트 엔진은 자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어로 변환하고 실행하는 역할을 한다. V8, SpiderMonkey, JavascriptCore 등이 존재하며 모두 ECMAScript를 준수한다. 또, 자바스크립트 엔진은 자바스크립트 코드를 해석하여 AST(Abstract Synctax tree)를 생성하고 그걸 기반으로 인터프리터가 실행할 수 있는 중간코드인 바이트 코드를 생성하여 실행한다.

Javascript > Token > AST > ByteCode > 인터프리터에 의해 실행

리플로우와 리페인팅

한번 생성한 DOM, CSSOM 을 다시 변경시키면 다시 Render tree를 결합하고 그 렌더트리를 기반으로 레이아웃과 페인팅 과정을 거쳐 다시 브라우저 화면에 렌더링한다. 레이아웃계산을 다시 하는 것을 리플로우, 재결합된 렌더트리를 다시 페인팅 하는 것은 리페인팅 이라고 한다.
무조건 같이 실행되는 것은 아니고 레이아웃의 영향이 없는 변경은 리페인팅만 실행된다.


자바스크립트 파싱에 의한 HTML 파싱 중단

브라우저는 동기적으로 HTML 코드를 읽어 순차적으로 파싱되기 때문에 script태그의 위치에 따라 에러가 발생될 수 있다. DOM이 생성되기 전에 자바스크립트에서 DOM을 조작한다면 에러가 발생할 것이다. 이런 에러를 방지하기 위해 script 코드는 아래에 위치 시키는 것이 좋다.


script 태그의 async/defer

DOM 생성이 중단되는 문제를 근본적으로 해결하기 위해 HTML5부터 script 태그에 async와 defer 어트리뷰트가 추가되었다.

두 어트리뷰트 모두 파일의 로드가 비동기적으로 진행된다는 공통점이 있다. 하지만 자바스크립트의 실행 시점에 차이가 있다.

<script async src="x.js"/>

자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되면 이 때 HTML 파싱이 중단된다. 여러 개 사용 시 순서가 보장되지 않는다. 순서 보장이 필요하면 async를 지정하지 않아야 한다.

<script defer src="x.js"/>

자바스크립트의 파싱과 실행은 HTML 파싱이 완료된 직후, DOM 생성이 완료된 직후 (DOMContentLoaded event 실행) 진행된다. DOM 생성이 완료된 이후 실행되어야 할 자바스크립트에 유용하다.

https://yceffort.kr/2021/05/ast-for-javascript
https://developer.mozilla.org/ko/docs/Web/HTTP/Overview
https://withbundo.blogspot.com/2021/02/http-http-10-http-11.html
https://jaehoney.tistory.com/281
https://ko.javascript.info/dom-nodes

profile
Front-end developer
post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 7월 30일

잘 읽었습니다. 좋은 정보 감사드립니다.

1개의 답글