[Node.js] NPM, Yarn

wrld_worthy·2024년 1월 2일

JavaScript

목록 보기
19/21

NPM

NPM은 자바스클비트용 패키지 매니저이다. 유저가 만든 패키지를 등록하는 저장소를 의미하기도 하고, CLI를 의미하기도 한다. Node.js를 설치할 때 함께 설치된다.

NPM은 세상에서 가장 많은 수의 패키지가 등록되어 있고, Java의 메이븐, .NET의 NuGet, Python, PyPI, PHP, Ruby를 모두 합친 것보다 압도적으로 많은 패키지들이 등록되어 있다.

NPM은 Node Package Manager를 줄인 말로 프로젝트에 필요한 의존성 패키지를 고나리하는 프로그램이다.

의존성 패키지는 해당 프로젝트를 실행하는데 꼭 필요한 라이브러리와 모듈을 말한다.

예를 들어서 express 서버는 express 라이브러리에 의존성이 있기 때문에 express 없이는 동작하지 않는다.

npm은 이런 의존성 패키지를 잘 관리하기 위해서 만들어졌다. 단순하게 의존성 패키지 리스트만 잘 관리하면 될 것같지만 npm은 이보다 더 많은 기능을 제공한다.

Package/ Module

Node.js에서 Package는 package.json으로 정의한 파일 또는 디렉터리를 의미한다.
패키지에는 반드시 package.json이 포함되어야하고, 여기에 정의된 모두가 패키지가 될 수 있다.

  1. Package.json 파일이 있는 디렉토리
  2. 1번을 압축한 파일
  3. 2번을 내려받을 수 있는 URL주소
  4. 3번 정보를 가지고 npm 저장소에 <패키지명>@<버전>으로 등록된 것
  5. 4번을 가리키는 <패키지명>@<태그>
  6. <패키지명>만 있는 경우 5번에서 latest 태그를 가리킴
  7. 1번을 결과로 주는 git URL

결과적으로 package.json으로 정의한 코드 뭉치가 바로 패키지이다.


Modulenode_modules 디렉토리 아래에 있는 파일 또는 디렉토리를 말한다.
node_modules에 있는 파일이라 디렉토리는 require()함수로 읽을 수 있다.

Node.js는 commonJS이다.


CommonJS는 브라우저 뿐아니라 서버 애플리케이션에서도 모듈 기능을 제공하기 위해 나온 모듈 규약으로, ES6가 나오기 전에 많이 사용되었고, 특히 Node.js는 CommonJS가 기본 값으로 사용되고 있다.
ES module은 ES6의 자바스크립트 모듈의 표준이다. 패키지를 임포트할 때 CommonJS는 require()함수로, ES module(ESM)은 import로 임포트한다.
package.json에서 type 속성을 module로 설정하면 ESM을 사용할 수 있다.
Node.js에서 ESM을 사용하면 프론트엔드의 코드를 백엔드에서도 쉽게 가져가 사용할 수 있다는 장점이 있다.

node_modules 디렉토리에는 npm install로 설치한 패키지들이 저장된다. 즉 모든 패키지는 모듈이고 설치한다면 node_modules에 모여있다.

또한 npm에 등록되어 있지 않더라도 패키지 포멧만 맞다면 npm install로 설치를할 수 있다.

패키지로 만들면 npm 레지스트리에 등록할 수 있고, 패키지로 만든 코드들은 간단하게 다른 곳에서 설치해 사용이 가능하므로 사설 npm 레지스트리에 공통으로 사용하는 패키지를 배포해서 사용하기도 한다.

npm 문제점

사실 require() 함수를 사용할 때 단순히 현재 디렉토리의 node_modules만 읽는 것은 아니다.

module.pahts에 있는 경로를 따라서 모듈을 찾는다.

mkdir sample-package
cd sample-package
node

# 아래는 Node.js의 REPL이다.
> module.paths

임시 sample-package라는 폴더를 생성하여 node에서 module.paths를 확인해 보았다.

새로 생성한 폴더에는 어떠한 파일도 존재하지 않지만


출력 결과는 다르다.

무언가 있는 것처럼 나온다.

왜 이러는 것일까??

이 출력 결과는 있는 폴더를 나타내는게 아니라, node_modules가 있을 것이라고 가정한 path들이다. 즉, 있지도 않는 디렉토리들을 상위 디렉토리에 있는 패키지를 계속 타고 올라가면서 node_modules를 확인하는 것이다. 이런 방식은 굉장히 많은 I/O를 발생시킨다.
때문에 require()가 무거워지는 원인이 된다.
이런 문제점을 해결한 yarn 프로젝트가 존재한다.

package.json 파일 만들기

package.json이 패키지들의 정보들을 담고 있는데, 이런 파일은 어떻게 생성이 가능할까?

일단 쉬운 방법은 직접 작석하는 것이있다. 하지만 오타나 무언가 문제가 발생할 확률이 매우매우 높다.

그래서 다른 방법은 npm install을 하여 필요한 패키지를 설치하는 것이다. 그럼 자동적으로 package.json과 외에 필요한 파일들을 생성하고 패키지들의 정보를 기록한다.

하지만 위와 같은 방식보다는 최종적으로 npm init -y를 사용하여 생성하는 것이 가장 권장된 방식이다.

위의 명령어를 사용하여 생성하면 기본적인 내용을 작성하여 파일을 생성해준다.

-y 옵션을 사용하지 않고 직접 입력하면 내용을 변경할 수 있다.

생성한 package.json의 내용은

{
	"name": "sample-package",
	"version": "1.0.0",
	"description":"",
	"main":" "index.js",
	"scripts": {
  		"test": "echo \"Error: no test specified\" && exit 1"
	},
	"keywords": [],
	"author": "",
	"license": "ISC"
}

위와 같다.

이렇게 디렉터리를 만드록 package.json을 만들면 설정은 끝이다.

// sample-package/index.js
console.log("require로 부르면 호출됩니다.");

module.exports = {
  add: (a,b) => a + b,
  sub: (a,b) => a - b,
  multi: (a,b) => a * b,
  div: (a,b) => a / b
}

require() 함수로 모듈을 읽을 때 파일을 위에서부터 읽기 때문에 conosle.log를 먼저 실행 할 것이다. module.exports는 require를 사용해 불러왔을 때 반환하는 객체이다.

# 현재 위치는 sample-package의 상위 폴더이다.
$ mkdir sample-test
$ cd sample-test
$ npm install ../sample-package

sample-package를 설치한 후

// smaple-test/index.js

const calc = require("sample-package");

const a = 17;
const b = 3;

console.log("a + b = ", calc.add(a,b));
console.log("a - b = ", calc.sub(a,b));
console.log("a * b = ", calc.multi(a,b));
console.log("a / b = ", calc.div(a,b));

하면 결과는

require로 부르면 호출됩니다.
a + b = 20
a - b = 14
a * b = 51
a / b = 5.66666666666667

와 같은 결과를 얻을 수 있다.

Package 설치, 업데이트, 삭제

설치

npm install 명령어로 패키지 설치를 진행할 수 있다.

  • npm install [<@패키지 스코프(네임스페이스)>/]<패키지 명>@<tag/version/version range>
  • npm i
  • npm add

설치 옵션

  • -D, -save-dev : devDependencies에 의존성 설정 추가
  • -P, -save-prod : dependencies에 의존성 설정 추가, 기본 값 코통은 사용하지 않음
  • -g, -global : 프로젝트 디렉토리가 아닌 node가 설치되어 있는 디렉터리의 [node_modules]에 의존성 패키지 설치

-g 옵션으로 설치한 node_modules 데릭토리는 npm root -g명령으로 찾을 수 있다. -g 옵션으로 설치하려면 관리자 권한이 필요할 수도 있음.

패키지의 정보가 담긴 package.json을 받았는데 실질적으로 node_modules에 설치가 안되어 있을 때!!
npm install 명령으로 package.json에 있는 패키지들을 설치할 수 있다.

그리고 package.json만으로 설치가 진행된다고 생각하기 쉽겠지만 사실은
package.json이 없이 package-lock.json 파일만 잇어도 설치가 된다.
패키지 잠금 부분에서 살펴보자.

업데이트

  • `npm update [-g][패키지명1, 패키지명 2, ...]
  • npm up
  • npm upgrade
    -g 옵션은 install과 마찬가지로 node가 설치되어 있는 디렉토리의 의존성 패키지를 업데이트할 때 사용한다.

설치 패키지 확인하기

  • npm ls [@스코프/] 패키지명
  • npm list
  • npm la
  • npm ll

삭제

  • npm uninstall [@스코프/] 패키지명[@버전][-S| -D| -O]
  • npm remove
  • npm rm
  • npm r
  • npm un
  • npm unlink

예시

$ npm uninstall express --save
$ npm uninstall @types/react
$ npm uinstall jest --save-dev
$ npm uninstall plugin --save-optaional

NPX

npm은 명령어를 지정해 실행하는 스크립트 기능도 제공한다. 스크립트 기능은 앱 시작, 중지, 빌드, 배포, 테스트 등의 명령어를 터미널에 매번 입력하지 않고 package.json에 정의함으로써 조금 더 간편한 명령어를 실행하는 기능이다. 보통 스크립트는 [node_modules] 디렉토리 아래에 설치된 패키지에 있기 때문에 경로를 지정해야 하지만 npx를 이요하면 경로를 지정하지 않고 간편하게 사용할 수 있다.

npm 스크립트 파일을 정의하기

Node.js 프로젝트의 package.json에는 scripts 항목이 존재한다. scripts에 등록된 항목들은 npm run 명령으로 실행할 수 있다.

# sample-test의 상위 폴데어 test-scripts폴더를 생성한다.
$ mkdir test-scripts
$ cd test-scripts
// npx 테스트용 package.json
{
	"name": "test-scripts",
	"version": "1.0.0",
  	"scripts": {
      "hello": "echo 'hello Node.js'"
    }
}

참고로 json에는 주석을 달 수 없다. //는 임시로 그냥 적은 것이다.

터미널에서 실행해보자

$ npm run hello
> test-scripts@1.0.0 hello
> echo 'hello Node.js'

hello Node.js

scripts에 hello로 등록된 명령이 실행된다. 이렇게 npm을 사용해 package.json의 scripts에 선언되어 있는 스크립트들을 실행할 수 있따.

scripts에 정의하는 명령 중에 run 없이 사용할 수 있는 명령어

  • start
  • stop
  • test
  • restart
{
	"name": "test-scripts",
	"version": "1.0.0",
  	"scripts": {
      "hello": "echo 'hello Node.js'"
      "test": "ehco 'test Node.js'", // npm test
      "start": "ehco 'start Node.js'", // npm start
      "stop": "ehco 'stop Node.js'", // npm stop
      "restart": "ehco 'restart Node.js'", // npm restart
    }
}

npx로 prettier 실행하기

NPX는 Node package eXecute의 약자로 Node 패키지 실행자라고도 한다.
Node.js 패키지는 대부분 프로젝트에 임포트해서 사용하지만 개발할 때는 프로젝트 실행, 관리, 테스트 등에 명령형 패키지를 다수 사용한다. 대표적으로 prettier, eslint, jest같은 포매팅, 문법 검사, 단위 테스트 도구들이 있다. 이렇나 패키지들을 실행하려면 node_modules/.bin/{패키지명} 경로로 명령어를 실행해야 한다. npx를 사용하면 npx {패키지명}처럼 경로를 생략해 실행할 수 있다.

# test-npx 폴더 생성
$ mkdir test-npx
$ npm install prettier
{
	"name": "prettier",
	"version": "2.5.1",
  	"description": "Prettier is an opinionated code formatter",
  	"bin": "./bin-prettier.js", // npx로 실행하는 명령어의 경로 지정
  // 생략...
}

prettier 프로젝트의 package.json에 있는 bin 설정이 바로 npx 명령어에서 실행하는 파일이다.

다음과 같이 index.js파일을 생성하여 작성해보자.

function getRandomInt(
	min,
	max)/*주석도 포매팅 해준다*/
{
  	return Math.floor(
      Math.random()
      * (max - min)) + min;
}

console.log(
  getRandomInt
  (10,20));

터미널에서 npx 명령어로 prettier를 실행해보자


$ npx prettier index.js
$ npx prettier -w index.js
// npx prettier index.js 결과
function getRandomInt(min, max){
	/*주석도 포매팅해줍니다*/
    return Math.floor(Math.random() * (max - min)) + min;
}

console.log(getRandomInt(10, 20));

//npx prettier -w index.js의 결과
index.js 33ms

npm prettier index.js를 실행해 index.js를 포매팅한 결과를 출력한다.
이때 포매팅이 index.js에 반영 되지는 않는다. -w 옵션을 추가해 실행하면 포매팅된 코드가 index.js 파일에 반영이 된다.

같은 포매팅을 사용해야 가독성이 높아지고 파일 저장 시 혹은 저장소에 커밋이나 푸시하기 전에 자동으로 적용되도록 하면 유용하게 사용할 수 있다.

yarn

지금까지는 npm만 보았다. npm을 사용한 패키지 관리가 편해보이면서 쉽진 않을 것이다.
npm은 용량 문제, 패키지 내려받는 속도 문제, 보안 문제를 가지고 있다. 이를 신경 쓰지 않는다면 매우 편하겠지만 간과할 수 없는 일이다.

여러문제가 있는 npm의 node_modules를 사용하지 않고 다른 방법으로 패키지를 관리할 수 있는 방법 yarn은 이렇나 문제들을 해결할 목적으로 페이스북에서 만든 패키지 관리 프로그램이다.

yarn은 버전 1과 yarn berry라고 부르는 버전 2가 있다.
버전 1은 npm과 거의 유사하며 패키지 설치가 조금 더 빠른 정도인데,
버전 2는 PnP(Plug n Play) 전력을 사용하여 node_modules를 사용하지 않고 의존성 패키지를 관리한다.

PnP 전략은 패키지를 적절한 위치에 꽂으면 바로 실행 하도록 단순화하는 데 있다.

이를 위해서 node_modules 디렉토리를 사용하지 않고, 의존성 찾기는 .pnp.cjs에 정리하고, 실제 의존성 패키지 파일은 압축 파일의 형태로 .yarn 디렉토리 아래에 cache 디렉토리에 저장한다.

cache 디렉토리 안에는 트리구조가아닌 끌어올림(hoisting)되어 평평하게 저장된다.

PnP 전략을 사용해 의존성 패키지를 코드 저장소에 바로 저장하면, 당연한 말이지만 추후에 서버 배포시 패키지 설치를 하지 않아도 된다. 이것을 제로 인스톨(Zero Install)이라고 한다.

제로 인스톨을 하면, 서버에 소스 코드를 배포할 때 패키지도 같이 설치가 된다. 그러면 패키지 설치 시점에 따라 버전이 달라져서 버그가 발생하는 일도 없어진다.

PnP를 사용함으로써 얻는 이점이 또 하나 있는데, 그것은 서버의 기동 속도가 빨라진다는 것이다.

애플리케이션이 동작하면 require로 불러오는 의존성 패키지가 어디있는지 불러오는 과정이 있는데, PnP를 사용하는 yarn2는 기동할 때 의존성 패키지를 찾는데 파일 시스템을 순회할 필요가 없기 때문에 그 시간만큼 빨리 기동할 수 있다.

yarn 실습

# test-yarn 폴더를 생성한다
$ mkdir test-yarn
$ cd test-yarn
$ corepack enable	# corepack 활성화. Linux/ Mac에서 permission이뜨면  sudo로 실행 해주자.
$ yarn init -2	# yarn 2버전 초기화

corepack은 Node.js 프로젝트에서 npm 이외의 패키지 매니저를 사용하는 기능이다.
yarn과 pnpm을 지원한다.

혹시 corepack 활성화 하는 과정이 에러가 발생한다면?
Node.js 버전이 낮아서 그럴 가능성이 높다.
npm i -g corepack
명령으로 corepack을 설치한 후 진행하면 된다.

README.md, package.json, yarn.lock, .pnp.cjs, .yarn, .git 등등 파일이 생성된 것을 확인할 수 있다.

npm과 yarn 명렁어

명령어npmyarn
의존성 설치npm installyarn
패키지 설치npm install 패키지명yarn add 패키지명
개발용 패키지 설치npm install --save-dev 패키지명yarn add -dev 패키지명
패키지 업데이트npm updateyarn upgrade
패키지 삭제npm uninstall 패키지명yarn remove 패키지명
프로젝트 초기화npm inityarn init
스크립트 실행npm runyarn run
bin 패키지 실행npm 패키지 명령어yarn 패키지 명령어

yarn을 사용하여 chalck 패키지를 설치해보자.

$ yarn add chalk


package.json이 업데이트가 되고, .yarn 아래의 cache 디렉토리에 chalk 패키지의 압축 파일이 추가될 것이다.

0개의 댓글