모듈, NPM

모듈의 개념과 Node.js의 모듈에 대해서 알아보고, NPM에 대해서 알아봅니다.
모듈, NPM, 패키지 매니저


모듈

모듈모듈

… 모듈은 여러 가지로 정의될 수 있지만, 일반적으로 큰 체계의 구성요소이고, 다른 구성요소와 독립적으로 운영된다… 프로그램에서 임의의 두 부분이 직접적인 상호관계가 많아지면, … 모듈성이 떨어진다… (위키백과)

특정 기능을 하는 재사용 가능한, 독립적인 코드를 모듈(Module)이라고 합니다. 모듈을 작성 할 때는 다른 모듈과의 직접적인 상관관계를 최소화하며, 상호간에 느슨한 인터페이스를 노출하는 것이 스파게티 코드를 방지하고, 짜임새 있는 프로그램을 작성하는 지름길 입니다.

Node.js에서는 파일 하나를 하나의 모듈로 인식합니다. 코드 상에서 모듈의 실체는 값이나 객체(Object)입니다.

module.exports

./gugu.js

function notExported(){
    // ...
}

function printGuguAll(){
    printGugu(1,9);
}

function printGugu(x,y){
    for(var i=x; i <= y; i++)
        for(var j=1; j <= 9; j++)
            console.log(i+'*'+j+'='+i*j);
}

module.exports = { // 이 모듈은 이 객체를 노출합니다.
    print: printGugu,
    printAll: printGuguAll
};

./some-module.js

var fs = require('fs'); // 내장 모듈은 경로를 지정 할 필요가 없습니다.
var gu = require('./gugu'); // 상대 경로나 절대 경로로 모듈의 경로를 정확히 지정해야합니다. .js 확장자는 생략 할 수 있습니다.
gu.print(3,4);

// 이 모듈은 {}를 노출합니다.

/*
3*1=3
3*2=6
3*3=9
3*4=12
3*5=15
3*6=18
3*7=21
3*8=24
3*9=27
4*1=4
4*2=8
4*3=12
4*4=16
4*5=20
4*6=24
4*7=28
4*8=32
4*9=36
*/

위처럼 module.exports라는 객체에 값을 덮어 씌우거나 속성을 할당함으로써 모듈의 내부는 숨기고, 모듈 밖으로 제공하고 싶은 인터페이스를 노출 할 수 있습니다. module 객체는 Node.js에서 파일(모듈) 스코프내에 존재하는 모듈에 대한 정보가 담긴 객체입니다.

require

타 모듈을 사용 할 때는 require라는 내장 함수를 이용합니다. Node.js의 require 함수는 아래와 같은 흐름으로 요청된 경로의 모듈을 로드합니다.

  • require(‘some-module’)
    1. some-module이 내장 모듈(Core Module)이면 그 바이너리 파일을 로드한다.
    2. 그렇지 않다면 ./node_modules 폴더가 있는지 확인하고 ./node_modules/some-module 폴더를 찾는다.
    3. 폴더를 찾으면 some-module 폴더를 로드하고 없으면 상위 디렉토리로 이동하여 다시 위 작업을 반복한다.
    4. /node_modules에서도 some-module 폴더를 찾지 못하면 오류
  • require(‘…/path/some-module’)
    1. 절대 경로나 상대 경로가 주어질 때 그 경로가 폴더라면 폴더를 로드한다.
    2. 그렇지 않다면 경로 뒤에 .js .json .node 를 붙혀가면서 찾아보고 파일을 로드한다.
    3. 그래도 찾지 못하면 오류
  • 경로가 파일인 경우
    1. 파일을 실행하고 module.exports 값을 리턴
  • 경로가 폴더인 경우
    1. 폴더에 package.json 파일이 있다면 main 파일의 경로를 찾아 그 main 파일을 로드한다.
    2. package.json이 없다면 폴더의 index.js 파일을 로드한다.
    3. 파일을 실행하고 module.exports 값을 리턴
  • 이미 로드된 모듈을 로드하는 경우
    • 처음 로드하는 모듈의 경우, 모듈의 코드를 실행하고 module.exports 값을 리턴하는 동시에 메모리에 module.exports 값을 기억해둔다.
    • 다시 모듈을 로드하는 경우, 모듈의 코드를 실행하지 않고 메모리에 있는 해당 모듈의 module.exports 값을 참조한다.

package.json

package.json은 Node.js의 패키지의 메타 정보를 담고 있는 텍스트 파일입니다. 뒤에서 다시 다룹니다.

{
  "name": "workshop",
  "version": "1.0.0",
  "main": "server/server.js", // main 파일의 경로
  "scripts": {
    "lint": "eslint .",
    "start": "node .",
    "dev": "nodemon .",
    "posttest": "npm run lint && nsp check"
  },
  "dependencies": {
    "async": "^2.0.1",
    "compression": "^1.0.3",
    "cors": "^2.5.2",
    "escape-html": "^1.0.3",
    ...  
   }
}

JSON(JavaScript Object Notation)은 속성-값 쌍으로 이루어진 … 포맷이다… 인터넷에서 자료를 주고 받을 때 그 자료를 표현하는 방법으로 알려져 있다. … 본래는 자바스크립트 언어로부터 파생되어 자바스크립트의 구문 형식을 따르지만 언어 독립형 데이터 포맷이다. 즉, 프로그래밍 언어나 플랫폼에 독립적이므로, 구문 분석 및 JSON 데이터 생성을 위한 코드는 C, C++, C#, 자바, 자바스크립트, 펄, 파이썬 등 수많은 프로그래밍 언어에서 쉽게 이용할 수 있다. … (위키백과)

Node.js 주요 내장 모듈

모듈이름기능
osOS운영체제 및 하드웨어 정보 관련
fsFile System파일 시스템 관련
pathPath운영체제별 경로 관련
child_processChild Processes(자식) 프로세스 실행 관련
eventsEvents이벤트 시스템 관련
clusterCluster멀티 코어 CPU를 위한 병렬처리 관련
cryptoCrypto데이터 암호화 관련
httpHTTPHTTP 프로토콜 관련
httpsHTTPSHTTPS 프로토콜 관련
netNetTCP 관련
dgramUDP/DatagramUDP 관련
tlsTLS/SSLTCP/IP 암호화 프로토콜 관련
dnsDNS도메인과 IP 관련
urlURLURL 관련
querystringQuery StringURL 중 query string 관련
readlineReadlineCLI에서 사용자 입력 관련
replREPL대화형 CLI를 만드는 관련
bufferBuffer바이너리 관련
string_decoderString Decoder바이너리를 인코딩에 따라 해석
utilUtilities잡다한 도우미 함수 관련

Node.js의 공식 API 문서 에는 전체 내장 모듈들의 인터페이스에 대한 설명과 예제 코드들이 잘 정리 되어있습니다. 위 표에서 강조된 모듈은 뒤따르는 강의에서 주요하게 사용하게 될 내장 모듈들입니다.

Node.js 주요 전역 객체

객체설명예시
global최상위 전역 객체global.someThing = 10;
console.log(someThing); // 10
consoleCLI에 텍스트 출력 관련console.log();
process현재 프로세스 관련process.exit();
process.env // 시스템의 환경 변수를 담은 객체
module현재 모듈 관련module.exports = 20;
require모듈 로드 관련require(‘express’);
__dirname현재 모듈이 위치한 디렉토리의 절대 경로/Users/dehypnosis/benzene/workshop
__filename현재 모듈의 절대 경로/Users/dehypnosis/benzene/workshop/test.js

전역 객체는 console.log의 console이나 require 또는 표준 내장 객체(Array, String 등) 처럼 따로 require하지 않고서도 바로 이용할 수 있는 객체, 값들을 의미합니다. 특히 global 객체에 값을 설정하면 한 프로세스 내의 모듈 사이에 그 값이 공유됩니다. 물론 global 객체에 쓰고 읽는 것은 지양 할 안티패턴입니다.

NPM

어떤 프로그램을 만들 때 수 많은 부품이 필요 할 수 있습니다. 모든 부품을 장인 정신으로 만들 수도 있지만, 생산성을 위해 타인이 만들어 둔 부품들을 이용 할 수 있습니다. Node.js에서는 인터넷에 공개된 패키지(모듈)를 설치하고 관리하는 도구로 NPM(Node Package Manager)을 제공합니다. Node.js를 설치할 때 같이 CLI 프로그램 npm이 설치됩니다.

웹 서버를 만들 때 Express.js, 웹 소켓을 구현 할 때 Socket.io 등 개발에 도움이 되는 오픈소스 패키지들이 많습니다. 유명한 패키지에 대해서 모르더라도 필요한 기능에 대한 공개 패키지를 구글링 해보면 많은 시간을 줄일 수도 있습니다. 또는 패키지를 만들고 인터넷에 공개하여 오픈소스 생태계에 도움을 줄 수도 있습니다.

https://www.npmjs.com/

package.json

이렇게 프로그램에서 다른 독립적인 모듈을 이용하는 것을 의존성(Dependency)을 갖는다고 합니다. npm은 이러한 의존성을 관리하기 위해서 모듈의 ./package.json 파일에 모듈이 의존하고 있는 모든 모듈의 이름과 버전을 기록해둡니다. 이후에 모듈을 배포 할 때는 타 모듈들이 설치된 ./node_modules 폴더를 제외하고 package.json을 포함한 모듈의 코드를 배포하면 충분합니다.

{
  "name": "workshop", // 패키지에 대한 설명
  "version": "1.0.0",
  "main": "server/server.js", // main 파일의 경로
  "scripts": {
    "lint": "eslint .",
    "start": "node .",
    "dev": "nodemon .",
    "posttest": "npm run lint && nsp check"
  },
  "dependencies": {
    "async": "^2.0.1", // 의존 모듈들
    "compression": "^1.0.3",
    "cors": "^2.5.2",
    "escape-html": "^1.0.3",
    ...  
  }
}

npm 사용법

~$ mkdir package
~$ cd package

# 현재 폴더에 package.json 파일을 생성
~/package$ npm init

# nodemon 모듈을 전역으로 설치
~/package$ npm install nodemon -g

# express 모듈을 ./node_modules에 설치하고 package.json에 기록
~/package$ npm install express --save

# express 모듈을 제거
~/package$ npm remove express

# 현재 패키지의 의존성 트리를 출력
~/package$ npm list

# package.json에 기록된 의존 모듈들을 모두 ./node_modules에 설치
~/package$ npm install

# express 모듈을 제거하고 package.json에서 지움
~/package$ npm remove express --save
~/package$ npm list
이 강의를 포함한 커리큘럼
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 10일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.