렌더링
HTML,CSS,자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것
브라우저별 렌더링 엔진
- 파이어폭스 : 게코(Gecko)
- 사파리, 크롬: 웹킷(Webkit)
렌더링 과정
- 브라우저가 HTML,CSS,자바스크립트,이미지, 폰트 파일 등의 리소스를 서버에 요청한다. 기본적으로 브라우저가 html을 파싱할때 script 태그 등을 만나면 파싱을 일시 중지하고 스크립트 파일 등의 리소스 파일을 서버에 요청한다
- 서버로부터 응답을 받으면 브라우저의 렌더링 엔진이 HTML,CSS를 파싱하여 각각 DOM tree와 CSSOM tree를 생성하고 이를 결합해 렌더 트리를 만든다.
- 브라우저의 자바스크립트 엔진은 자바스크립트를 파싱하여 AST(Abstract Syntax Tree)를 만들어 바이트코드로 변환하여 실행한다. 이때 자바스크립트의 DOM API를 사용해서 DOM과 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리에 결합된다.
- 렌더 트리 기반으로 HTML 요소의 레이아웃을 계산하고, 브라우저 화면에 HTML 요소를 페인팅한다.
파싱 Parsing
프로그램을 런타임 환경에서 동작할 수 있도록 내부 포맷으로 분석하고 변환하는 것 (출처: MDN)
- 브라우저 렌더링에서의 파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미
- 구조적으로 잘 짜여진 문서는 파서가 좀더 빠르게 파싱한다. 파서가 토큰화된 input을 문서에 파싱하고 이는 문서 트리를 빌드한다.
- CSS 스타일을 만나면 텍스트를 CSSOM(레이아웃과 페인팅을 위해 사용되는 자료구조)으로 파싱한다.
- 자바스크립트 파싱은 컴파일할때 혹은 파서가 작동할 때(예를 들면 메소드 호출) 일어난다.
Render-blocking
파서의 역할
- 구문 규칙에 따라 문서 구조 분석하여 파싱 트리 생성
- 자료를 유효한 토큰으로 분해하는 어휘 분석기(Tokenizer)로부터 토큰을 받아서 구문 규칙과 일치하는지 확인
- 일치 → 토큰에 해당하는 노드가 파싱 트리에 추가
- 일치 X → 토큰을 내부적으로 저장하고 일치하는 규칙 찾을 때까지 계속 요청한다. 맞는 규칙 없는 경우 예외 처리한다.(구문 오류)
자바스크립트 엔진
역할
JS 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어로 변환하고 실행
종류
브라우저별로 다양한 자바스크립트 엔진이 있으며 이들 모두 ECMAScript 사양을 준수한다.
- 구글 크롬, Node.js : V8
- 파이어폭스 : SpiderMonkey
- 사파리의 JavaScriptCore
자바스크립트 엔진이 자바스크립트를 해석하여 Abstract Syntax Tree(추상적 구문 트리)를 생성하고 이를 기반으로 인터프리터가 실행할 수 있는 중간코드인 바이트코드를 생성, 실행한다.
리플로우
- 레이아웃 계산을 다시 하는 것
- 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한해 실행
리페인트
- 재결합된 렌더 트리를 기반으로 다시 페인트 하는 것
레이아웃에 영향을 주지 않는 변경은 리플로우 없이 리페인트만 실행됨.
script 태그를 body 요소의 가장 마지막에 두는 이유
- DOM이 완성되기 전 DOM 조작을 하면 에러 발생하므로
- 자바스크립트 로딩/파싱/실행으로 인한 HTML 요소들의 렌더링 지연을 방지하여 페이지 로딩 시간 단축
- 렌더링 엔진과 자바스크립트 엔진은 직렬적으로 파싱 수행한다. 브라우저는 동기적으로, 즉 위에서 아래 방향으로 순차적으로 HTML,CSS,자바스크립트를 파싱하고 실행한다. 이는 script 태그의 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다는 것을 의미. 따라서 script 태그의 위치는 중요한 의미를 갖는다.
async/defer
- HTML5에 추가된 속성
src
속성으로 외부 자바스크립트 파일 로드하는 경우에만 사용가능. 즉, src
속성이 없는 인라인 자바스크립트에는 사용 불가.
- 두 속성 모두 HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 하지만 자바스크립트 실행 시점에 차이가 있음.
async
html과 자바스크립트가 비동기적으로 로드 된다. 자바스크립트 로드가 완료되면 실행된다. 이때, html 파싱이 중단된다. 자바스크립트 로드가 완료되면 바로 실행이 되기 때문에 순서가 보장되지 않는다. 순서 보장이 필요한 script 태그에는 async를 쓰면 안된다.
defer
html과 자바스크립트가 비동기적으로 로드 된다. 하지만 html 파싱되어 DOM이 생성이 완료된 후에 자바스크립트가 실행된다. 따라서 DOM 생성 완료 후 실행되어야 할 자바스크립트에 유용하다.
참고자료
브라우저는 어떻게 동작하는가? https://d2.naver.com/helloworld/59361
Introduction to HTTP/2 https://web.dev/performance-http2/
응답 헤더 Response Header https://developer.mozilla.org/en-US/docs/Glossary/Response_header
Parse란? https://developer.mozilla.org/en-US/docs/Glossary/Parse
render-blocking 리소스 제거하는 방법 https://web.dev/render-blocking-resources/