Node.js는 Chrome V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경으로 주로 서버 사이드 어플리케이션 개발에 사용되는 소프트웨어 플랫폼이다.
// app.js
const http = require('http'); // 1
http.createServer((request, response) => { // 2
response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.end('Hello World');
}).listen(3000); // 3
console.log('Server running at http://127.0.0.1:3000/');
module.exports
또는 exports
객체를 통해 정의하고 외부로 공개한다.require
함수를 사용하여 임포트한다.http
는 기존 선언된 모듈이며 이를 require
함수로 import 한 것이다.createServer([requestListener])
메소드를 사용하여 HTTP 서버 객체를 생성한다.모듈이란 어플리케이션을 구성하는 개별적 요소를 말한다.
자바스크립트를 Client-side에 국한하지 않고 범용적으로 사용하고자하며 모듈에 대한 필요성이 대두되었다. (자바스크립트 파일은 독립적인 Scope를 가지지않고 하나의 전역 객체에 바인딩됨)
CommonJS와 AMD는 사양(spec)으로 라이브러리가 아니다.
Spec | 문법 | 동작 방식 |
---|---|---|
CommonJS | 비교적 간단함 | 동기 방식 모듈 시스템의 사실상 표준(de facto standard) |
AMD | 다소 까다로움 | 비동기 방식 대표적인 모듈 로더는 RequireJS |
Node.js는 독자적인 진화를 거쳐 CommonJS 사양과 100% 동일하지는 않지만 기본적으로 CommonJS 방식을 따른다.
🤔 브라우저는 ES6 모듈을 지원하지 않으므로 Browserify 또는 webpack 과 같은 모듈 번들러를 사용해야한다.
✔️ CommonJS는 JS를 브라우저에서 뿐만 아니라, 서버사이드 어플리케이션이나 데스크톱 어플리케이션에서도 사용하려고 조직한 자발적 워킹 그룹이다.
탄생 배경
서버사이드 JavaScript의 주요 쟁점
Kevin은 자바스크립트에 다음과같은 문제를 제기하였다.
핵심은 모듈화
앞에서 언급한 문제점들은 결국 모듈화로 귀결된다.
👉 CommonJS의 주요 명세는 이 모듈을 어떻게 정의하고, 어떻게 사용할 것인가에 대한 것이다.
모듈화는 다음과 같은 세 부분으로 이루어진다.
exports.${변수명}
이나 require(${모듈명})
을 이용하여 다른 모듈의 객체를 사용하는 것은 모든 파일이 로컬 디스크에 있어 필요할 때 바로 불러올 수 있는 상황을 전제로 한다. (=서버사이드 JS 환경)
이는 필요한 모듈을 모두 내려받을 때까지 아무것도 할 수 없게 되는 것을 의미하며, 동적으로 <script>
태그를 삽입하는 방법으로 극복하였다.
비동기 모듈 로드 문제
브라우저에서 JS를 사용할 때는 파일 단위 스코프가 없기 때문에 <script>
를 이용해서 파일을 로드하면 동일한 이름의 변수는 덮어씌워지는 문제가 발생한다.
이를 해결하기 위해 서버모듈을 비동기적으로 클라이언트에 전송할 수 있는 모듈전송 포맷을 추가로 정의하였다.
/* 서버사이드에서 사용하는 모듈 */
// complex-numbers/plus-two.js
var sum = require("./math").sum;
exports.plusTwo = function(a){
return sum(a, 2);
};
/*************************************/
/* 브라우저(클라이언트사이드)에서 사용하는 모듈 */
// complex-numbers/plus-two.js
require.define({"complex-numbers/plus-two": function(require, exports){
//콜백 함수 안에 모듈을 정의한다.
var sum = require("./complex-number").sum;
exports.plusTwo = function(a){
return sum(a, 2);
};
},["complex-numbers/math"]);
//먼저 로드되어야 할 모듈을 기술한다.
전송 포맷으로 서버사이드의 모듈을 감싸면 비동기적으로 이를 로드할 수 있다.
✔️ AMD 그룹은 비동기 상황에서도 JavaScript 모듈을 쓰기 위해 CommonJS와 함께 논의하다 합의점을 이루지 못하고 독립한 그룹이다.
Vs. CommonJS
모듈 명세
require()
함수를 사용할 수 있으며exports
형태로 모듈을 정의할 수 있다.define()
함수를 이용해 파일 스코프를 대신한다. (일종의 네임스페이스 역할) 👉 모듈에서 사용하는 변수와 전역변수를 분리 가능define() 함수
전역함수로써 다음과 같이 정의한다.
define(id?, dependencies?, factory);
script
태그의 src 값으로 자동 치환된다.전역변수와 define.amd 프로퍼티
AMD 명세에서 정의하는 전역변수는 다음과 같다.
하지만, 그 밖에 다른 전역변수나 메서드, 프로퍼티를 추가하면 안된다.
example
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
// 넘겨받는 인수를 사용해도 되고
return beta.verb();
// 또는 require()를 이용해
// 얻어 온 모듈을 사용해도 된다.
return require("beta").verb();
}
});
alpha(id)라는 모듈을 정의하는데 beta(dependencies) 모듈이 필요함을 나타낸다.
다음과 같이 CommonJS 형태의 모듈을 래핑할 수도 있다.
define(function (require, exports, module) {
var a = require('a'),
b = require('b');
exports.action = function () {};
});
AMD의 장점
npm(node package manager)은 Node.js에서 사용할 수 있는 모듈들을 패키지화하여 모아둔 저장소 역할 겸 패키지 설치 및 관리를 위한 CLI를 제공한다.
패키지 설치
$ npm install <package>
# 버전 명시
$ npm install <package@version>
지역 설치와 전역 설치
# 지역 설치
$ npm install <package>
# 전역 설치
$ npm install -g <package>
지역 설치 시 프로젝트 루트 디렉터리에 node_modules
디렉터리가 자동 생성되고 그 안에 패키지가 설치된다. 이는 해당 프로젝트 내에서만 사용할 수 있다.
package.json과 의존성 관리
npm은 package.json
파일을 통해 프로젝트 정보와 패키지 의존성을 관리한다.
# package.json 생성 -> 프로젝트에 대한 정보를 입력하라고 요청한다.
$ npm init
# package.json 생성 -> 기본 설정값으로 생성된다.
$ npm init [-y | --yes]
package.json에서 가장 중요한 항목은 name과 version이다.
{
"name": "emoji",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"node-emoji": "^1.10.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
dependencies
항목에는 해당 프로젝트가 의존하는 패키지(해당 프로젝트에서 참조하는 모듈)들의 이름과 버전을 명시한다.
유의적 버전
...
"dependencies": {
"node-emoji": "^1.5.0"
},
...
버전의 명시적 선언과 함께 install을 하면 버전 앞에 ^
이 추가된다. 이는 이후 해당 패키지 버전이 업데이트되었을 경우, 마이너 버전 범위 내에서 업데이트를 허용한다는 의미이다.
즉, 다시 한 번 npm install node-emoji
를 실행하면 최신 버전으로 자동 업데이트되는 것이다.
버전 정보 앞에는 기호를 부여하여 업데이트 범위를 지정할 수 있다.
표기법 | Description |
---|---|
version | 명시된 version과 일치 |
>version | 명시된 version보다 높은 버전 |
>=version | 명시된 version과 같거나 높은 버전 |
<version | 명시된 version보다 낮은 버전 |
<=version | 명시된 version과 같거나 낮은 버전 |
~version | 명시된 version과 근사한 버전 |
^version | 명시된 version과 호환되는 버전 |
~
(틸트)와 ^(
캐럿)의 차이는 아래와 같다
~
(틸트)는 패치 버전 범위 내에서 업데이트한다. :^
(캐럿)는 마이너 버전 범위 내에서 업데이트한다. :브라우저 상에서 동작하는 JS는 script tag로 로드하며 복수의 JS 파일을 로드할 경우 하나의 파일로 merge되며 동일한 유효범위를 갖게된다.
Node.js는 모듈 단위로 각 기능을 분할한다.
모듈은 module.exports 또는 exports 객체를 통해 정의하고 외부로 공개한다. 공개된 모듈은 require 함수를 사용해 임포트한다.
모듈 안에 선언한 모든 것들은 기본적으로 해당 모듈 내부에서만 참조 가능하다.
// circle.js
const { PI } = Math;
exports.area = (r) => PI * r * r;
exports.circumference = (r) => 2 * PI * r;
// app.js
const circle = require('./circle.js'); // == require('./circle')
console.log(`지름이 4인 원의 면적: ${circle.area(4)}`);
console.log(`지름이 4인 원의 둘레: ${circle.circumference(4)}`);
export 객체는 프로퍼티/메소드를 여러개 정의 할 수 있는 것에 반해 module.exports에는 하나의 값(원시 타입, 함수, 객체)을 할당할 수 있다.
require()로 할당받은 변수는 module.exports에 할당한 값 자체이다.
exports는 module.exports의 참조이며 module.exports의 alias이다. 즉, exports는 module.exports와 같다고 보아도 무방하다.
구분 | 모듈 정의 방식 | require 함수의 호출 결과 |
---|---|---|
exports | exports 객체에는 값을 할당할 수 없고 공개할 대상을 exports 객체에 프로퍼티 또는 메소드로 추가한다. | exports 객체에 추가한 프로퍼티와 메소드가 담긴 객체가 전달된다. |
module.exports | module.exports 객체에 하나의 값(원시 타입, 함수, 객체)만을 할당한다. | module.exports 객체에 할당한 값이 전달된다. |
// foo.js
module.exports = function(a, b) {
return a+b;
};
// app.js
const add = require('./foo');
const result = add(1, 2);
console.log(result);
module.exports는 1개의 값만을 할당할 수 있기에 다음과 같이 객체를 사용하여 복수의 기능으 ㄹ하나로 묶어 공개하는 방식을 사용할 수 있다.
// foo.js
module.exports = {
add (v1, v2) { return v1 + v2 },
minus (v1, v2) { return v1 - v2 }
};
// app.js
const calc = require('./foo');
const result1 = calc.add(1, 2);
console.log(result1); //3
const result2 = calc.minus(1, 2);
console.log(result2); //-1
require 함수의 인수에는 파일뿐만 아니라 디렉터리를 지정할 수도 있다.
모듈을 명시하지 않는 경우에는 해당 디렉터리의 index.js를 로드한다.
project/
├── app.js
└── module/
├── index.js
├── calc.js
└── print.js
// app.js
const myModule = require('./module');
// module/index.js
module.exports = {
calc: require('./calc'),
print: require('./print')
};
app.js 에서의 한 번의 require로 module/ 디렉터리 하위의 모든 모듈들을 사용할 수 있다.
const http = require('http');
const mongoose = require('mongoose');
const foo = require('./lib/foo');