Node.js modules
Node의 모듈 시스템에 대해 이야기 해보겠습니다.
모듈은 당신의 어플리케이션의 다른 자바스크립트 파일들을 인클루드 할 수 있게 해줍니다. 사실 Node의 대부분의 핵심 기능들은 기존에 자바스크립트로 작성된 모듈을 사용하여 구현됩니다. 이말을 즉 Github에 올라와있는 핵심 라이브러리들의 소스코드를 확인하는것이 가능함을 뜻합니다.
모듈은 데이터베이스에 엑세스 하는 라이브러리 같은 외부 라이브러리를 인클루드할수 있게 해줌으로써 어플리케이션을 개발하는데 있어 필수적인 요소입니다. 그리고 제한적인 역할에 따라 코드를 분리하여 정리될 수 있도록 도와줍니다. 당신의 코드중에 재사용이 가능한 파트를 식별할 수 있고 그 부분을 모듈로 분리하여 파일의 코드량을 줄일 수 있으며 읽기 쉽고 유지보수가 더 쉽도록 만들어 줍니다.
모듈을 사용하는 방법은 간단합니다. 핵심 라이브러리의 이름 또는 로드하고자 하는 모듈 파일의 경로를 하나의 인자로 갖는 require() 함수를 사용하면 됩니다. require() 를 이용하여 몇몇의 모듈을 사용했던 이전의 단순한 메시징 어플리케이션 예제에서 본적이 있을것입니다.
모듈을 만들기 위해서는 익스포트 하길 원하는 오브젝트를 정의하는 코드를 작성하여 주면 됩니다. Node에서는 이런 목적으로 사용하기 위해 최상위 스코프에서 exports 오브젝트를 사용할 수 있습니다.
exports.funcname = function() { return 'Hello World'; };
어떠한 프로퍼티라도 exports 오브젝트에 할당 되면 require() 함수의 반환값으로부터 엑세스 할 수 있게 됩니다.
var hello = require('./hello.js'); console.log(hello.funcname()); // "Hello World" 출력
또한 exports 대신에 다음과 같이 module.exports를 사용할 수 있습니다.
function funcname() { return "Hello World"; } module.exports = { funcname: funcname };
이 대체 문법은 exports에 클래스와 같은 단일 오브젝트를 할당하는것을 가능하게 해줍니다. 우리는 이전에 클래스 작성시에 prototype을 이용한 상속을 구현하는것을 논의하였었습니다. 당신의 클래스를 분리된 모듈로 만든 후 매우 손쉽게 그것들을 당신의 어플리케이션에 인클루드 할 수 있습니다.
// class.js: var Class = function() { ... } Class.prototype.funcname = function() {...} module.exports = Class;
클래스를 포함한 모듈을 작성하였습니다. 이제 require() 를 사용하여 당신의 클래스의 새로운 인스턴스를 만들겠습니다.
// 다른 파일 var Class = require('./class.js'); var object = new Class(); // 새로운 인스턴스 생성
Sharing variables between modules
Node에는 전역 컨텍스트 환경이 존재하지 않습니다. 각각의 스크립트마다 그들의 컨텍스트를 가지고 있습니다. 그렇기 때문에 다수의 모듈을 인클루드 하는것이 현재의 스코프를 더럽히진 않습니다. 최상위 스코프에서 var foo = ‘bar’; 와 같은 코드를 선언했더라도 다른 모듈에서는 foo는 선언되지 않은 변수가 됩니다.
이것이 의미하는것은 Node에서 모듈들간에 변수나 값들을 공유하는 유일한 방법은 다수의 파일들이 동일한 모듈을 인클루드 하는 것입니다. 일단 모듈이 캐시가 되면 데이터를 모듈을 이용하여 가령 설정 옵션들처럼 공유할 수 있습니다.
// config.js var config = { foo: 'bar' }; module.exports = config;
다른 모듈에서는 다음과 같이 사용 가능합니다.
// server.js var config = require('./config.js'); console.log(config.foo);
하지만 Node 모듈은 기본적으로 사용 가능한 몇개의 변수를 가지고 있습니다. globals 와 process API 문서를 참고하시기 바랍니다.
- __filename : 현재 실행된 파일의 이름
- __dirname : 현재 실행중인 스크립트가 존재하는 디렉토리의 이름
- process : 현재 실행중인 프로세스에 포함된 정보를 가진 오브젝트, 변수뿐만 아니라 process.exit, process.cwd, process.uptime같은 메소드도 포함
- process.argv : 커맨드 라인의 매개변수를 가지고 있는 배열, 첫번째 엘리먼트의 값은 ‘node’가 될것이며 두번째 엘리먼트는 자바스크립트 파일의 이름이 될것임. 이후의 엘리먼트들은 추가로 입력되는 매개변수들.
- process.stdin / process.stdout / process.stderr : 현재 프로세스의 표준입력, 표준출력, 표준에러에 대응하는 스트림
- process.env : 현재 프로세스의 사용자 환경을 가지고 있는 오브젝트
- require.main : Node를 통해 바로 실행된 파일이라면 require.main은 module과 동일
다음의 코드는 현재 스크립트에 대한 값들을 출력해 줍니다.
console.log('__filename', __filename); console.log('__dirname', __dirname); console.log('process.argv', process.argv); console.log('process.env', process.env); if(module === require.main) { console.log('This is the main module being run.'); }
require.main은 현재 실행중인 모듈이 메인 모듈인지 여부를 알기 위해 사용 될 수 있습니다. 이것은 모듈을 직접 실행했을 때에 무언가 하고 싶은 작업이 있을 경우 유용합니다. 예를 들어 node filename.js로 실행시에 다음과 같이 무언가를 인클루드하여 테스트 파일을 실행하도록 할 수 있습니다.
// 이 모듈이 직접 실행 된다면 테스트가 실행됩니다. if (module === require.main) { var nodeunit_runner = require('nodeunit-runner'); nodeunit_runner.run(__filename); }
process.stdin, process.stdout, process.stderr 들은 스트림에 대해 논의되는 다음 챕터에서 다시 다루도록 하겠습니다.
Organizing modules
require() 를 사용하여 파일을 인클루드 하는 방법은 3가지가 있습니다.
- 상대 경로 사용하기 : foo = require(‘./lib/bar.js’);
- 절대 경로 사용하기 : foo = require(‘/home/foo/lib/bar.js’);
- 검색 사용하기 : foo = require(‘bar’);
첫번째 두번째 방법의 경우 이해하기 쉬울것입니다. 하지만 3번째 방법의 경우 Node가 현재 디렉토리에서 실행될 때 ./node_modules/ 위치 이하의 모듈들을 로드하려고 시도하게 됩니다. 만약 모듈을 찾지 못한다면 부모 디렉토리로 이동하여 같은 시도를 하게 되고 파일 시스템의 루트까지 올라가며 찾게 됩니다.
예를 들어 /home/foo/ 이하의 특정 스크립트에서 require(‘bar’)가 호출 되었다면 원하는 모듈을 찾을 때 까지 다음과 같이 검색을 해 나갑니다.
- /home/foo/node_modules/bar.js
- /home/node_modules/bar.ja
- /node_modules/bar.js
이러한 방법으로 모듈을 로딩하는 것은 상대 경로를 특정짓는것보다 더 간단합니다. 심지어 파일을 이동할 경우에도 패스가 변경되거나 하는것에 대해 걱정할 필요가 없습니다.
Directories as modules
Node를 위한 추가적인 작업을 통해 모듈을 디렉토리 안으로 구성할 수 있습니다.
가장 쉬운 방법은 디렉토리를 생성하는 것입니다. ./node_modules/mymodulename/ 과 같이 디렉토리를 만들고 그 안에 index.js 를 넣으면 됩니다. 이 index.js파일은 기본적으로 로드가 되게 됩니다.
또는 package.json 파일을 mymodulename 폴더 안에 넣으면 모듈의 이름과 메인 파일의 경로를 정의해 줄 수 있습니다.
{ "name": "mymodulename", "main": "./lib/foo.js" }
이 설정은 require(‘mymodulename’) 을 호출할 경우 ./node_modules/mymodulename/lib/foo.js파일을 로드한 결과가 반환되게 됩니다.
npm
npm은 분산 Node 모듈을 이용하는 패키지 관리자입니다. 이것에 대해 여기서 자세히 다루지는 않을 것입니다. 왜냐하면 인터넷 상에 잘 정리가 되어있기 때문입니다. 참고
npm은 굉장합니다. 그리고 당신은 이것을 이용해야 합니다. 아래에 몇가지 사용 예시를 다룰 것입니다.
Installing packages
npm에서 가장 많이 쓰이는 사용 예는 다른 사람의 모듈을 설치하기 위해서 입니다.
- npm search packagename
- npm view packagename
- npm install packagename
- npm outdated packagename
- npm update packagename
패키지는 현재 디렉토리의 ./node_modules/ 아래에 설치가 됩니다.
Specifying and installing dependencies for your own app
npm은 새로운 머신에 당신의 어플리케이션을 설치하는것을 손쉽게 만들어줍니다. 왜냐하면 어플리케이션의 루트 디렉토리에 있는 package.json 파일에 어떤 모듈을 필요로 하는지 정의할 수 있기 때문입니다.
다음은 package.json 파일의 가장 최소화된 모습입니다.
{ "name": "modulename", "description": "Foo for bar", "version": "0.0.1", "repository": { "type": "git", "url": "git://github.com/mixu/modulename.git" }, "dependencies": { "underscore": "1.1.x", "foo": "git+ssh://git@github.com:mixu/foo.git#0.4.1", "bar": "git+ssh://git@github.com:mixu/bar.git#master" }, "private": true }
이 설정으로 인해 당신의 어플리케이션의 의존성에 적합한 적절한 버전의 모듈을 가져올 수 있게 됩니다. 의존성에 맞게 모듈을 설치하기 위해 단지 다음을 실행하면 됩니다.
$ npm install
Loading dependencies from a remote git repository
제가 좋아하는 기능중 하나는 원격지의 git 저장소에서 git+ssh URL을 이용하여 파일들을 가져올 수 있는 기능입니다. git+ssh://github.com:mixu/nwm.git#master 처럼 URL을 지정해 두면 원격 git 저장소에서 바로 의존성 모듈을 설치할 수 있습니다. 해시(#) 이후의 값은 저장소의 tag나 branch를 나타냅니다.
설치된 의존성 모듈의 리스트를 보기위해서는 다음의 명령을 사용하시면 됩니다.
$ npm ls
Specifying custom start, stop and test scripts
당신은 package.json 파일의 script 설정을 통해 다양한 스테이지에서 하고자 하는 특정 작업을 정의해 줄 수 있습니다.
{ "scripts" : { "preinstall" : "./configure", "install" : "make && make install", "test" : "make test", "start": "scripts/start.js", "stop": "scripts/stop.js" } }
위의 예제에서, 우리는 인스톨 이전에 무엇을 할지 인스톨 과정에 무엇을 할지 npm test, npm start, npm stop 명령이 호출되었을 때 무엇을 할것인지에 대한 정의를 하였습니다.
참고 : http://book.mixu.net/ch8.html