면접 중 질문을 받게 되었는데 답변을 완벽하게 하지 못했다는 느낌이 들었다. 이번 기회에 충분한 정리하고 정보 공유를 위해서 브라우저 렌더링에 대한 글을 작성하게 되었다.
브라우저의 기본 구조
브라우저의 기본적인 구조는 다음과 같다.
- 사용자 인터페이스 - 주소표시줄, 뒤로 가기/앞으로 가기 버튼 등 요청 페이지를 제외한 인터페이스
- 브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진사이의 동작을 제어
- 렌더링 엔진 - 요청한 컨텐츠를 표시, HTML과 CSS를 파싱하여 화면에 표시
- 통신 - HTTP 요청과 같은 네트워크 호출에 사용
- UI 백엔드 - 버튼, 체크 박스와 같은 기본적인 위젯를 그림
- 자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행
- 자료 저장소 - localStorage, 쿠키와 같이 로컬에 데이터를 저장하는 파트
이 내용을 토대로 간략한 브라우저 렌더링 과정은 다음과 같다.
- 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답받는다.
- 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML, CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
- 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
- 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML요소를 페인팅한다.
조금 더 깊은 내용을 위해 우리가 학습해야 할 부분은 렌더링 엔진이다.
렌더링 엔진
렌더링 엔진을 통해 XML 문서와 이미지도 표시할 수 있고 플러그인과 브라우저 확장 기능을 통해 PDF와 다른 유형의 파일도 있다. 이번 글에서는 HTML을 표시하기 위해 동작되는 내용들을 다루려고 한다.
HTML을 파싱하며
렌더링 엔진은 HTML을 파싱하여 DOM을 생성한다. 개발자 도구의 네트워크 탭을 열어보면 css, 자바스크립트, 이미지, 폰트 파일들도 응답받는다. 이는 브라우저 렌더링 엔진이 HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그 link, img, script 등을 만나면 파싱을 일시 정지하고 해당 리소스 파일을 서버로 요청하기 때문이다. 작업이 끝나면 다시 HTML을 파싱하여 DOM tree를 구축한다.
일시정지된 상황에서 CSS를 파싱하며 CSSOM을 생성하고 CSSOM tree를 구축한다. DOM tree와 CSSOM tree를 합하여 렌더 트리를 구축하기에 이를 하나의 과정으로 간략화하는 경우가 있는 것 같다.
이제 이 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 화면에 페인팅하고 이를 합성(Composite)함으로 렌더링 과정을 설명할 수 있다.
다음과 같은 경우 브라우저 렌더링 과정은 반복해서 실행되고 레이아웃 계산과 페인팅이 재차 실행된다.
- 자바스크립트에 의한 노드 추가 또는 삭제
- 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
- HTML 요소의 레이아웃에 변경을 발생시키는 스타일 변경
레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이 드는 작업이다. (성능에 악영향을 줌) 그렇기에 가급적 리렌더링이 빈번하게 발생하지 않도록 주의해야 한다.
리플로우와 리페인트
앞서 설명했던 브라우저 렌더링 과정이 반복해서 실행되는 경우에 대해서 더 깊게 알아보자.
자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM, CSSOM이 변경된다. 변경된 둘은 다시 렌더 트리로 결합되고 변경된 렌더트리를 기반으로 레이아웃 페인트 과정을 거쳐 브라우저의 화면을 다시 렌더링한다. 이를 리플로우, 리페인트라고 한다.
- 리플로우는 레이아웃 계산을 다시 하는 것을 말한다. 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행된다.
- 리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트하는 것을 말한다.
리플로우와 리페인트가 항상 함께 실행되는 것은 아니다 레이아웃의 변경이 없다면 리페인트만 실행된다.
추가로 CSS 속성 중 transform과 opacity는 GPU가 관여할 수 있는 속성이기에 리플로우, 리페인트가 생략된다.
async/defer
HTML 파싱은 동기적으로 일어난다 그렇기에 CSS, img, script 태그를 만나면 파싱이 중단된다. 이 과정에서 DOM이 생성되기 전에 자바스크립트가 호출되고 실행되어 DOM API를 사용하게 된다면 에러가 발생할 수 있다. 그렇기에 body 최하단에 script를 배치하여 DOM 조작에 문제가 없게 하고 렌더링을 지연시기키지 않아 페이지 로딩 시간이 단축되도록 해야한다.
위치에 상관 없이 순서를 고려하기 위해 등장한 것이 script 태그의 async/defer 어트리뷰트이다.
async
HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 단 자바스크립트 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되고 이때 HTML 파싱이 중단된다.
defer
async와 같이 파싱과 파일의 로드가 비동기적으로 동시에 실행되지만 자바스크립트의 실행은 HTML 파싱이 완료된 직후 진행된다. 따라서 DOM에 접근해야 하는 자바스크립트가 있다면 유용하다.
마치며
브라우저 렌더링에 대해 찾아보며 어느 부분에 초점을 맞추는가에 따라 깊이와 방향이 완전히 달라질 수 있다는 것을 알게 되었다.
브라우저 렌더링 최적화를 위해서 자바스크립트, 폰트, 이미지의 크기를 줄이는 것뿐만이 아닌 리플로우와 리페인트를 관리하여 최적화하거나 CSS 속성을 활용한 렌더링 최적화 기법도 있고 개발자 도구의 퍼포먼스 탭을 활용하여 과정을 확인하는 방법 등 다양한 내용들을 확인할 수 있었다. 이번에 정리한 내용을 토대로 새로운 내용들을 발견하면 추가적인 학습이 필요할 것 같다.
참고 자료
- [네이버 D2]https://d2.naver.com/helloworld/59361
- [모던 자바스크립트 Deep Dive]https://product.kyobobook.co.kr/detail/S000001766445
'프론트엔드' 카테고리의 다른 글
웹 성능 측정 지표(Web Vitals) (0) | 2023.09.05 |
---|---|
가비지 컬렉션(garbage collection) (0) | 2023.08.11 |
REST API (0) | 2023.05.25 |