JavaScript에는 다양한 모듈 시스템(module system)이 존재합니다. 이 글에서는 현존하는 다양한 모듈 시스템에 대해서 정리를 해보려고 합니다.
CommonJS (CJS)
CommonJS (CJS)는 웹 브라우저 환경 외에서도 JavaScript를 사용할 수 있도록 모듈 시스템에 대한 명세(specification)을 제작하는 그룹입니다.
이 때, CommonJS가 정의한 모듈 시스템의 특징은 require()
함수와 module.exports
및 exports
를 사용하는 것입니다.
하나의 값을 내보내기
예를 들어, 다음과 같이 lib.js
에서 module.exports
를 통해 하나의 함수를 내보내고, require("./lib")
를 통해서 이 함수를 불러올 수 있습니다.
1
module.exports = () => { console.log("Hello World!!!"); };
1
2
3
// main.js
const f = require("./lib");
f();
실행을 원하시면, Node.js를 설치하고, 다음의 명령어를 통해 실행을 시켜보세요.
1
$ node main.js
여러 값을 내보내기
또한, 다음과 같이 하나의 객체를 내보내는 것을 넘어 exports
객체의 여러 속성을 할당하여 여러 개의 값을 내보낼 수도 있습니다.
1
2
exports.f = () => { console.log("Hi, f"); };
exports.g = () => { console.log("Hi, g"); };
1
2
3
const lib = require("./lib");
lib.f();
lib.g();
물론, 다음과 같이 구조 분해 할당(destructuring assignment)도 사용할 수 있습니다.
1
2
3
const {f, g} = require("./lib");
f();
g();
module.exports
와exports
는 섞어서 사용할 수 없고, 가능하면module.exports
를 사용하는 것이 좋습니다.
Asynchronous Module Definition (AMD)
JavaScript 표준 API 라이브러리 제작 그룹에는 CommonJS만 있는 것이 아니고, Asynchronous Module Definition (AMD)라는 그룹도 있습니다.
AMD 그룹은 웹 브라우저 내에서의 실행에 중점을 두었습니다. 그래서, 비동기 상황에서도 JavaScript 모듈을 쓰기 위해 CommonJS에서 함께 논의하다 합의점을 이루지 못하고 독립한 그룹입니다.
AMD는 CommonJS와 호환할 수 있는 기능들을 제공해서, require()
함수를 사용하거나 exports
형태로 모듈을 정의할 수도 있습니다.
RequireJS는 이러한 AMD는 사용하는 대표적인 파일 및 모듈 로더입니다.
반면, AMD만의 특징도 있는데요. 바로 define()
함수입니다.
브라우저 환경에서는 JavaScript는 파일 범위가 따로 존재하지 않기 때문에, 이 define()
함수로 파일 스코프의 역할을 대신합니다. 즉, 일종의 네임스페이스 역할을 하여 모듈에서 사용하는 변수와 전역변수를 분리합니다.
예를 들어, 위의 여러 값들을 내보내는 CommonJS의 예시를 AMD를 사용하면 다음과 같이 바꿀 수 있습니다.
1
2
3
4
define({
f: () => { console.log("Hi, f"); },
g: () => { console.log("Hi, g"); },
});
1
2
3
4
5
6
require([
"lib"
], (util) => {
util.f();
util.g();
});
실행을 원한다면, 다음과 같은 HTML 파일을 만들어서 브라우저에서 실행시켜 보세요!
1
2
3
4
5
<!DOCTYPE html>
<html>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script type="text/javascript" src="./main.js"></script>
</html>
Universal Module Definition (UMD)
Universal Module Definition (UMD)는 Node.js를 위한 CommonJS와 웹 브라우저를 위한 AMD를 모두 지원하는 방식입니다. 따라서, 새로운 명세가 아닌 서로 다른 두 모듈 시스템의 명세를 모두 만족하도록 해주는 코드작성 패턴이라고 보면 됩니다.
GitHub의 umdjs/umd 저장소에는 다양한 UMD 패턴을 제공해주고 있는데, 예를 들어 다음은 returnExports.js
에 작성이 되어있는 예시 패턴입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Uses Node, AMD or browser globals to create a module.
// If you want something that will work in other stricter CommonJS environments,
// or if you need to create a circular dependency, see commonJsStrict.js
// Defines a module "returnExports" that depends another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.
// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.
// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('b'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.b);
}
}(typeof self !== 'undefined' ? self : this, function (b) {
// Use b in some fashion.
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
그리고, gulp-umd와 같이 소스 코드를 UMD 패턴으로 변환시켜주는 도구도 존재합니다.
ECMAScript Module (ESM)
2015년에 세상에 나온 ES6에는 모듈 기능 또한 추가가 되었고, 이를 ECMAScript Module (ESM)이라고 부르고 있습니다. ESM은 import
… from
… 와 export
키워드를 사용해야하고, 모듈 코드임을 인식할 수 있도록 .js
대신 .mjs
확장자를 사용해야합니다. ESM의 자세한 정의는 공식 JavaScript 언어의 명세인 ECMA-262에서 확인할 수 있습니다.
예를 들어, 위에서 구현 했던 예시를 다음과 같이 ESM을 이용하여 구현할 수 있습니다.
1
2
export const f = () => { console.log("Hi, f"); };
export const g = () => { console.log("Hi, g"); };
1
2
3
import {f, g} from "./lib.mjs";
f();
g();
실행을 원하시면, Node.js를 설치하고, 다음의 명령어를 통해 실행을 시켜보세요.
1
$ node main.mjs
요약
- CJS (CommonJS): 동기적인 특징으로 Node.js와 같은 서버 사이드에서 사용하기 용이합니다.
- AMD (Asynchronous Module Definition): 비동기적인 특징으로 웹 브라우저와 같은 클라이언트 사이드에서 사용하기 용이합니다.
- UMD (Universal Module Definition): 새로운 명세가 아닌 CJS와 AMD 모두 사용하기 위한 코드 작성 패턴을 의미합니다.
- ESM (ECMAScript Module): ES6 이후로 JavaScript의 공식 모듈 시스템입니다.