2012년 7월 11일

Node.js에서의 Module.(Module의 번역)

http://nodejs.org/api/modules.html 의 내용을 아름다운 한글로 번역.


Module


Node.js는 간단한 모듈 로딩 시스템을 가지고 있다. Node.js에서 파일과 모듈은 1:1로 대응된다. 예를 들어, 다음과 같이 foo.js가 동일 디렉토리에 있는 circle.js 모듈을 로드하는 경우를 보자.

foo.js:
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
           + circle.area(4));

circle.js:
var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

circle.js 모듈은 함수 area()와 circumference()를 외부에 노출하였다(exports). 객체를 외부에 노출하기 위해서는 'exports'라는 객체에 함수를 추가하면 된다.

모듈내에서 로컬변수는 일종의 private이다. 위 예제에서 PI 변수는 circle.js 내에서의 private이다.
모듈 시스템은 require("module")를 통해서 이용할 수 있다.


Cycles

순환하는 require() 호출이 있는 경우에는 그 결과가 리턴되더라도 실행이 되는 것은 아니다.

다음 사항을 고려해보자:

a.js:
console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
b.js:
console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
main.js:
console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

main.js는 a.js를 로드하고, a.js는 다시 b.js를 로드한다. b.js는 a.js를 로드하려고 한다. 이렇게 무한히 반복되는 루프를 막기 위하여, a.js의 exports 객체는 b.js 모듈에서 리턴된다. 그러면, b.js는 모듈 로딩을 끝낸다. 그리고, b.js의 exports 객체는 a.js 모듈에게 제공된다.

main.js는 두 모듈을 모두 로드할 때까지, 즉, 모듈 로드가 끝날 때 종료된다. 따라서, 이 예제는 다음의 결과를 보여준다.

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true


코어 모듈(Core Modules)

Node는 몇 개의 모듈을 컴파일하여 바이너리에 포함하였다. 이러한 모듈들의 상세한 설명은 이 문서(node.js document)에서 기술하고 있다.

코어 모듈이라 함은 node에서 lib/ 폴더에 있는 소스파일들을 의미한다.

코어 모듈이 require()를 통하여 불려지면 항상 우선적으로 로드된다. 예를 들면, require('http')는 동일한 파일(http.js)이 존재하더라도, 컴파일되어 바이너리에 포함된 HTTP 모듈을 리턴할 것이다.(즉, lib/http.js를 로드함)


파일 모듈(File Module)

Node가 모듈을 로드하기 위해서 정확한 파일이름을 찾으려 했을 때, 파일이 존재하지 않는다면 '.js', '.json', '.node' 순으로  확장자를 가진 파일을 로드하려고 시도할 것이다.

'.js' 파일들은 자바스크립트 텍스트 파일로, '.json' 파일들은 JSON 텍스트 파일로 간주된다. '.node' 파일들은 컴파일된 addon 모듈로 인식되고, 이러한 파일들은 'dlopen'를 이용하여 로드된다.

모듈의 경로의 맨앞에 '/'가 있다면 절대경로로 인식한다. 예를 들어, require('/home/macro/foo.js')는 /home/macro/foo.js 파일을 모듈로써 로드한다.

require()를 호출하여 file를 로드할 때, 경로가 './'로 시작한다면 그것은 상대경로를 의미한다. 즉, foo.js에서 require('./circle')가 가능하기 위해서는 circle.js가 동일 경로에 존재해야한다.

파일 지정시에 '/', './'를 가지지 않는다면, 모듈은 core 모듈을 로드하거나 node_modules 폴더의 모듈을 로드하게 된다.

require()에 지정한 경로가 존재하지 않으면, code의 속성에  'MODULE_NOT_FOUND'가 지정되면서 Error를 던진다(throw).

'node_modules' 폴더로부터 로드하기

require()를 이용하여 모듈을 로드할 때, Native 모듈도 아니고, '/', '../' 또는 './'로 시작하지 않는다면, node는 현재 모듈의 상위 디렉토리를 시작으로 '/node_modules'를 검색하고 그 위치로 부터 모듈을 로드하려는 시도를 한다.

만약, 그 경로에서 모듈이 발견되지 않으면, 더 상위 디렉토리로 이동하여 '/node_modules'를 찾으려할 것이다.

예를 들면, '/home/ry/projects/foo.js'에서 require('bar.js')를 호출했다면, node는 다음의 순서를 통하여 해당디렉토리에서 bar.js를 찾는 시도를 하게된다.

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

이러한 과정은 프로그램들이 그들의 의존성을 국한시키고, 충돌을 방지한다.

모듈역할을 위한 폴더(Folder as Modules)

프로그램이나 라이브러리가 그들만을 위한 모듈 디렉토리 관리가 가능하고, 이는 매우 편리함을 준다. 또한, 라이브러리에게 하나의 모듈 진입점을 제공하게 된다. require()를 위한 폴더가 모듈이 되는 방법에는 세 가지가 있다.