[Javascript] script async & defer

2022. 4. 2. 14:22Frontend

 

Overview

 

실제로 nextjs를 사용해서 빌드한 후 문서를 요청하면 head에 다음과 같이 여러개의 script tag가 들어가 있습니다.

 

React, Nextjs와 같은 Javascript 라이브러리를 사용해서 개발을 하면, 대부분의 경우 build 옵션에 대해서 큰 신경을 쓸 필요가 없습니다. React의 경우 react-scripts를 기반으로 하는 Create React App(CRA)가 기본적인 웹팩 설정을 해주고, Nextjs의 경우 내부적으로 next build 커맨드를 통해서 모듈 번들링을 지원해줍니다. 하지만 각각의 라이브러리를 사용해서 개발을 하더라도 실제로 내부적으로 bundling이 어떻게 되는지를 이해하는 것은 중요합니다. 이번 포스팅에서는 브라우저가 html문서를 파싱하는 도중 script 태그를 만났을 때 어떻게 동작하는지, 어떤 옵션을 사용할 수 있는지에 대해서 살펴보려고 합니다.

 

 

 

<script> Tag

 

 

async & defer

일반적으로 script 태그를 아무런 속성 없이 가져오게 되면 브라우저는 HTML을 파싱하는 도중 파싱을 멈추고 해당 스크립트를 가져오고 실행할 때까지 기다렸다가 다시 HTML을 파싱합니다. 이는 Javascript 코드를 통해 DOM Tree에 접근하여 이를 수정할 수 있기 때문입니다. (HTML을 파싱하는 도중 스크립트가 DOM을 변경하여 의도치 않은 에러가 발생하지 않도록 하는 것입니다.)

 

async나 defer속성을 사용하면 스크립트를 "Fetch"하는 동안 HTML 파싱이 중단되지 않으며, 두 속성을 동시에 사용하는 경우 async가 적용되고, async를 지원하지 않는 브라우저의 경우 defer로 fallback 됩니다.

 

async

일반 스크립트에 async 속성이 존재하면, HTML 파싱 중에도 스크립트를 가져오며, 사용 가능해지는 즉시 평가를 수행합니다. 다만 평가가 진행될 때, 즉 스크립트가 실행될 때는 HTML 파싱이 중단되었다가 스크립트가 실행을 완료하면 다시 파싱을 수행합니다.

 

 

defer

defer 속성은 브라우저가 스크립트 태그를 만나더라도 파싱을 멈추지 않고 해당 스크립트를 "Fetch"하도록 합니다. async와 다른 점은 Fetch이후에 파싱을 멈추고 바로 스크립트를 실행하는 async과 다르게 Fetch만하고 실제 스크립트 실행 시점은 모든 파싱이 끝난 이후에 실행된다는 것입니다. 따라서 defer를 사용하면 HTML파싱이 중간에 중단되지 않으며, defer를 사용해서 여러 개의 스크립트를 가져와 실행할 경우, HTML 파싱이 끝나고 onDOMContentLoaded 이벤트가 발생하기 전에 스크립트가 문서상의 순서대로 실행됩니다. 

 

 

 

onDOMContentLoaded 이벤트는 브라우저가 HTML을 전부 읽고 파싱하여 DOM 트리를 완성하는 즉시 발생하는 이벤트이며, 이미지 파일이나 CSS 등의 기타 자원들이 도착하지 않았더라도 DOM Tree만 완성되었다면 트리거 됩니다. 이 이벤트는 Document 객체에서 발생하면 DOM 노드에 이벤트 핸들러를 부착하는 등의 작업이 가능해집니다. defer를 사용하면 실제로 스크립트가 DOM Node를 변경하게 되더라도 이 변경사항이 모두 반영된 뒤에 onDOMContentLoaded 이벤트가 수행되는 것을 보장할 수 있습니다.

 

 

 

module

module 속성은 위의 async defer 속성과 약간 다른 특성을 지니고 있습니다. 스크립트 자체를 Javasript 모듈로 간주합니다. module 속성은 몇 가지 특징을 지닙니다.

 

1. src 속성값이 동일한 외부 스크립트는 한 번만 실행됩니다.

<!-- my.js는 한번만 로드 및 실행됩니다. -->
<script type="module" src="my.js"></script>
<script type="module" src="my.js"></script>

 

2. Cross Origin 스크립트를 불러오려면 모듈이 저장되어 있는 원격 서버가 Access-Control-Allow-Origin 헤더를 제공해야만 외부 모듈을 불러올 수 있습니다. (* 를 사용하거나 특정 도메인을 명시하는 식으로 처리 가능)

<!-- another-site.com이 Access-Control-Allow-Origin을 지원해야만 외부 모듈을 불러올 수 있습니다.-->
<!-- 그렇지 않으면 스크립트는 실행되지 않습니다.-->
<script type="module" src="http://another-site.com/their.js"></script>

 

3. 일반적으로 module은 defer처럼 동작합니다. 즉 HTML 파싱이 완료된 이후에 순차적으로 실행되는 것입니다. 하지만 async 속성을 추가로 명시하면 HTML 파싱 도중에도 파싱을 중단하고 스크립트를 실행할 수 있습니다

 

 

nomodule

nomodule 속성은 구형 브라우저 지원을 위한 속성입니다. 구형 브라우저는 type="module"을 해석하는 방법을 갖고 있지 않기 때문에 모듈 타입의 스크립트를 만나면 이를 무시하고 넘어갑니다. 따라서 다음과 같이 사용하여 type="module"을 해석하지 못하는 브라우저에 대한 대응을 처리합니다.

 

<script type="module">
  alert("모던 브라우저를 사용하고 계시군요.");
</script>

<script nomodule>
  alert("type=module을 해석할 수 있는 브라우저는 nomodule 타입의 스크립트는 넘어갑니다. 따라서 이 alert 문은 실행되지 않습니다.")
  alert("오래된 브라우저를 사용하고 있다면 type=module이 붙은 스크립트는 무시됩니다. 대신 이 alert 문이 실행됩니다.");
</script>

 

 

실제로 Nextjs에서는 다음과 같이 nomodule 태그를 사용하여 module 타입을 지원하지 않는 브라우저에 대한 polyfill 번들을 추가로 불러오도록 처리하고 있습니다.

 

 

 

 

Reference

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

 

<script>: The Script element - HTML: HyperText Markup Language | MDN

The <script> HTML element is used to embed executable code or data; this is typically used to embed or refer to JavaScript code. The <script> element can also be used with other languages, such as WebGL's GLSL shader programming language and JSON.

developer.mozilla.org

https://ko.javascript.info/modules-intro

 

모듈 소개

 

ko.javascript.info

 

반응형

'Frontend' 카테고리의 다른 글

[Web.dev] Fast (1) - Introduction  (0) 2022.04.15
[Web] preload, prefetch, preconnect  (0) 2022.04.03
[Nextjs] How getInitialProps Works  (0) 2022.03.28
Strict Mode  (0) 2022.03.27
[Web.dev] Network  (0) 2022.03.19