[HOW]브라우저는 어떻게 화면을 보여줄까?

HOW

 ⋅ Dec 27, 2023 ⋅ 3 min read

브라우저 렌더링 과정
브라우저 렌더링 과정

우리는 chrom, safari, edge와 같은 브라우저를 사용하여 여러 웹 서비스를 이용합니다. 주소창에 우리가 사용하고자 하는 주소를 입력하면 웹 서비스가 화면에 나타납니다. 지금부터 브라우저 창에 웹 서비스 화면이 나오는 과정에 대해 알아보도록 하겠습니다!


1. 요청과 응답

화면을 구성(렌더링) 하는 데 필요한 자원은 모두 웹 서버에 존재합니다. 따라서 이 자원(리소스)을 웹 서버에 요청하여 응답받습니다.

서버에 요청하기 위해 브라우저는 주소창을 제공하며 주소창에 URL을 입력하면 이는 URL의 호스트 이름이 DNS를 통해 IP 주소로 변환되고, 변환된 IP 주소를 갖는 웹 서버에 리소스를 요청합니다. 이후 웹 서버는 요청에 맞는 리소스를 브라우저(클라이언트)에 응답합니다.

2. HTTP 1.1과 HTTP 2.0

HTTP(HyperText Transfer Protocol)는 웹에서 브라우저와 서버가 통신하기 위한 프로토콜(규약)입니다. 1999년 발표된 HTTP 1.1과 2015년 발표된 HTTP 2.0의 차이점을 알아보도록 하겠습니다.

HTTP 1.0은 클라이언트와 서버 사이의 커넥션당 하나의 요청과 응답만 처리합니다. 따라서 응답받은 HTML 내에 있는 리소스 요청들은 개별적으로 전송되고 개별적으로 응답받습니다. 이는 요청할 리소스의 개수에 비례하여 응답 시간이 증가하는 단점이 존재합니다.

반면, HTTP 2.0은 커넥션 당 여러 개의 요청과 응답이 가능합니다. 따라서, HTTP 1.1보다 로딩 속도가 약 50% 개선되었습니다.

3. HTML 파싱과 DOM 생성

서버로부터 받아온 HTML은 사람들이 알아볼 수 있는 언어로 작성되어 있기 때문에, 이를 브라우저가 인식할 수 있는 객체로 변환하여 보관해야 합니다. 브라우저 렌더링 엔진은 HTML 문서를 파싱하는 과정을 거쳐 브라우저가 읽을 수 있는 DOM(Document Object Model)을 생성합니다.

HTML의 DOM 변환 과정

  1. 서버에서 요청받은 HTML 파일을 메모리에 저장한 후 메모리에 저장된 바이트를 응답
  2. 브라우저는 바이트 형태로 응답받아 meta 태그의 charset 어트리뷰트에 선언된 인코딩 방식을 기준으로 문자열 형태로 변환
  3. 변환된 HTML 문서를 토큰 단위로 분해
  4. 토큰들을 객체로 변환하여 노드 생성
  5. HTML 요소 간의 관계를 반영한 모든 노드들을 트리 자료구조 형태로 구성하며, 이를 DOM이라고 함

4. CSS 파싱과 CSSOM 생성

HTML을 한 줄씩 파싱 하던 렌더링 엔진은 CSS를 로드하는 태그를 만나면 파싱을 멈추고 CSS 파일을 요청합니다. 요청한 CSS 파일을 응답받으면 HTML 파싱 후 DOM 생성 과정과 동일한 방식으로 CSSOM을 생성합니다. 이후 중단되었던 위치부터 HTML을 파싱 하여 DOM 생성을 이어갑니다.

5. 렌더 트리 생성

생성된 DOM과 CSSOM을 결합하여 렌더 트리를 생성합니다. 렌더 트리는 렌더링을 위한 트리 구조의 자료구조로 브라우저 화면에 출력할 노드로만 구성됩니다. 렌더 트리는 HTML 요소의 레이아웃(위치, 크기)을 계산하는 데 사용되며 픽셀을 렌더링 하는 페인팅 처리에 입력됩니다.

브라우저의 렌더링 과정은 특정 상황(자바스크립트에 의한 수정, 뷰포트 크기 변경, 레이아웃 변경)에서 반복되어 실행될 수 있습니다. 렌더링 과정이 반복 실행되는 리렌더링 과정은 성능에 악영향을 주는 작업으로 리렌더링이 자주 발생하지 않도록 해야 합니다.

6. 자바스크립트 파싱과 실행

DOM은 HTML 문서의 구조와 정보뿐만 아니라 HTML 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스인 DOM API를 제공합니다. DOM API를 사용하여 동적으로 DOM 조작이 가능합니다.

HTML 파싱 과정에서 자바스크립트를 로드하는 태그를 만나면 DOM 생성을 중단하고 웹 서버에 자바스크립트 파일을 요청합니다. 자바스크립트 파일을 응답받으면 자바스크립트 코드 파싱을 위해 자바스크립트 엔진에 제어권을 넘깁니다.

자바스크립트 엔진은 코드를 파싱 하여 CPU가 이해할 수 있는 저수준 언어로 변환한 뒤 실행합니다. 자바스크립트 엔진은 코드를 해석하여 AST(Abstract Syntax Tree)를 생성하여, 이를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드를 생성하여 실행합니다.

자바스크립트 파싱과 실행 과정

  1. 토크나이징: 자바스크립트 소스코드를 어휘 분석하여 코드의 최소 단위인 토큰으로 분해
  2. 파싱: 토큰들의 집합을 구문 분석하여 AST 생성
  3. 바이트코드의 생성과 실행: 생성된 AST를 인터프리터가 실행할 수 있는 바이트코드로 변환하여 인터프리터에 의해 실행됩니다.

7. 리플로우와 리페인트

리플로우는 레이아웃 재계산하는 것을 의미합니다. 노드를 추가하거나 삭제, 요소의 크기 또는 위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생하는 경우 실행됩니다.

리페인트는 리플로우로 인해 재결합된 렌더 트리를 기반으로 다시 페인트하는 것을 발합니다.

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

렌더링 엔진과 자바스크립트 엔진은 병렬적으로 실행되지 않습니다. 따라서 브라우저는 동기적으로 파싱하고 실행되기 때문에 DOM 생성을 중단시키는 자바스크립트 요청을 하는 script 태그의 위치는 중요합니다.

HTML 파싱 중 script 태그를 만난 후 자바스크립트 코드를 파싱 하는 과정에서 DOM API를 사용할 경우 DOM과 CSSOM이 이미 생성되어 있어야 합니다. 만약 생성되지 않았다면 문제가 발생할 수 있습니다.

따라서, 위와 같은 문제를 방지하기 위해 HTML의 body 요소 아래에 script 태그를 위치하는 것이 좋습니다. 이는 자바스크립트 실행 시점을 렌더링 엔진이 DOM 생성을 마무리한 후로 고정하는 것으로 문제 발생이 사라지는 효과를 누릴 수 있습니다. 또한, 자바스크립트 실행 전 DOM 생성이 마무리되어 렌더링 되므로 페이지 로딩 시간이 단축되는 효과를 가져올 수 있습니다.

9. script 태그의 async/defer 어트리뷰트

HTML5에는 자바스크립트 파싱에 의한 중단 문제를 해결하기 위해 script 태그에 async, defer 어트리뷰트가 추가되었습니다. async와 defer 어트리뷰트는 src 어트리뷰트를 통해 외부 자바스크립트 파일 로드 시 사용할 수 있습니다. async와 defer 어트리뷰트 사용은 HTML 파싱과 자바스크립트 파일 로드가 비동기적으로 동시에 진행되지만, 자바스크립트 실행 시점에 차이가 존재합니다.

async 어트리뷰트

HTML 파싱과 자바스크립트 로드가 비동기적으로 동시에 진행되지만, 자바스크립트의 파싱과 실행은 자바스크립트 파일 로드 완료 이후 진행되며, HTML 파싱이 중단됩니다. 여러 개의 script 태그에 async 어트리뷰트를 지정하면 script 태그 순서와 상관없이 로드 완료 순서대로 파싱이 실행되어 순서가 보장되지 않습니다.

defer 어트리뷰트

async 어트리뷰트와 마찬가지로 HTML 파싱과 자바스크립트 파일 로드가 비동기적으로 진행되지만, 자바스크립트의 파싱과 실행은 DOM 생성이 완료된 이후(DOMContentLoaded 이벤트 발생) 진행됩니다. 따라서 DOM 생성 이후 실행되어야 할 자바스크립트에 유용합니다.


참고

이웅모, ⌈모던 자바스크립트 - Deep Dive⌋, 2020, 38_브라우저의 렌더링 과정(p.660 - 676)

LINKS

© Copyright 2021

made by React.js & gatsby