What is Module
Module이란 독립적인 하나의 Software를 의미합니다. 사실 순수한 Javascipt에는 Module이라는 개념이 분명하게 존재하고 있지는 않았는데, JS가 구동되는 환경에 따라서 서로 다른 Module화 방법이 제공되고 있었습니다. 이렇게 Module화된 Software는 자주 사용되는 코드라면 재활용성이 크게 향상되고, 해당하는 코드를 개선하면 이를 사용하는 모든 Application의 동작이 개선됩니다. 만일 Module의 런타임 환경이 웹브라우저라면, 해당하는 공간에 저장이 되므로 동일한 로직을 로드할 때 시간과 트래픽을 절약할 수 있다는 장점이 있습니다.
Javscript 모듈 시스템의 종류
앞서 말했듯이 순수한 JS에는 Module System 개념이 분명하게 존재하고 있지는 않습니다. 하지만 JS가 점점 커지고 복잡해지면서 Module Stytem의 필요성을 느낀 개발자들은 몇 개의 Module System Pattern을 개발하는데, 그 중에 가장 유명한 것이 CommonJS
, AMD(Asynchronous Module Definition)
와 ES Module
입니다.
CommonJS
CommonJS
는 Server Side JS Apps를 개발하기 위해 도움이 되는 사양을 정의하기 위한 자발적 프로젝트였습니다. Common은 JS를 브라우저에서만이 아니라 범용으로 사용할 수 있도록 하겠다는 의미를 포함하고 있다고 생각할 수 있을 듯합니다. JS를 범용으로 사용하기 위해서는 여러 가지 문제를 해결해야 할 필요가 있는데, CommonJS에서 정리한 문제점은 다음과 같습니다.
- 문제점
- 서로 호환되는 표준 라이브러리가 없다
- DB에 연결하는 표준 인터페이스가 없다
- 다른 모듈을 삽입하는 표준이 없다
- 코드를 패키징해서 배포, 설치하는 방법이 필요하다
- 의존성 문제까지 해결하는 패키지 모듈 저장소가 필요하다
이 모든 문제점은 결국 모듈화를 통해서 해결할 수 있는 문제였고, 따라서 CommonJS는 이 모듈을 어떻게 정의하고 사용할 것인지에 관한 명세를 만들었습니다. 모듈화의 가장 핵심은 다음과 같습니다.
- 핵심
- Scope: 모든 모듈은 자신만의 독립적인 실행 영역을 가져야한다
- Definition: 모듈 정의는 exports 객체를 이용한다
- Usage: 모듈 사용은 require 함수를 이용한다
서버사이드에서 사용되는 JavaScript(Node.js 등)
의 경우, 파일마다 독립적인 파일 스코프가 있기에, 파일 하나를 모듈 하나로 생각해서 작성하면 됩니다. 즉,A.js
와 B.js
에서 동일한 a
라는 변수를 사용하더라도 전역변수가 겹치는 일은 일어나지 않습니다. 만일 이 경우에 서로 Data를 교환할 필요가 있다면 exports
라는 전역 객체를 통해서 공유하게 되며, 이를 다른 곳에서 사용하려면 require()
라는 함수를 통해서 사용할 수 있습니다.
// A.js
const a = 1;
const b = 2;
exports.sum = (c, d) => {
return a + b + c + d
}
// or
const sum = (c, d) => {
return a + b + c + d;
}
module.exports = sum;
// B.js
const a = 5;
const b = 6;
const moduleA = require('A.js');
moduleA.sum(a, b); // 결과는 14(1+2+5+6)가 출력될 것입니다.
위의 예제는 가장 흔한 방식의 JS 모듈입니다. 여기서 module.exports는 객체
이므로, 만일 하나의 파일에서 다양한 기능을 exports하고 싶다면, 객체 형태로 만들어서 넣어주면 됩니다.
// A.js
const a = 1;
const b = 2;
const sum = (c, d) => {
return a + b + c + d;
}
const multiply = (c, d) => {
return a * b * c * d;
}
const substract = (c, d) => {
return a - b - c - d;
}
module.exports = {
sum,
multiply,
substract
};
// or
exports.sum = (c, d) => {
// ...
}
exports.multiply = (c, d) => {
// ...
}
// B.js
const sum = require('A.js').sum;
sum(4, 5);
A.js
에 나와있듯이 module을 export할 때는 한꺼번에 묶어서 객체로 부여하는 방법이 있고, exports객체에 속성을 부여하여 추가하는 방법이 있습니다. exports
와 module.exports
는 참조 관계로, 동일한 기능을 수행합니다.
CommonJS
는 모든 파일이 로컬에 있어 필요할 때 바로 불러올 수 있는 상황을 전제로 합니다. 즉, 서버 사이드에서 사용함을 전제로 한다는 의미이기도 합니다. 하지만 이런 경우 브라우저에서는 치명적인 단점을 가지게 되는데, 필요한 모듈을 모두 로컬에 내려받기 전까지는 브라우저가 아무것도 할 수 없는 상태에 빠진다는 것입니다. CommonJS 자체에서도 물론 이런 문제를 해결하기 위한 방법이 생겨났지만, 이에 불만을 가진 사람들이 따로 그룹을 만들어 나오게 되는데 그것이 AMD
입니다.
AMD(Asynchronous Module Definition)
Asynchronous라는 이름에서도 볼 수 있듯이 AMD는 필요한 모듈을 네트워크를 이용해 내려받아야 하는 브라우저 환경에서도 모듈을 사용할 수 있도록 하는 것에 중점을 두었습니다. AMD와 CommonJS의 차이는 비동기 상황에서 어떻게 사용하면 좋은지에 관한 것으로, 기본적인 Definition이나 Usage는 비슷합니다. 다만 차이점이 있다면 define()
입니다.
define()
은 전역 함수로, define(id?, dependencies?, factory)과 같이 사용됩니다. 첫번째 인자 id
는 이 모듈의 이름을 식별하는 인자로, optional하게 사용합니다. 만일 이게 없다면 default는 파일명이 됩니다. 두번째 인자 dependency
는 의존성 배열인데, 반드시 먼저 로드되어야 하는 모듈을 나타냅니다. 이 역시 optional하게 적용할 수 있고, 만일 지정하지 않은 경우에는 ['require', 'exports', 'module']이 사용됩니다. 이렇게 먼저 로드된 모듈은 세번째 인자인 factory함수로 넘어갑니다.
// A.js
define(['library'], (lib) => {
// 호출 된 'libaray'는 함수의 인자 'lib'에 담기고, 이 scope 내에서만 접근 가능하다
return {
libraryA: lib // 이러면 다른 파일에서도 해당하는 프로퍼티명으로 접근 가능하다
}
})
// B.js
require(['A'], (module) => {
module.libraryA // A에서 export한 libraryA에 접근할 수 있다
})
이를 구현한 가장 대표적인 스크립트로 RequireJS
가 있지만, ES6가 등장하고, React나 Vue등의 프론트엔드 라이브러리/프레임워크가 사용되고 있는 현재는 AMD는 거의 사용되고 있지 않고 ES Module이 주로 사용
됩니다.
ES2015 모듈
자바스크립트는 ECMA Script 2015(ES6)
이전과 이후로 나뉜다고 해도 과언이 아닐 정도로 ES6에는 많은 변화를 가지고 왔는데요, ES6에서 변화된 것들 중에서 가장 획기적인 변화는 아무래도 ES모듈의 적용이 아닐까합니다. 기존의 순수JS에서는 모듈 시스템이 명확하게 존재하지 않았고, 이로 인해서 앞서 말씀드렸던 AMD나 CommonJS등의 명세가 생겨나게 되었는데요, 이들보다 훨씬 직관적인 사용법으로 현재 프론트엔드에서는 가장 많이 사용되고 있는 방법입니다.
// A.js
export const random = Math.ceil(Math.random() * 100);
export const square = (x) => {
return x * x;
}
// B.js
import { random, square } from 'A';
console.log(square(random))
Conclusion
결과적으로 백엔드에서는 Node.js에서 채택한 방식인 CommonJS를, 프론트엔드에서는 ES모듈을 사용하고 있다는 것을 알 수 있습니다. Node.js는 ES6가 부분적으로만 적용되어 있고, 모듈 시스템은 차용하고 있지 않은바, 모듈에 한해서는 반드시 CommonJS를 사용해야 합니다.