Modern JS Deep Dive 38

MM·2022년 11월 8일

JSDeepDive

목록 보기
5/6
post-thumbnail

📃 38장. 브라우저 렌더링 과정

critical rendering path 이라고도 하며, 프론트엔드 면접때 꼭 나오는 단골질문이랍니다..🙃

🔹 브라우저 렌더링 과정

  1. 브라우저가 렌더링에 필요한 리소스를 서버에 요청!
  2. 브라우저 렌더링 엔진이 받은 리소스(HTML, CSS)를 파싱하여 DOM과 CSSOM을 생성!
  3. DOM과 CSSOM을 합쳐서 렌더 트리를 형성한다!
    👉 파싱 도중 필요한 외부 리소스가 생겼다?!
    👉 파싱 중단!! 다시 리소스파일을 서버에 요청!
  4. 브라우저 JS엔진이 받은 리소스(JS)를 파싱하여 AST(추상 구문 트리)를 생성!
  5. AST를 바이트코드로 변환해 실행!
    👉 이때 JS코드에 의해 DOM이나 CSSOM이 변경되어 렌더 트리가 수정될 수 있음!
  6. 렌더트리를 기반으로 레이아웃을 만들고 페인팅 진행하면 완성!!

파싱

프로그래밍 언어의 문법에 맞게 작성된 텍스트문서를 어휘분석하여 문자열 토큰으로 분해한 뒤 이 토큰에 문법적 의미와 구조를 반영하여 파스 트리를 생성하는 일련의 과정!
파싱이 종료된 후에는 파스트리를 기반으로 중간 언어(바이트코드)를 생성하고 실행한다~

렌더링

HTML, CSS, JS로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것


🔹 HTTP1.1과 HTTP2.0

🔸 HTTP 1.1

커넥션당 하나의 요청과 응답만 처리한다!
리소스 동시 전송이 불가능하여 요청하는 리소스 개수에 비례하게 응답시간이 증가한다.

🔸 HTTP 2.0

커넥션당 다중 요청과 응답이 가능하다!
자세한 내용은 구글의 HTTP/2소개를 읽어 봅시다...
브라우저 렌더링 다 정리하고 한번 봅시다.


🔹 HTML 파싱과 DOM 생성

순수텍스트인 HTML을 시각적인 픽셀로 렌더링하려면, HTML문서를 브라우저가 이해할 수 있는 자료구조(객체)로 변환하여 메모리에 저장해야 한다!

🔸 바이트코드에서 DOM이 만들어지는 과정

🤔 토큰과 노드는 무슨 차이에요?

토큰은 단순히 문법적 의미를 갖는 코드최소단위이고,
노드가 되어서야 비로소 객체로 생성된다!


🔹 CSS 파싱과 CSSOM 생성

HTML을 한 줄씩 파싱하다 CSS 로드 태그(link, style)을 만나면, DOM 생성을 일시 중단하고 해당 CSS파일을 서버에 요청하여 파싱해다 CSSOM을 만든 뒤 다시 DOM 생성을 재개한다!


🔹 렌더 트리 생성

DOM과 CSSOM을 결합-!!!

🔸 리렌더링 사례

  • JS에 의한 노드 추가 및 삭제
  • 브라우저창 리사이징에 의한 뷰포트 크기 변경
  • HTML요소 레이아웃 변경
    👉 리렌더링은 레이아웃 계산과 페인팅을 다시 진행하여 비용이 많이 들기 때문에, 성능에 악영향을 준다!

🔹 JS 파싱과 실행

🔸 토크나이징

단순 문자열인 js 소스코드를 어휘분석하여 토큰으로 분해하는 과정.
렉싱이라고 부르기도 하는데 토크나이징과 미묘한 차이가 있다..

😧 그 미묘한 차이를 설명을 해달라고요.. 안 해줘서 찾아봄.

  • 토크나이징: 일반적으로 공백(탭, 공백, 새 줄)을 찾아 텍스트 스트림을 토큰으로 나눈다!
  • 렉싱: 기본적으로 토크나이저! 여기에 추가 컨텍스트가 첨부된다.
    👉 그러니까 대충 토크나이징이 더 큰 의미라고.

🔸 파싱

토큰들의 집합을 구문분석하여 AST(추상구문트리)를 생성한다.

😏 TypeScript나 Babel, Prettier도 AST를 사용한 거랍니다.

따라서 위 라이브러리들도 일종의 파서라고 할 수 있다네요!

🔸 바이트코드의 생성과 실행

AST는 바이트코드로 변환되어 인터프리터에 의해 실행된다!

🤭 참고로 V8엔진은요

터보팬이라고 불리는 컴파일러가 자주 쓰는 코드를 최적화된 머신 코드로 만들어 성능을 최적화하는 기능이 있다고 하네요!
코드 사용 빈도가 적어지면 알아서 이 최적화를 해제시킨다고 합니다.

🚚 참고로 터보팬은요

메인스레드를 감시하다가 일정 기준 이상 동일한 함수가 호출되면 히든클래스와 인라인 캐싱을 이용해 최적화를 진행한다고 하네요.


🔹 리플로우와 리페인트

🔸 리렌더링과 리플로우와 리페인트

  • 리렌더링: 렌더링을 다시 진행한다! 리플로우와 리페인트를 동반한다
  • 리플로우: 레이아웃을 다시 잡는다! 리페인트를 동반한다
  • 리페인트: 페인트칠을 다시 한다!

🔹 JS 파싱

HTML을 한 줄씩 파싱하다 JS태그(script) 을 만나면, DOM 생성을 일시 중단하고 해당 JS을 서버에 요청하여 JS를 파싱한다!

🔸 HTML는 직렬적이고 동기적인 파싱을 한다

HTML은 위에서 아래로 한 줄씩 읽어내려가며 순차적으로 파싱을 진행하기 때문에,
HTML 레이아웃이 완성되지 않은 상태에서 JS로 DOM에 접근하면 문제가 발생할 수 있다!
👉 따라서 scrip태그를 HTML 후반부에 놓는 것을 권장한다!

🔸 script 태그의 async/defer 어트리뷰트

위 문제를 해결하기 위해 HTML5부터 추가된 어트리뷰트로,
HTML파싱과 외부 JS파일 로드가 비동기적으로 동시진행된다.
👉 단, src 어트리뷰트를 통해 외부 js파일을 로드하는 경우에만 사용이 가능하다

<script async src="extern.js"/>  //순서 보장x
<script defer src="extern.js"/> 


📃 최신 브라우저에서 렌더링 과정이 바뀌었다?

🔹 렌더러 프로세스

일련의 렌더링 과정을 담당하는 브라우저의 프로세스!
각각의 과정은 해당 프로세스 내부 스레드들이 하나씩 맡아 담당한다!

🔹 이런 순서로 바뀌었습니다

🔸 Parsing

가진 것: HTML 텍스트 코드

HTML을 파싱하여 DOM 트리를 만든다!

🔸 Style

가진 것: Dom 트리

CSS를 계산해 Computed Style을 도출한다!
모든 수치(% 등..)를 px단위로 변환한다!

🔸 Layout

가진 것: Dom 트리(색칠됨)

DOM 트리의 각 요소들이 배치될 좌표를 계산하여 Layout 트리를 만든다!

🌳 속성 트리(property tree)

레이아웃 트리를 순회하면서 clip, transform, opacity등의 속성 정보만 뽑아낸 트리!
기존에는 이런 정보를 분리하지 않고 노드가 가지고 있어서, 특정 노드 속성이 변경되면 해당 노드의 하위 노드에도 이 값을 다시 반영하면서 노드를 순회해야 했지만....
최신 Chrome에서는 이런 속성만 별도로 관리하고 각 노드에서는 속성 트리의 노드를 참조하는 방식으로 변경되고 있다~

🔸 Layer

가진 것: Dom 트리(색칠됨), Layout 트리

css에서 z-index나 position 등 레이어 관련 정보를 추출하여 Layer 트리를 만든다!
레이어가 많을수록 합성 비용과 메모리 소비가 많아진다!

😏 개발자 모드에서 레이어 확인하기

개발자모드의 more tools에서 Layer를 입체적으로 확인할 수 있다!

💥 layer explosion(레이어 과도 현상)

크롬에는 이를 막기 위해 특정 경우 레이어를 생성하지 않거나 합치는 기능이 있다!

🔸 Paint

가진 것: Dom 트리(색칠됨), Layout 트리, Layer 트리

Layout 트리와 Layer 트리로 페인트 레코드를 만든다!

🔸 Tiling

이 과정은 메인스레드가 아니라 컴포지터 스레드가 담당합니다!

페인트 레코드로 타일(컴포넌트)로 만든다!
이렇게 조각난 타일들을 필요한 부분만 우선적으로 렌더링할 수 있다!

🔸 Raster

이 과정은 래스터 스레드가 담당합니다!

화면을 픽셀로 변환하는 작업. 시간을 매우 소모한다!
위에서 만든 타일로 실제 화면에 보여질 비트맵을 만든다~~

이 타일은 해상도 별로 여러 세트를 만들어 줌인, 줌아웃에 따라 해상도를 바꿀 수 있게 해준다고.

😌 각각의 타일은 말이죠

각각의 래스터 스레드에서 래스터화가 진행됩니다
다 만들면 GPU 메모리에 비트맵을 저장하고 컴포지터 스레드로 넘겨줘요

🔸 Draw Quad

이 과정은 컴포지터 스레드가 담당합니다

🔖 Draw Quad

타일이 래스터화된 후의 정보 모음(타일이 그려질 위치 등..)

컴포지터 스레드는 이 드로 쿼드들을 모아 합성프레임(compositor frame)을 만든다~

컴포지터 스레드는..

  • 래스터 스레드간 우선순위를 지정할 수 있다!
    👉 따라서 뷰포트 근처의 래스터를 먼저 진행할 수 있다!

  • 메인 스레드와 별개로 작동하므로, 부드러운 애니메이션이 가능하다!👍
    👉 단, 레이아웃이나 페인트를 다시 계산해야 할 경우에는 메인 스레드가 관여해야 한다..

  • 스크롤 이벤트가 발생할 때마다 GPU로 보낼 다른 합성 프레임을 만든다.
    👉 미리 만들어둔 비트맵을 재조합하기만 하면 되기 때문에 쉽고 빠르다고~

🔰 펜딩 트리

아직 화면에 그려지지 않은 최신 프레임 상태를 가지고 있는 트리.
컴포지터 스레드 안에 존재한다.

🕐 액티브 트리

현재 화면에 그려지고 있는 이전 프레임을 그렸던 트리.
최신 정보로 화면을 갱신할 때 이 액티브 트리와 펜딩 트리가 교체된다!
이것도 컴포지터 스레드 안에 존재.

🔸 렌더링-!!!!

만들어진 합성 프레임이 IPC를 통해 브라우저 프로세스로 전송된다!
이 합성 프레임은 GPu로 전송되어 화면에 디스플레이된다!

앞으로는 합성 프레임이 GPU 프로세스로 바로 보내지는 형태로 변경될 예정이라고...!


🔹 그리고 이런 것들이 추가됐습니다

🔸 프리로드 스캐너

HTML문서에서 < img>, < link> 같은 외부 리소스 태그를 먼저 찾아 요청한다!
메인스레드와 동시에 병렬적으로 실행되기 때문에 메인스레드가 html을 전부 파싱하는 속도를 높여준다.

profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글