# ESM

# export import

  • 최상위 모듈스코프에서만 동작
    • { } 안에서는 동작하지 않는다.
  • 코드가 최상위 모듈스코프의 어떤 위치에 있어도 상관 없다.
  • 정적인 방식
    • import 문에 동적 매개변수를 사용할 수 없다.
    • 모듈 경로엔 원시 문자열만 들어갈 수 있다.
    import ... from getModuleName(); // 모듈 경로는 문자열만 허용되기 때문에 에러가 발생합니다.
    
    1
    • 함수 호출 결과값을 경로로 쓸 수 없다.
    • 런타임이나 조건부로 모듈을 불러올 수 없다.
    if(...) {
      import ...; // 모듈을 조건부로 불러올 수 없으므로 에러 발생
    }
    
    {
      import ...; // import 문은 블록 안에 올 수 없으므로 에러 발생
    }
    
    1
    2
    3
    4
    5
    6
    7
    • 정적인 방식은 번들링 작업을 가능하게 한다.

# export

변수/함수/클래스 의 선언부 앞에 export 를 붙인다.

// 배열 내보내기
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// 상수 내보내기
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// 클래스 내보내기
export class User {
  constructor(name) {
    this.name = name;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

클래스나 함수선언을 export 할 땐 세미콜론을 붙이지 않는다.

  • 세미콜론은 표현식에서 붙인다.
export function sayHi(user) {
  alert(`Hello, ${user}!`);
}  // 끝에 ;(세미콜론)을 붙이지 않습니다.
1
2
3

선언부와 떨어진 곳에 export 붙이기

// 📁 say.js
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // 두 함수를 내보냄
1
2
3
4
5
6
7
8
9
10

# import

import {sayHi, sayBye} from './say.js';
1

# import *

  • 코드가 짧아진다.
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
1
2
3

가져올 땐 대상을 명시하는 것이 좋은이유

  1. 웹팩과 같은 모던 빌드 툴
    • 로딩 속도를 높이기 위해 모듈들을 모으는 번들링 최적화 수행
    • 빌드툴은 실제 사용되는 함수만 번들링 결과물에 포함한다.
    // 📁 say.js
    export function sayHi() { ... }
    export function sayBye() { ... }
    export function becomeSilent() { ... }
    
    // 📁 main.js
    import {sayHi} from './say.js';
    
    1
    2
    3
    4
    5
    6
    7
  2. 이름을 간결하게 써 준다. say.sayHi()보다 sayHi()
  3. 코드 구조를 파악하기 쉬워 리팩토링, 유지보수에 도움

# as

import 'as'

  • 모듈의 이름을 바꿔서 가져옴
import {sayHi as hi, sayBye as bye} from './say.js';
1

export 'as'

export {sayHi as hi, sayBye as bye};
1

# named export 와 export default

모듈의 종류

  1. 복수의 함수가 있는 라이브러리 형태
    • 예제 say.js
    • named export 한 모듈
  2. 개체 하나만 선언 되어있는 모듈
    • export default
named export export default
import {} 필요 {} 불필요
export 개체의 이름 필요 개체의 이름 없어도됨
개수 N 개 1개
export, import Name 동일해야함 임의로 정할 수 있음

# export default

export 옆에 'default' 추가

// 📁 user.js
export default class { 
  constructor(name) {
    this.name = name;
  }
}
1
2
3
4
5
6

{User} 가 아닌 User 로 클래스를 가져옴

import User from './user.js'; 

new User('John');
1
2
3

export, import 이름을 임의로 정할 수 있음.

import User from './user.js'; // 동작
import MyUser from './user.js'; // 동작
1
2

하지만 코드의 일관성을 위해 파일이름과 동일한 것이 좋음.

  • 강제 일관성을 위해서는 default 가 아닌 named export 를 사용할 수 있음.

# default 키워드

기본 내보내기를 참조하는 용도

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

// 함수 선언부 앞에 'export default'를 붙여준 것과 동일합니다.
export {sayHi as default};
1
2
3
4
5
6

# export default / named export 혼용 가능

// 📁 user.js
export default class User {
  constructor(name) {
    this.name = name;
  }
}

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

// 📁 main.js
import {default as User, sayHi} from './user.js';
// 혹은
import User, { sayHi } from './user.js';

new User('John');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 으로 가져오기
// 📁 main.js
import * as user from './user.js';

let User = user.default; // default export
new User('John');
1
2
3
4
5

# 모듈 다시 내보내기

export { sayHi } from './say.js'; // sayHi를 다시 내보내기 함
export { default as User } from './user.js'; // default export를 다시 내보내기 함
1
2

export User from './user.js' 는 에러

export * from './user.js'

  • default export 가 무시된다.
  • named export 만 다시 보내진다.

named 와 default 를 동시에 내보내기

export * from './user.js'; // named export를 다시 내보내기
export { default } from './user.js'; // default export를 다시 내보내기
1
2

예시) npm 을 통해 외부에 공개할 패키지를 만드는 상황

폴더구조

auth/
    index.js
    user.js
    helpers.js
    tests/
        login.js
    providers/
        github.js
        facebook.js
        ...
1
2
3
4
5
6
7
8
9
10

auto/index.js

  • 진입점 역할을 하는 주요 파일
  • 주요 파일을 통해 외부에 패키지를 노출시킨다.

import {login, logout} from 'auth/index.js'

  • 개발자가 패키지를 사용하는 방법
// 📁 auth/index.js

// login과 logout을 가지고 온 후 바로 내보냅니다.
import {login, logout} from './helpers.js';
export {login, logout};

// User를 가져온 후 바로 내보냅니다.
import User from './user.js';
export {User};
...
1
2
3
4
5
6
7
8
9
10

# 동적으로 모듈 가져오기 import(module) 표현식

# import(module) 의 반환값

  • 이행된 프로미스 객체
  • 프로미스의 값: 해당 모듈의 import, export 를 모두 포함하는 객체

# 특징

코드 어디에서나 동적으로 사용 가능

let modulePath = prompt("어떤 모듈을 불러오고 싶으세요?");

import(modulePath)
  .then(obj => <모듈 객체>)
  .catch(err => <로딩 에러, e.g. 해당하는 모듈이 없는 경우>)
1
2
3
4
5

named export

(async ()=> {
  let {hi, bye} = await import('./say.js');
  
  hi();
  bye();
})()
1
2
3
4
5
6

default export

(async ()=> {
    let obj = await import('./say.js');
    let say = obj.default;
    // let {default: say} = await import('./say.js');
    
    say();
})()
1
2
3
4
5
6
7

동적 import 는 일반 스크립트에서도 동작한다.

  • <script type="module"/> 가 없어도 된다.

import() 는 함수 호출문법이 아니다.

  • 변수에 복사할 수 없다.
  • call, apply 를 쓸 수 없다.