# 브라우저 모듈과 ESM

<script type="module">

  • 스크립트가 모듈이라는 것을 속성을 통해 명시해야 한다.
  • 항상 엄격모드
  • 모듈레벨 스코프가 존재함.

# 단 한번만 평가됨

동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출 시 단 한번만 실행된다.

// 📁 alert.js
alert("모듈이 평가되었습니다!");
// 동일한 모듈을 여러 모듈에서 가져오기
// 📁 1.js
import `./alert.js`; // 얼럿창에 '모듈이 평가되었습니다!'가 출력됩니다.
// 📁 2.js
import `./alert.js`; // 아무 일도 발생하지 않습니다.
1
2
3
4
5
6
7

모듈이 최초 호출 시 admin 객체가 만들어 지고, 이 모듈을 가져오는 모든 모듈에 admin 객체가 전달된다.

// 📁 admin.js
export let admin = {
  name: "John"
};
// 📁 1.js
import {admin} from './admin.js';
admin.name = "Pete";

// 📁 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete

// 1.js와 2.js 모두 같은 객체를 가져오므로
// 1.js에서 객체에 가한 조작을 2.js에서도 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# import.meta

현재 모듈에 대한 정보를 제공해 주는 객체

  • 브라우저 환경
    • 스크립트 url 정보
    <script type="module">
      alert(import.meta.url); // script URL (인라인 스크립트가 위치해 있는 html 페이지의 URL)
    </script>
    
    1
    2
    3

# this 는 undefined

모듈 최상위 레벨의 this 는 undefined

<script type="module">
  alert(this); // undefined
</script>
1
2
3

일반 스크립트의 this 는 전역 객체

<script>
  alert(this); // window
</script>
1
2
3

# type="module" 의 특징

# 지연실행

  • 모듈 스크립트는 항상 지연 실행된다.
  • 외부 스크립트, 인라인 스크립트와 관계없이 마치 'defer' 속성을 붙인 것처럼 실행된다.
  • defer
    • 외부 모듈 스크립트를 다운로드 할 때, 브라우저의 HTML 처리와 병렬적으로 불러온다.
    • HTML 처리가 완료된 후 스크립트가 실행된다.
    • 스크립트의 순서가 유지 된다.
    • 모듈 스크립트는 완전한 HTML 페이지를 볼 수 있고 문서 내 요소에 접근가능.
  • 모듈이 완전히 로드되기 전 사용자에게 페이지가 노출되면 혼란을 느낄 수 있다.
    • 모듈 스크립트를 불러오는 동안 투명 오버레이나 로딩 indicator 를 보여준다

모듈 스크립트는 지연 실행되기 때문에 페이지가 모두 로드되고 난 다음에 얼럿 함수가 실행되므로 얼럿창에 object 가 정상적으로 출력됩니다. 모듈 스크립트는 아래쪽의 button 요소를 '볼 수' 있죠.

<script type="module">
  alert(typeof button);
</script>
1
2
3

하단의 일반 스크립트와 비교해 봅시다.

일반 스크립트는 페이지가 완전히 구성되기 전이라도 바로 실행됩니다. 버튼 요소가 페이지에 만들어지기 전에 접근하였기 때문에 undefined 가 출력되는 것을 확인할 수 있습니다.

<script>
  alert(typeof button); 
</script>

<button id="button">Button</button>
1
2
3
4
5

# 인라인 스크립트의 비동기 처리

async 속성

  • defer 와 같이 HTML 처리와 병렬적으로 스크립트가 로딩 된다.
  • 스크립트 로딩이 끝나면, HTML 문서나 다른 스크립트가 로드되길 기다리지 않고 바로 실행된다.
  • 광고, 문서 레벨 이벤트 리스너, 카운터 등 어디에도 종속되지 않는 기능을 구현할 때 유용하다.

일반 스크립트의 async 속성

  • 인라인으로 async 속성을 적용할 수 없다. 🔗

모듈 스크립트의 async 속성

  • 인라인으로 async 속성을 적용할 수 있다.
<script async type="module">
  import {counter} from './analytics.js';
  counter.count();
</script>
1
2
3
4

# 외부 스크립트

type="module" 의 외부 모듈 스크립트의 두가지 특징

  1. 'src' 속성 값이 동일한 외부 스크립트는 한 번만 실행된다.
    <script type="module" src="my.js"></script>
    <script type="module" src="my.js"></script>
    
    1
    2
  2. Cross Origin 요청에서 CORS 헤더가 필요하다.
  • 모듈이 저장되어있는 원격 서버에서 Access-Control-Allow-Origin: * 헤더를 제공해줘야 한다.
    <script type="module" src="http://another-site.com/their.js"></script>
    
    1

# 경로가 없는 모듈은 금지

브라우저 환경에서 import 는 반드시 상대/절대 URL 앞에 와야 한다.

import {sayHi} from 'sayHi'; // Error!
// './sayHi.js'와 같이 경로 정보를 지정해 주어야 합니다.
1
2

Node.js 나 번들링 툴은 경로가 없어도 해당 모듈을 찾을 수 있는 방법을 알기 때문에 경로가 없는 모듈을 수 있다.

# 호환을 위한 'nomodule'

  1. 구식 브라우저
    • type="module" 을 해석하지 못하여, 무시하고 넘어간다.
    • nomodule 을 해석한다.
  2. 모던 브라우저
    • type="module" 을 해석한다.
    • nomodule 을 무시한다.
<script type="module">
  alert("모던 브라우저를 사용하고 계시군요.");
</script>

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

CanIuse 에서 호환성을 볼 수 있습니다. 폴리필을 위하여 script 는 body 의 맨 아래에 두는 것이 좋을 것 같습니다.

# 빌드 툴

  • 브라우저 환경에서는 모듈 단독 사용이 흔치 않다. TODO
  • 웹팩과 같은 빌드 툴을 이용해 모듈을 번들링(묶어)하여 프로덕션 서버에 올린다

# 번들러의 장점

  • 모듈 분해의 통제 (TODO)
  • 경로가 없는 모듈, CSS, HTML 포맷의 모듈을 사용할 수 있게 해준다.

# 빌드툴의 과정

  1. HTML 의 <script type="module"> 에 넣을 '주요' 모듈(진입점 역할을 하는 모듈) 을 선택한다.
  2. '주요' 모듈에 의존하고 있는 모듈 분석을 시작으로 모듈 간의 의존 관계를 파악한다.
  3. 모듈 전체를 한데 모아 하나의 큰 파일을 만든다.
    • 설정에 따라 여러개의 파일을 만들수도 있다.

# 빌드툴의 변형과 최적화

  • 삭제
    • 도달 가능하지 않은 코드
    • 내보내진 모듈 중 쓰임처가 없는 모듈
    • console, debugger 같은 개발 관련 코드
    • 공백
  • 변환
    • 최신 자바스크립트 문법은 babel 을 사용하여 낮은 스크립트 버전으로 변환된다.
    • 변수이름 줄이기
    • import/export 문이 번들러 내 함수로 대체 된다.
      • type="module 이 필요 없어 진다.
      • 웹팩과 같은 툴로 번들링 과정을 거친 스크립트인 bundle.js
      <script src="bundle.js"></script>
      
      1