자바스크립트 ES6 - 모듈(modules)
안녕하세요?
오늘은 ES6의 모듈(module)에 대해 알아보겠습니다.
모듈은 자바스크립트 프로그래밍에 있어 굉장히 중요한 역할을 하는데요.
코드를 분할하여 좀 더 쉬운 관리가 가능하게 하고, 확장성에 용이합니다.
그러면 이 모듈을 이용해서 변수, 함수, 클래스를 import나 export를 어떻게 하는지 알아보겠습니다.
참고로 ES6의 모듈은 엄격 모드(strict mode)에서만 적용됩니다.
즉, 모듈에서 선언된 변수나 함수가 자동으로 전역 스코프에 적용되지 않는다는 점입니다.
웹 브라우저에서 모듈 실행하기
일단 message.js라는 파일이 아래와 같이 있다고 합시다.
export let message = 'ES6 Modules';
message.js 파일은 ES6에서 보면 message라는 변수를 가지고 있는 모듈입니다.
그리고 export 명령어를 통해 message라는 변수를 다른 모듈에 노출시키고 있습니다.
즉, export로 변수나 함수를 외부에 노출하여 외부 모듈에서 사용하라고 하는 겁니다.
그럼, 이 모듈을 받아서 코드를 확장하는 다른 모듈을 만들어 볼까요?
아래와 같이 app.js 파일을 만들어 보겠습니다.
import { message } from './message.js';
const hello = 'Hello '+ message;
console.log(hello);
위 코드를 보시면 message.js 모듈이 export한 message 변수를 app.js에서 import하여 사용하고 있습니다.
간단한 모듈 사용 로직을 만들었으니, 웹 페이지에 적용해 볼까요?
다음과 같은 HTML 파일을 만듭시다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ES6 Modules</title>
</head>
<body>
<script type="module" src="./app.js"></script>
</body>
</html>
위 HTML 파일을 보시면 app.js파일을 불러올 때 아예 type을 module라고 지정했습니다.
ES6 기능인 거죠.
Exporting
이제 export에 대해 좀 더 자세히 살펴보겠습니다.
우리가 한 모듈에서 변수나, 함수 또는 클래스를 다른 모듈에서 쓰이게끔 하려면 export 키워드를 통해 외부 노출을 해줘야 합니다.
익스포트(export)하는 방식은 아래와 같은 형식이 쓰입니다.
// log.js
export let message = 'Hi';
export function getMessage() {
return message;
}
export function setMessage(msg) {
message = msg;
}
export class Logger {
}
log.js 모듈에서 export 하는 방식은 위와 같이 변수, 함수, 클래스 어떤 것이든 가능합니다.
대신에 export 하려면 함수나 클래스의 이름이 꼭 있어야 합니다.
즉, 익명 함수나 익명 클래스는 export가 안 됩니다.
그리고 export는 나중에 한꺼번에 가능합니다.
위 코드에서 각각 export 했지만 아래 코드에서는 한꺼번에 모아서 export 했습니다.
// log.js
let message = 'Hi';
function getMessage() {
return message;
}
function setMessage(msg) {
message = msg;
}
class Logger {
}
export message, getMessage, setMessage;
그리고 위 코드에서 보면 제가 일부로 Logger 클래스를 exort 안 했습니다.
그러면 log.js 모듈의 Logger 클래스는 외부 모듈에서 사용할 수 없다는 뜻입니다.
Importing
우리가 한 모듈에서 export했으면 이제, 다른 모듈에서 export된 모듈의 변수, 함수, 클래스를 import하여 사용할 수 있습니다.
import { what, ever } from './other_module.js';
임포트(import)하려면 괄호 '' 안에 해당 변수나, 함수, 클래스 이름을 나열하면 됩니다.
그러면 export 한 모듈에서의 원래 변수나, 함수, 클래스가 import 한 모듈과 바인딩이 되는 겁니다.
바인딩이 됐다는 의미는 import 한 모듈에서 import 된 변수, 함수, 클래스를 수정할 수 없고, 또 이름이 중복되면 안 된다는 뜻입니다.
예를 들어 볼까요?
// greeting.js
export let message = 'Hi';
export function setMessage(msg) {
message = msg;
}
// app.js
import {message, setMessage } from './greeting.js';
console.log(message); // 'Hi'
setMessage('Hello');
console.log(message); // 'Hello'
message = 'Hallo'; // error
위 코드에서 greeting.js 모듈에 있는 message란 변수를 마지막 줄 코드에서 app.js 파일에서 강제 할당하고 있는데요.
이렇게 하면 에러가 발생합니다. 모듈 간 변수가 바인딩됐기 때문에 직접 수정할 수 없다는 뜻입니다.
쉽게 이해하시려면 모듈에서 import한 변수나, 함수, 클래스가 const 지정자로 선언됐다고 이해하시면 편할 겁니다.
개별 import하기
개별 import는 아래와 같이 한 개의 변수만 export하고 다시 import하는 것을 뜻합니다.
// foo.js
export foo = 10;
// app.js
import { foo } from './foo.js';
console.log(foo); // 10;
foo = 20; // throws an error
foo.js 파일에서 foo라는 변수를 export 했고, 그다음 app.js 모듈에서 그걸 import하고 사용했습니다.
개별 import 방식인데요.
그리고 코드 마지막 줄처럼 import된 foo 변수에는 직접 값을 할당할 수 없습니다.
foo 변수가 const로 선언되었다고 생각하시면 에러가 나는 이유를 쉽게 납득하실 수 있을 겁니다.
여러 개 import하기
아래와 같이 cal.js파일이 있고 cal.js 파일에는 여러 변수와 함수를 export하고 있는데요.
// cal.js
export let a = 10,
b = 20,
result = 0;
export function sum() {
result = a + b;
return result;
}
export function multiply() {
result = a * b;
return result;
}
아래 app.js 모듈에서는 cal.js 모듈에서 export한 걸 import하고 있습니다.
//app.js
import {a, b, result, sum, multiply } from './cal.js';
sum();
console.log(result); // 30
multiply();
console.log(result); // 200
여러 개를 import 하려면 괄호 안에 그냥 나열하면 여러 개를 동시에 import 할 수 있습니다.
전체 모듈을 한 개의 객체로 import 하기
타입스크립트 코드에서 아래와 같은 형식을 많이 보셨을 듯싶은데요.
전체 모듈을 우리가 지정한 이름의 객체로 한꺼번에 import하는 방식입니다.
import * as cal from './cal.js';
이렇게 import하면 cal.js 파일의 변수나 함수는 객체 cal의 속성으로 참조할 수 있습니다.
cal.a;
cal.b;
cal.sum();
이런 방식을 영어로는 'Namespace Import'라고 부릅니다.
그리고 import에 있어 중요한 게 아래 코드처럼 한 개의 모듈을 여러 번 import한다고 해도 해당 모듈은 한 번만 실행된다는 점입니다.
import { a } from './cal.js';
import { b } from './cal.js';
import { result } rom './cal.js';
위 코드에서 첫 번째 줄이 실행되면 cal.js는 메모리에 로드되고, 두 번째 줄이 실행될 때 한번 로드된 cal.js 모듈을 메모리상에서 재사용하는 방식입니다.
그래서, cal.js 모듈에 있는 명령어는 한 번만 실행된다는 뜻입니다.
import, export의 제한사항
임포트(import)나 익스포트(export)에 있어 제한 사항이 있는데요.
바로 import나 export 명령은 다른 명령어나 함수 바깥에 위치해야 한다는 뜻입니다.
그냥 단순하게 import나 export를 함수 안에서는 사용할 수 없다고 이해하시면 됩니다.
if ( requiredSum) {
export sum;
}
function importSum() {
import { sum } from './cal.js';
}
위와 같이 export, import를 함수 안에서 사용하면 안 됩니다.
왜냐하면 자바스크립트에서는 import, export할 대상이 정적으로 정해져야 하기 때문입니다.
참고로 ES6의 다음 버전인 ES2020에서는 다이내믹하게 import 할 수 있는 import() 객체가 도입되었는데요.
오늘은 ES6를 공부하는 날이니까 다음에 알아보도록 하겠습니다.
Aliasing (별칭)
자바스크립트에서는 import할 때나 export할때 별칭을 지정할 수 있는데요.
바로 아래와 같이 as 키워드를 쓰면 됩니다.
// math.js
function add( a, b ) {
return a + b;
}
export { add as sum };
import { sum as total } from './math.js';
위와 같이 export 할 때 add 함수를 sum이라는 별칭 이름으로 export했습니다.
그리고 import할 때는 sum이라는 함수를 total이라는 별칭으로 import했습니다.
Re-Exporting 바인딩
ES6에서는 import한 걸 바로 export할 수 있는데요.
아래와 같은 코드가 있다고 합시다.
import { sum } from './math.js';
export { sum };
위 코드에서는 math.js 모듈에서 import한 sum을 export하고 있는데요.
이런 방식을 Re-exporting이라고 합니다.
그리고 이론적으로는 위 코드에서 두 줄이나 소비됐던 코드가 아래 코드처럼 한 줄로 처리할 수 있습니다.
export { sum } from './math.js';
조금 헷갈릴 수 있는데요.
아래도 가능합니다.
export { sum as add } from './math.js';
export * from './cal.js';
Default exports
우리가 보통 export할 때 한 개의 디폴트 export를 지정할 수 있는데요.
오직 한 개의 default export만 가능합니다.
그냥 export default라고 지정하면 됩니다.
// sort.js
export default function(arr) {
// sorting here
}
그러면 import하는 방식은 괄호 ''를 쓰지 않고 그냥 import하면 됩니다.
import sort from sort.js;
sort([2,1,3])
그리고 디폴트와 디폴트가 아닌걸 import할 때는 꼭 디폴트 import를 먼저 써야 합니다.
// sort.js
export default function(arr) {
// sorting here
}
export function heapSort(arr) {
// heapsort
}
아래와 같이 디폴트로 지정된 걸 먼저 import해야 합니다.
import sort, {heapSort} from './sort.js';
sort([2,1,3]);
heapSort([3,1,2]);
그리고 디폴트도 별칭을 통해 import할 수 있습니다.
import { default as quicksort, heapSort} from './sort.js';
이상으로 ES6 모듈의 import, export 방식에 대해 알아보았습니다.
그럼.