Jihyeok's Blog JavaScript Modules
Jihyeok's Blog
Cancel

JavaScript Modules

JavaScript에는 다양한 모듈 시스템(module system)이 존재합니다. 이 글에서는 현존하는 다양한 모듈 시스템에 대해서 정리를 해보려고 합니다.

CommonJS (CJS)

CommonJS (CJS)는 웹 브라우저 환경 외에서도 JavaScript를 사용할 수 있도록 모듈 시스템에 대한 명세(specification)을 제작하는 그룹입니다.

이 때, CommonJS가 정의한 모듈 시스템의 특징은 require() 함수와 module.exportsexports를 사용하는 것입니다.

문서에서 살펴볼 수 있듯이, Node.js 또한 CommonJS를 사용하는 것을 알 수 있습니다.

하나의 값을 내보내기

예를 들어, 다음과 같이 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.exportsexports는 섞어서 사용할 수 없고, 가능하면 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은 importfrom … 와 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의 공식 모듈 시스템입니다.
This post is licensed under CC BY 4.0 by the author.
Trending Tags