💻 모듈이란?
모듈이란 특정한 목적을 가진 복수의 함수나, 클래스, 변수등으로 구성된 파일을 지칭합니다.
특히 JavaScript
에서는 명시적으로 global scope
에 선언하지 않는한 global scope
를 오염시키지 않는 값들을 선언한 파일이라고 할 수도 있습니다.
💻 Script vs Module
👨💻 Script
script
파일은 모듈과는 달리 export
나 import
구문이 없는 파일로, 보통 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.js
가 a.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.js
의 sayHi
함수를 다른 모듈에서 사용하게끔 하려면 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 };
위 코드는 personName
을 Default Export
로 노출시키며, Person
을 Human
이라는 식별자로 노출시킨 코드 입니다. 이 코드는 아래 코드와 같은 의미를 갖습니다.
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 libA
는 a.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
모듈 파일은 name
을 Default Export
로 sayHi
, Person
을 Named 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
});