Skip to main content

Browser Engine & Rendering

2021-05-04#

List#

  • Browser Rendering
    • Browser Architecture
    • Rendering Engine
    • Render Flow

Browser Architecture#

browser_architecture

  • User Interface : 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분
  • Browser Engine : User Interface와 Rendering Engine 사이의 동작을 제어
  • Rendering Engine : 요청한 콘텐츠를 표시, HTML을 요청하면 HTML과 CSS를 파싱 하여 화면에 표시
  • Networking : HTTP 요청과 같은 네트워크 호출에 사용
  • Javascript Interpreter(or Engine) : 자바스크립트 코드를 해석하고 실행함. 크롬에서는 V8 엔진을 사용
  • Display Backend : 기본적인 위젯(콤보 박스 등..)을 그림
  • Data Persistence : Local Storage, 쿠키 등 클라이언트 사이드에서 데이터를 저장하는 영역
FireFoxChrome
browser_architecturebrowser_architecture

렌더링 엔진#

렌더링 엔진의 역할은 요청받은 내용을 브라우저 화면에 나타내는 일을한다. HTML, CSS, JavaScript 등의 파일을 브라우저가 화면에 표시할 수 있도록 변환하여 픽셀 단위로 나타냄.

렌더링 엔진들#

브라우저마다 사용하는 렌더링 엔진들이 다르다. 렌더링 엔진이 브라우저마다 다르기 때문에, 같은 소스가 브라우저마다 다르게 그려지는 크로스 브라우징 이슈가 발생하게 되는것.(자바스크립트 엔진이 달라 발생하기도 함)

브라우저렌더링 엔진
IETrident
EdgeEdgeHTML, Blink
ChromeWebkit, Blink(버전 28 이후)
SafariWebkit
FireFoxGecko

크롬 브라우저(정확히는 크로미움은)는 사파리 브라우저에서 사용하는 Webkit을 사용하다가 버전 28 이후 Webkit 소스를 Fork 하여 Blink 엔진을 만들어 사용하고 있습니다.

크로미움이란?

크롬은 크로미움 기반으로 만들어진 브라우저라는 이야기를 들어봤을 수 있다. 크로미움은 오픈 소스 웹 브라우저로 https://chromium.woolyss.com/ 에서 다운로드해 브라우저로 사용가능하다.

크로미움은 V8이라는 자바스크립트 엔진과 Blink라는 렌더링 엔진을 사용하는 브라우저이며, 크롬이 크로미움 기반으로 만들어졌다는 것은 오픈 소스인 크로미움 브라우저 코드 위에 살을 덧붙여 개발되었다는 의미를 뜻한다.

크로미움 기반의 크롬이 절반 이상 점유율을 차지하고 있으며 이제는 Edge 브라우저도 크로미움 기반의 브라우저가 되었다. Edge 브라우저가 사용하던 EdgeHTML 렌더링 엔진을 포기하고 크로미움 기반의 브라우저를 만들어 사용중.

동작 과정 요약#

렌더링 엔진은 요청한 문서의 내용을 얻는 것에서 시작한다. 문서는 보통 8KB 단위로 전송됨.

  • 렌더링 엔진은 HTML 문서를 파싱 하여 DOM 트리를 만들고, CSS 문서를 파싱 하여 CSSOM 트리를 만듦
  • DOM과 CSSOM을 이용하여 렌더 트리를 만듦
  • 렌더 트리 생성이 끝나면 Layout(Reflow라고도 합니다)을 시작
    (이 과정은 각 노드가 화면의 정확한 위치에 표시하기 위해 위치와 크기를 계산하는 과정)
  • 마지막으로 계산된 위치과 크기 등의 스타일들이 실제 픽셀로 표현하는 과정을 시작
    (이 과정을 Paint(or Rasterizing)라고도 한다)

rendering_engine_process_summary

동작 과정 상세#

웹킷 렌더링 엔진 동작 과정#

webkit_rendering_engine_process

위의 그림은 Webkit의 렌더링 동작 과정이다.

  1. HTML을 파싱하여 DOM 노드를 생성하고, 이 DOM 노드들을 병합하여 DOM 트리를 생성
  2. CSS를 파싱하여, 스타일 규칙을 생성
  3. DOM 트리와 스타일 규칙을 사용하여, Attachment라는 과정을 통해 Render 트리를 생성
  4. Render 트리를 배치(Layout)
  5. Render 트리를 화면에 그림(Painting)

게코 렌더링 엔진 동작 과정#

gecko_rendering_engine_process

위의 그림은 Gecko의 렌더링 동작 과정이다.

Webkit과 Gecko는 용어가 약간 다르지만 렌더링 과정은 유사하다.

WebkitGecko설명
Render TreeFrame Tree렌더링 되는 노드 트리
Render ObjectFrame렌더링 되는 노드
LayoutReflow렌더링 되는 노드를 배치하는 과정
AttachmentFrame Constructor렌더링 되는 노드 트리를 만드는 과정
Content SinkDOM노드를 만드는 과정

2021-05-12#

List#

  • Browser Rendering
    • Parser
    • Javascript load, CSS load
    • Attachment
    • 요약
    • 번외 Virtual Dom

Parser#

Parser는 서버로부터 전송받은 문서의 문자열을 브라우저가 이해할 수 있는 구조로 변환하는 과정이며, Parser 결과는 문서 구조를 나타내는 노드 트리인데, 파싱 트리(parse tree) 또는 문법 트리(syntax tree)라고 지칭한다.

DOM(Document Object Model)#

dom_parsing_summary

위의 그림은 동작 과정 상세에서 DOM을 파싱 하는 과정이다.

Dom Parsing#

dom_parsing

  • 변환(Conversion): HTML의 원시 바이트(raw bytes)를 읽어와 해당 파일에 지정된 인코딩(UTF-8 등…)에 따라 문자열로 변환하는 과정
  • 토큰화(Tokenizing): 문자열을 W3C HTML5 표준에 따라 고유 토큰(<html>, <body>등, 꺽쇠괄호로 묶인 문자열)으로 변환합니다. 각 토큰은 특별한 의미와 고유한 규칙을 가짐
  • 렉싱(Lexing): 토큰을 해당 속성 및 규칙을 정의한 객체(Nodes)로 변환
  • DOM 생성(Dom construction): HTML은 상위-하위 관계로 정의할 수 있어, 트리 구조로 나타낼 수 있습니다. 렉싱 과정을 거쳐 생성된 노드들을 트리 구조로 변환

dom_tree

4가지 과정을 모두 거치면 위의 그림과 같은 트리 형태의 DOM이 생성되며, 브라우저는 이후 모든 페이지 처리를 이 DOM을 사용한다.

CSSOM(CSS Object Model)#

CSSOM MDN

css_parsing_summary

CSS Object Model은 JavaScript에서 CSS를 조작할 수 있는 API 집합. HTML 대신 CSS가 대상인 DOM이라고 생각할 수 있으며, 사용자가 CSS 스타일을 동적으로 읽고 수정할 수 있는 방법이다.

cssom_construction

위의 그림과 같이 DOM을 생성하는 과정 그대로 CSSOM을 생성한다.

브라우저는 DOM을 생성하는 동안 외부 CSS를 참조하는 <link> 태그를 만나게 되면 브라우저에 리소스를 요청한다. CSS의 원시 바이트(raw bytes)가 문자열로 변환된 후 차례로 토큰과 노드로 변환되고 마지막으로 CSSOM(CSS Object Model)이라는 트리 구조를 생성한다.

CSSOM Tree#

cssom_tree

CSSOM이 트리 구조를 가지는 이유는 하향식으로 규칙을 적용하기 때문이다. 최종 스타일을 계산할 때 브라우저는 해당 노드에 적용 가능한 가장 일반적인 규칙으로 시작해 더 구체적인 규칙을 적용하는 방식을 제공한다.

위의 CSSOM 트리 그림을 보면 하양식 규칙 적용에 대해 모습을 파악할 수 있다.

JS Load & CSS Load#

HTML과 CSS, 자바스크립트를 파싱 하여 렌더 트리를 형성하고 화면에 그리는 과정을 최적화하면 브라우저의 렌더링 속도를 높여 사용성을 개선할 수 있다.

JavaScript Load#

자바스크립트는 Parser 차단 리소스(parser blocking resource) 특징을 가진다. 브라우저는 문서를 파싱 하다가 자바스크립트를 만나면 진행하던 파싱을 중지하고 자바스크립트 엔진에게 권한을 넘겨 자바스크립트를 파싱하고 실행한다.

자바스크립트가 실행되는 동안 문서의 파싱은 중단된다. 자바스크립트는 파싱을 중단시키기 때문에, 보통 자바스크립트를 <head> 태그가 아닌 <body> 태그가 닫히기 바로 전에 사용되도록 하는 것이 좋다.

보통 bootstrap 같은 템플릿에 사용되는 JavaScript 들의 선언된 위치를 보면 <footer>쪽에 거의 모두 선언이 되어있는데 이런 이유가 크게 작용한다.

<script> 태그에 defer 속성을 주면, 문서 파싱은 중단되지 않고 문서 파싱이 완료된 이후에 자바스크립트가 실행됩니다. HTML5에서 스크립트를 비동기(async)로 처리하는 속성이 추가되었으며, 자바스크립트가 별도의 맥락에 의해 파싱 되고 실행된다.

script_load script_async_defer

예)

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script>
document.getElementById('target').innerHTML = 'Hello JavaScript!';
</script>
</head>
<body>
<div id="target">This is a Heading</div>
</body>
</html>
// 오류 발생 // test.html:6 Uncaught TypeError: Cannot set property 'innerHTML'
of null // at test.html:6
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<div id="target">This is a Heading</div>
<script>
document.getElementById('target').innerHTML = 'Hello JavaScript!';
</script>
</body>
</html>

CSS Load#

CSS는 렌더링 차단 리소스(render blocking resource)이다.

CSS는 렌더링을 할 때 반드시 필요한 리소스이기 때문에 브라우저는 빠르게 CSS를 다운로드하는 것이 좋다.

<head> 태그 안에서 정의하여 빠르게 리소스를 받을 수 있도록 하는게 성능상 좋다.

CSS는 DOM 트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유가 없으나, 자바스크립트에서 스타일 정보를 요청하는 경우, CSS가 파싱 되지 않은 상태라면 스크립트 에러가 발생할 수 있다.

이런 문제를 해결하기 위해 Firefox 경우 로드 중이거나 파싱 중인 CSS가 있는 경우 모든 자바스크립트 실행을 중지한다. 반면 Webkit의 경우 로드되지 않은 CSS 가운데 문제가 될 만한 속성이 있을 때에만 자바스크립트를 중단한다.

Attachment#

CSSOM 트리와 DOM 트리를 결합하여, 표시해야 할 순서로 내용을 그려낼 수 있도록 하기 위해 렌더 트리를 형성하는 과정

(웹킷에서는 Attachment라고 지칭함)

렌더 트리는 화면에 표시되는 각 노드의 위치를 계산하는 레이아웃에 사용되고 픽셀을 화면에 그리는 페인트 과정에도 사용된다.

렌더 트리 구축#

attachment_summary

attachment 과정

  • 브라우저가 DOM 및 CSSOM을 렌더 트리에 결합
  • 렌더 트리는 페이지에 표시되는 모든 DOM 콘텐츠와 각 노드에 대한 모든 CSSOM 스타일 정보를 결합

render_tree_construction

render tree 생성 과정

  • DOM 트리의 루트에서 시작하여 화면에 표시되는 노드 각각을 탐색
    • 화면에 표시되지 않는 일부 노드들 script, meta 태그 등..은 렌더 트리에 반영되지 않음
    • CSS에 의해 화면에서 숨겨지는 노드들은 렌더 트리에 반영되지 않음
  • 화면에 표시되는 각 노드에 대해 적절하게 일치하는 CSSOM 규칙을 찾아 적용
  • 화면에 표시되는 노드를 콘텐츠 및 계산된 스타일과 함께 렌더트리 생성

DOM 트리와 렌더 트리의 관계#

렌더 트리 생성에서 화면에 표시되지 않는 노드들은 렌더 트리에 포함되지 않는다.

예) <head> 태그와 같은 비시각적 DOM 노드는 렌더 트리에 추가되지 않습니다.

팁)

  • CSS로 인해 display 속성에 none 값이 할당된 노드들을 렌더 트리에 추가되지 않음
  • visibility:hidden은 렌더 트리에 포함됨
    (visibility 속성에 hidden 값이 할당된 노드는 화면에 공간을 차지하기 때문에 렌더 트리에 포함)

Layout#

layout_summary

렌더 트리가 생성되고, 기기의 뷰포트 내에서 렌더 트리의 노드가 정확한 위치와 크기를 계산하는 과정을 Layout(혹은 Reflow)라고 한다.

이 과정에서 모든 상대적인 측정값은 화면에서 절대적인 픽셀로 변환된다.

예) CSS에 상대적인 값인 %로 할당된 값들은 절대적인 값은 px 단위로 변환

Painting#

painting_summary

렌더 트리의 각 노드를 화면의 실제 픽셀로 나타내는 과정을 Painting(혹은 rasterizing)라고 한다.

Painting 과정 후 브라우저 화면에 UI가 나타나게 됨

요약하자면#

Q) 브라우저에서 화면이 어떻게 렌더링 되나요?

  1. HTML 마크업을 처리하고 DOM 트리를 빌드 (DOM 파싱)
  2. CSS 마크업을 처리하고 CSSOM 트리를 빌드 (CSS 파싱)
  3. DOM 및 CSSOM을 결합하여 렌더 트리를 형성 (Attachment)
  4. 렌더 트리에서 레이아웃을 실행하여 각 노드의 기하학적 형태를 계산 (Layout)
  5. 개별 노드를 화면에 그림 (Painting)

번외 Virtual Dom#

가상돔(Virtual DOM)은 실제 DOM 문서를 추상화한 개념

변화가 많은 View를 실제 DOM에서 직접 처리하는 방식이 아닌 Virtual Dom 영역과 메모리에서 미리 처리하고 저장한 후 실제 DOM과 동기화 하는 프로그래밍 개념

Virtual DOM VS Real DOM#

DOM이 변경 될 때마다 브라우저는 CSS를 다시 계산하고 레이아웃을 수행하고 웹 페이지를 다시 그려야한다.

이 동작은 실제 돔에서 시간이 소요되며 메모리가 소요된다.

이 시간을 최소화하기 위해서 사용되는 전략들이 있는데 Ember는 키 / 값 관찰 기술을 사용하고 Angular는 Dirty Checking을 사용한다.

이런 기술들을 사용하면 Angular의 경우 변경된 dom 노드 또는 더티로 표시된 노드 만 업데이트 할 수 있다.

요즘 브라우저는 화면을 다시 그리는 데 걸리는 시간을 단축하려고 노력하는 기술들이 많이 파생됬으며 결국, 브라우저에서 가장 큰 작업중 하나인 DOM의 변경을 최소화하고 일괄 처리하는 것이다.

또한, React의 Virtual DOM의 전략으로 다른 수준의 추상화로 그려진 DOM으로 변경을 줄이고 최적화하는 전략을 채택하고있다.

React의 가상 DOM이 빠른 이유?#

React는 실제로 새로운 것을 하지 않는다.

  • React 라이브러리가 실제 하는 작업은 DOM의 복제본을 메모리에 저장
  • DOM을 수정하면 먼저 이러한 변경 사항을 메모리 내 DOM에 적용
  • 메모리에 적용된 DOM과 실제 DOM을 diffing 알고리즘으로 실제로 변경된 사항을 파악
  • 마지막으로 변경 사항을 일괄 처리하고 호출하여 한 번에 실시간으로 적용

결론은 React도 Reflow하는 과정을 최소화하고 다시 Paint하는 과정은 똑같다.

2021-05-18#

List#

  • Critical Rendering Path
    • CSS
    • Javascript

Critical Rendering Path(CRP)#

브라우저가 페이지의 초기 출력을 위해 실행해야 하는 순서

  • DOM 트리 구축
  • CSSOM 트리 구축
  • JavaScript 실행
  • 렌더 트리 구축
  • 레이아웃 생성
  • 페인팅

Critical Rendering Path 최적화#

CSS#

기본적으로 CSS는 렌더링 차단 리소스(render blocking resource)이다.

즉 CSSOM이 생성될 때까지 브라우저는 렌더링하지 않으며, 렌더 트리를 이용하여 레이아웃과 페인팅 동작을 하므로, 렌더 트리를 만들 때 사용되는 HTML과 CSS는 둘 다 렌더링 차단 리소스입니다.

CSS는 렌더링 차단 리소스이기 때문에, 최초 렌더링에 걸리는 시간을 최적화하려면 CSS를 간단하게 만들고, 클라이언트에 최대한 빠르게 다운로드하는것이 좋다.

  • 미디어 유형과 미디어 쿼리를 사용하면 일부 CSS 리소스를 렌더링 비차단 리소스로 표시가능
  • 브라우저는 차단 리소스이든 비차단 리소스이든 상관없이 모든 CSS 리소스를 다운로드

미디어 유형, 미디어 쿼리 사용#

페이지가 인쇄될 때나 대형 모니터에 출력하는 경우 등 몇 가지 특수한 경우에만 사용되는 CSS가 있다면, 해당 CSS가 렌더링을 차단하지 않는 것이 좋다.

이 경우에 미디어 유형과 미디어 쿼리를 사용면 CSS 리소스를 렌더링 비차단 리소스로 표시하는 방법이 존재한다.

<link href="style.css" rel="stylesheet">
<link href="style.css" rel="stylesheet" media="all">
<link href="print.css" rel="stylesheet" media="print">
<link href="portrait.css" rel="stylesheet" media="orientation:landscape">
<link href="other.css" rel="stylesheet" media="min-width: 40em">
  • 첫 번째 스타일시트 선언은 미디어 유형이나 미디어 쿼리를 제공하지 않았기 때문에 모든 경우에 적용됩니다. 즉, 항상 렌더링을 차단
  • 두 번째 스타일시트 선언은 미디어 유형을 all로 설정되었습니다. 첫 번째와 두 번째는 사실상 똑같아서 두 번째 스타일시트 또한 항상 렌더링을 차단
  • 세 번째 스타일시트 선언은 미디어 유형을 사용합니다. 콘텐츠가 인쇄될 때만 적용되어 처음 로드 될 때 페이지 렌더링이 차단되지 않음
  • 네 번째 스타일시트 선언은 미디어 쿼리를 orientation:landscape로 설정되었습니다. 기기의 방향이 가로일 때 렌더링을 차단
  • 다섯 번째 시타일시트 선언은 미디어 쿼리를 min-width: 40em로 설정되었습니다. 기기의 너비의 조건이 일치하면 렌더링을 차단

렌더링 차단은 페이지의 초기 렌더링을 보류만 할 뿐 항상 다운로드합니다. 즉, 어느 경우든지 비차단 리소스의 우선순위가 낮더라도 브라우저는 여전히 CSS 리소스를 다운로드합니다.

JavaScript#

기본적으로, 자바스크립트는 파서 차단 리소스(parser blocking resource)이다.

자바스크립트를 사용하면 콘텐츠, 스타일, 사용자와의 상호작용 등 거의 모든 것을 수정할 수 있다.

자바스크립트 실행은 DOM 생성을 차단하고 페이지 렌더링을 지연시킨다.

최적화를 위해 자바스크립트를 비동기로 설정하고, Critical Rendering Path에서 불필요한 자바스크립트를 제거하는것이 좋다.

Critical Rendering Path에서 불필요한 자바스크립트를 제거는 코드 최적화에 가깝다.

브라우저의 도움을 받을 수 있는 비동기 설정 방법에 초점을 두고 이야기한다.

비동기 설정을 해야 하는 이유중에 자바스크립트의 종속성에 알 필요성이 있다.

JavaScript와 HTML의 종속성#

자바스크립트는 DOM 노드와 CSS 스타일을 수정할 수 있는 강력한 기능과 유연성을 보여준다.

/* style.css */
body {
font-size: 16px;
}
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
  • HTML 파서는 script 태그를 만나면 DOM 생성 프로세스를 중지하고 자바스크립트 엔진에서 권한 제어
  • 자바스크립트 엔진의 실행이 완료된 후 브라우저가 중지했던 시점부터 DOM 생성을 다시 시작

문제점#

  • script 태그의 뒷부분에서 정의된 어떠한 태그들도 아직 생성되지 않았기 때문에 노드를 찾을 수 없음
  • 인라인 스크립트를 실행하면 DOM 생성이 차단되고, 이로 인해 초기 렌더링도 지연

이러한 이유로 인해 특별한 이유가 없다면 자바스크립트는 화면에 그려지는 태그들이 모두 파싱 된 후인, body 태그를 닫기 직전에 script 태그를 선언하는 것이 좋다.

JavaScript와 CSS의 종속성#

자바스크립트는 DOM뿐만 아니라 CSSOM 속성도 읽고 수정할 수 있다.

위의 코드에서 span 노드의 display 속성을 none에서 inline으로 변경한 것이 CSSOM 속성을 수정한 것입니다.

문제점#

  • CSS를 파싱 하는 동안 자바스크립트에서 스타일 정보를 요청하는 경우, CSS가 파싱이 끝나지 않은 상태라면 자바스크립트 오류가 발생할 수 있음
  • CSS 파싱으로 생성되는 CSSOM과 JavaScript에서 스타일 수정 시 발생하는 CSSOM 수정 사이에 경쟁 조건(race condition)이 발생

브라우저는 이 문제를 해결하기 위해 CSSOM을 생성하는 작업이 완료할 때까지 자바스크립트 실행 및 DOM 생성을 지연시키며, DOM, CSSOM, 자바스크립트 실행 간에 종속성 때문에 브라우저가 화면에 페이지를 처리하고 렌더링 할 때 상당한 지연이 발생할 수 있다.

비동기 JavaScript#

기본적으로, 자바스크립트 실행은 파싱을 중지시킵니다. HTML을 파싱 하면서 script 태그를 만나면 DOM 생성을 중지시키고 자바스크립트 엔진에게 제어 권한을 넘겨 자바스크립트를 실행한 후, DOM 생성을 계속하는데...

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<script src="app.js"></script>
</body>
</html>
// app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

위에서 살펴본 인라인 스크립트뿐만 아니라 위의 코드와 같이 script 태그를 통해 포함된 자바스크립트 역시 파싱을 중지시키며, 차이점이 있다면, script 태그를 사용하여 자바스크립트를 실행할 경우, 서버에서 자바스크립트를 가져올 때까지 기다려야 합니다. 이로 인해 수십~수천 밀리초의 지연이 추가로 발생할 수 있습니다.

문제점#

  • 기본적으로 자바스크립트는 Parser를 차단
  • Script 페이지에서 무엇을 수행할지 모르기 때문에 브라우저는 최악의 시나리오를 가정하고 Parser를 차단

브라우저에 자바스크립트를 바로 실행할 필요가 없음을 알려준다면, 브라우저는 계속해서 DOM을 생성할 수 있고 DOM 생성이 끝난 후에 자바스크립트를 실행할 수 있게 할 수 있는데 이때 사용할 수 있는 것이 비동기 자바스크립트다.

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<script src="app.js" async></script>
</body>
</html>

위의 코드와 같이 단순히 script 태그에 async 속성을 추가해 준다면 자바스크립트가 사용 가능해질 때까지 브라우저에게 DOM 생성을 중지하지 않고 Parser를 지속할 수 있다.

script_async_defer