모듈 시스템
포스트
취소

모듈 시스템

💻 모듈이란?

모듈이란 특정한 목적을 가진 복수의 함수나, 클래스, 변수등으로 구성된 파일을 지칭합니다.
특히 JavaScript에서는 명시적으로 global scope에 선언하지 않는한 global scope를 오염시키지 않는 값들을 선언한 파일이라고 할 수도 있습니다.

💻 Script vs Module

👨‍💻 Script

script 파일은 모듈과는 달리 exportimport 구문이 없는 파일로, 보통 script 태그에 아무 옵션없이 사용되는 파일을 의미합니다.
script 파일에 선언되는 모든 변수는 global scope에 선언되며, 다른 script 파일에서 해당 파일을 import하지 않아도 접근 가능합니다.

1
2
3
4
5
6
7
// a.js
function sayHi() {
  console.log("Hi nice to meet you.");
}

// b.js
sayHi();

위 예시에서, a.js 파일에 선언한 sayHi 함수를 b.js 파일에서 특별한 import 구문 없이 사용하고 있습니다.

1
2
<script src="./a.js"></script>
<script src="./b.js"></script>

위와 같이 파일들을 브라우저에서 실행하게 되면, 콘솔에 결과가 찍히는 것을 확인할 수 있습니다.
(❗️ b.jsa.js에 의존성이 있기때문에 a.js를 먼저 로드해야 합니다.)

함수가 전역변수에 선언되기 때문에, window.sayHi로 접근할 수도 있습니다.

1
2
3
4
5
// b.js
sayHi(); // Hi nice to meet you
window.sayHi(); // Hi nice to meet you
console.log(sayHi === window.sayHi); // true
console.log(this); // window

👨‍💻 Module

모듈은 본인 만의 scope가 존재합니다. 따라서 다른 모듈에서 해당 파일의 값에 접근하려면 import 구문을 통해 불러와야 합니다.

1
2
3
4
5
6
7
8
// a.js
export function sayHi() {
  console.log("Hi nice to meet you.");
}

// b.js
import { sayHi } from "./a.js";
sayHi();

위 예시처럼, a.jssayHi함수를 다른 모듈에서 사용하게끔 하려면 export 구문을 통해 외부에 노출시켜야 하고, b.js처럼 해당 함수를 사용하려면 import 구문을 사용해야 합니다.

1
2
<script type="module" src="./a.js"></script>
<script type="module" src="./b.js"></script>

모듈파일들을 브라우저에서 실행시키기 위해서는 type="module"를 추가해주어야 합니다.

모듈에 선언된 값들은 global scope에 선언되지 않기때문에 window 객체를 통해 접근할 수 없습니다.

1
2
3
4
5
6
// b.js
import { sayHi } from "./a.js";

sayHi(); // Hi nice to meet you
console.log(window.sayHi); // undefined
console.log(this); // undefined

💻 Named Exports

다른 모듈 파일들에게 값이나 변수, 함수등을 제공하기 위해서는 export로 외부에 노출시켜야 합니다.

1
2
3
4
5
6
7
8
9
10
// a.js
const name = "John";

function sayHi() {
  console.log("Hi nice to meet you.");
}

class Person {}

export { name, sayHi, Person };

위 코드에서 알 수 있듯이, 외부로 노출 시키고자 하는 값들을 식별자(이름)를 통해 내보내고 있는 것을 볼 수 있습니다.
export로 값을 노출시킬 때, 아래와 같은 방법을 사용할 수도 있습니다.

1
2
3
4
5
6
7
export const name = "John";

export function sayHi() {
  console.log("Hi nice to meet you.");
}

export class Person {}

다른 모듈 파일이 노출시킨 값들을 가져오기 위해서는 아래와 같이 import를 사용해야합니다. 이 때 모듈이 내보낸 값들을 모두 가져올 필요는 없습니다.

1
2
3
4
// b.js
import { sayHi } from "./a.js";

sayHi(); // Hi nice to meet you

위 코드는 a.js에서 내보낸 name, sayHi, Person 중에서 sayHi 함수만 선택적으로 import 구문을 사용해 가져온것입니다.
이렇게 노출된 값들의 식별자와 동일한 식별자를 import 구문에 명시해야 값을 가져올 수 있는것을 Named Export라고 합니다.

💻 Default Export

1
2
3
4
5
6
7
8
9
10
// a.js
const name = "John";

export function sayHi() {
  console.log("Hi nice to meet you.");
}

export class Person {}

export default name;

위 예시는 name 변수를 Default Export로 내보낸 코드 입니다. 위 코드에서 볼 수 있듯이 Default Export는 하나의 값에만 사용할 수 있습니다. 또한 함수, 클래스, 표현식(expression)을 제외하고는 export default를 사용해서 값을 노출시킬 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
const name = "John";
export default name; // work

export default function sayHi() {}; // work
export default class B {} // work
export default "John"; // work
export default {}; // work
export default () => undefined; // work
export default function() {}; // work
export default class {} // work

export default const name = "John"; // error

Default Export의 경우 값을 내보낼때, 식별자를 따로 지정하지 않기때문에 임의의 식별자를 사용해서 값을 가져올 수 있습니다. 또한 Named Export와 하나의 import 구문을 통해 가져올 수도 있습니다.

1
2
3
4
5
// b.js
import personName, { sayHi } from "./a.js";

console.log(personName); // John
sayHi();

💻 Import And Export Alias

값을 가져오거나, 내보낼때 as 키워드로 식별자를 재정의 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
// a.js
const personName = "John";

function sayHi() {
  console.log("Hi nice to meet you.");
}

class Person {}

export { personName as default, sayHi, Person as Human };

위 코드는 personNameDefault Export로 노출시키며, PersonHuman이라는 식별자로 노출시킨 코드 입니다. 이 코드는 아래 코드와 같은 의미를 갖습니다.

1
2
3
4
5
6
7
8
9
10
11
const personName = "John";

function sayHi() {
  console.log("Hi nice to meet you.");
}

class Person {}

export default personName;

export { sayHi, Person as Human };

모듈로 부터 값을 가져올때도, 아래의 예시처럼 식별자를 재정의 할 수 있습니다.

1
2
3
4
5
// b.js
import personName, { sayHi as sayHello } from "./a.js";

console.log(personName); // John
sayHello(); // Hi nice to meet you.

💻 Import All Named Exports

값을 가져올때, * as 구문을 사용하면 모듈의 Named Export된 값들을 모두 가져올 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// a.js
const name = "John";

function sayHi() {
  console.log("Hi nice to meet you.");
}

class Person {}

export default name;

export { sayHi, Person as Human };

// b.js
import name, * as libA from "./a.js";

console.log(name); // John
libA.sayHi(); // Hi nice to meet you.
console.log(libA.Human); // Class Person

위 코드에서 * as libAa.js에서 Named Export로 외부에 노출된 값들을 libA 객체에 할당하는 것을 의미합니다. 따라서 libA.sayHi와 같은 접근이 가능합니다.
한가지 주목할점은 libA를 실제로 콘솔에 찍어보면 Named Export된 값들 외에 default라는 키에 Default Export된 값도 들어 있는것을 볼 수 있습니다. 이를 통해 Default Export도 결국 default라는 식별자로 Named Export된 값임을 알 수 있습니다.

1
2
3
4
5
6
// b.js
import name, * as libA from "./a.js";

console.log(name); //John
console.log(libA); // {sayHi: function, Human: class, default: 'John'}
console.log(libA.default); // John

💻 Re-Export

1
2
3
4
// c.js
import name, { sayHi, Human } from "./a.js";

export { name as default, sayHi, Human as Person };

위 예시는 a.js로 부터 값들을 가져온 후 다시 내보내고 있는 코드입니다. 즉 c.js 모듈 파일은 nameDefault ExportsayHi, PersonNamed Export로 내보내고 있습니다. 위의 코드는 아래와 같이 축약해서 표현할 수도 있습니다.

1
2
// c.js
export { default, sayHi, Human as Person } from "./a.js";

위의 코드 처럼 Re-Export를 할때 주의점은, Re-Export하는 값에 접근할 수 없다는 것 입니다.

💻 Module Caching

모듈은 최초 호출시(런타임에 처음으로 import 구문을 만났을때) 한번만 실행됩니다. 즉 같은 모듈을 여러군데에서 가져와 실행시켜도 JavaScript Engine은 이미 실행된(초기화된) 모듈을 가져옵니다.

1
2
3
4
5
6
7
8
// alert.js
alert("모듈이 실행되었습니다.");

// a.js
import "./alert.js"; // 모듈이 실행되었습니다. alert 창 노출

// b.js
import "./alert.js"; // 노출되지 않음

위 예시처럼, alert.js 모듈을 a.js, b.js에서 두군데에서 사용하고 있지만, 처음으로 호출된 a.js에서만 실행되었습니다.

💻 Dynamic Import

import() 표현식(expression)을 사용하면, 모듈을 동적으로 불러올 수 있습니다. import() 포현식은 import 선언문(statement)와 유사하지만, 모듈의 경로를 인자로 받아 Promise를 반환한다는 차이점이 있습니다.
import 표현식이 반환하는 Promise 객체에는 Named Export로 내보낸 값들과, Default Export로 내보낸 값이 있습니다. 만약 모듈을 찾지 못한다면 오류가 발생해 catch문이 실행됩니다.

1
2
3
4
5
6
// b.js
import("./a.js").then((libA) => {
  console.log(libA.default); // John
  libA.sayHi(); // Hi nice to meet you.
  console.log(libA.Human); // class Person
});
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.