이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.
모듈이란 애플리케이션을 구성하는 개별적 요소로서 재사용 가능한 코드 조각을 말한다. 일반적으로 모듈은 기능을 기준으로 파일 단위로 분리한다. 즉, 자신만의 파일 스코프(모듈 스코프)를 가진다.
파일 스코프를 갖는 모듈의 자산(변수, 함수 등)은 기본적으로 비공개 상태이다. 즉, 캡슐화되어 있기에 다른 모듈에서 접근이 불가하다.
모듈은 애플리케이션이나 다른 모듈에 의해 재사용되어야 의미가 있으므로 이를 명시적으로 선택적 공개가 가능한데, 이 키워드를 export
라 한다.
그리고 공개된 모듈의 자산을 사용하는 모듈 사용자는 자신의 스코프 내로 불러들여 재사용할 수 있는데, 이 키워드를 import
라 한다.
정리하면 모듈은 애플리케이션과 분리되어 개별적으로 존재 가능하며 재사용성을 향상시켜 개발 효율성과 유지보수성을 높일 수 있다.
자바스크립트는 script
태그를 사용하여 외부의 자바스크립트 파일을 로드할 수 있지만 독립적인 파일 스코프를 갖지 않는다.
따라서, 파일 분리를 진행하고 script
태그로 로드해도 결국 하나의 자바스크립트 파일에 있는 것처럼 작동하는데 이는 전역 변수의 충돌 등 다양한 문제를 야기한다.
자바스크립트를 브라우저 환경 외에도 사용하고자 하는 움직임이 생기면서 모듈 시스템의 필요성이 더 높아졌고, 이런 상황에 제안된 것이 CommonJS
와 AMD(Asynchronous Module Definition)
이다.
Node.js는 CommonJS를 채택 후 독자적인 진화를 거쳐 완전히 동일한 사양의 CommonJS는 아니지만 기본적으로 CommonJS를 따르고 있다. 즉, ECMAScript 표준 사양은 아니다.
module.exports
로 모듈 정의, require
로 모듈 사용// A.js
module.exports = function sum(a,b) {
return a + b;
}
// B.js
const a = 10;
const b = 5;
const moduleSum = require('A.js');
moduleSum(a,b); // 15
정리하면, module.exports
로 모듈을 내보내고 require
키워드로 모듈을 가져온다고 볼 수 있겠다.
module.exports
와 exports
가 있는데 공식 문서에 의하면 exports
는 단순히 module.exports
를 참조할 뿐이라고 되어있다.
즉, module.exprots
와 exports
는 같은 객체를 바라보고 있으며, exports
는 module.exports
의 shortcut이다.
내보내기 할 것들의 컨테이너로 활용할 경우 아래와 같이 사용하면 된다.
// A.js
module.exports.foo = function() { console.log('foo'); }
module.exports.name = 'Kim';
exports.age = 100;
// B.js
const imports = require('A.js');
console.log(imports); // {foo: ƒ, name: 'Kim', age: 100}
constructor function으로 사용할 경우 아래와 같이 사용하면 된다.
// A.js
module.exports = function() { console.log('foo'); }
// B.js
const foo = require('A.js');
foo(); // foo
다만, 주의할 점은 다음과 같은 코드는 불가하다.
exports = { name : 'Kim' }
exports = function() { console.log('foo'); }
위 코드는 module.exports
에 key, value로 값을 넣는 것이 아닌 새로운 변수 exports
에 값을 넣으라는 뜻이다. 따라서 module.exports
의 값은 비어있게 되어 오류를 발생시킨다.
defined
으로 모듈 정의, require
로 모듈 사용전체적인 문법은 다음과 같다.
// myModule.js
// 첫 번째 인자 = 모듈의 의존성을 나타내는 배열
// 두 번째 인자 = 모듈의 실제 구현부
define(['A', 'B'], function(moduleA, moduleB) {
console.log(moduleA, moduleB);
// 모듈을 내보내는(정의하는) 곳
return {
newModuleA: moduleA,
newModuleB: moduleB,
}
});
// A.js
// 첫 번째 인자 = 사용하고자 하는 모듈
// 두 번째 인자 = 모듈 사용부
require(['myModule'], function (myModule) {
console.log(myModule.newModuleA); // A
console.log(myModule.newModuelB); // B
});
AMD와 CommonJS를 호환하기 위해 나온 모듈 시스템이다. 정해진 코드라기보단 하나의 디자인 패턴과 가깝다고 한다. AMD의 define
과 CommonJS의 module.exprots
의 차이를 활용하면 UMD를 만들 수 있다.
예시는 여기(제로초 블로그)서 확인해보자.
ES6에서 클라이언트 사이드 자바스크립트에서도 동작하는 모듈 기능을 추가했다. script
태그에 type="module"
어트리뷰트를 추가하면 로드된 자바스크립트 파일은 모듈로써 동작한다.
보통은 ESM임을 명확히 하기 위해 파일 확장자를 mjs
로 사용할 것을 권장한다고 한다.
<script src="app.mjs" type="module"></script>
ESM은 파일 자체의 독자적인 모듈 스코프를 제공하기 때문에 모듈 내 var
키워드로 선언한 변수는 더 이상 전역 변수가 아니며 window
객체의 프로퍼티도 아니다.
모듈 내부에서 선언한 식별자를 외부에 공개하기 위해 export
키워드를 사용한다. 선언문 앞에 사용하며 변수, 함수, 클래스 등 모든 식별자를 export
할 수 있다.
// A.mjs
export const school = {
Seoul : '서울대',
Suwon : '수원대',
};
export const name = 'Kim';
export function sum(a, b) {
return a + b;
}
하나의 값만 내보내고자 한다면 export default
키워드를 사용할 수 있다. export
와 차이점이라면 식별자 없이 하나의 값만 export
한다는 점과 var, let, const
키워드는 사용할 수 없다는 점이다.
export default const name = 'Kim'; // Uncaught SyntaxError
사용하고자 하는 모듈을 자신의 모듈 스코프 내부로 로드할 때 사용하는 키워드이다. export
한 식별자 이름으로 import
해야하며 주의할 점은, CommonJS와 다르게 파일 확장자를 생략할 수 없다.
import { school, name, sum } from './A.mjs';
export
한 식별자 이름을 일일히 지정하지 않고 한 번에 import
도 가능하다. 또한 import
되는 식별자는 as
키워드 뒤에 지정한 이름의 객체에 프로퍼티로 할당된다.
import * as lib from './A.mjs';
console.log(lib.school); // { Seoul : '서울대', Suwon : '수원대' }
console.log(lib.name); // 'Kim'
console.log(lib.sum(1,2)); // 3
export default
키워드의 경우 임의의 이름으로 import
할 수 있으나 가독성을 위하여 export
한 식별자의 이름으로 사용한다.
// Person.mjs
export default class Person {}
// App.mjs
import abc from './Person.mjs'; // abc => class Person