linux 환경에서 보통은 /usr/local/lib/node_modules
에 설치된다.
$ npm root -g
global 설치시 CLI 커맨드를 입력할 수 있게된다
$ npm install -g pm2
$ pm2 list # CLI 생성
원래 nodejs의 모듈 참조 우선순위는 가장 가까운 node_modules
(현재 디렉토리 -> 상위 -> 상위 -> ...-> Project Root)다.
그러나 bin
으로 등록되는 CLI 명령어는 우선순위가 다르다.
테스트를 위해 다음과 같이 프로젝트를 구성했다.
# local project 에 5.3.0 버전 설치
$ npm install pm2@5.3.0
# npm root (global) 에 5.1.2 버전 설치
$ npm install -g pm2@5.1.2
// 프로젝트 local node_modules 우선참조
import pm2 from 'pm2'
pm2 CLI 명령어 실행결과
Local 설치 여부 | Global 설치 여부 | 프로젝트 디렉토리상 실행 결과 | 프로젝트 바깥 경로에서 실행 결과 |
---|---|---|---|
O | O | 5.3.0 | 5.3.0 |
X | O | 5.1.2 | 5.1.2 |
O | X | 5.3.0 | Not found : pm2 |
global 및 local 에 모두 설치시
global
우선.
위 실험에서 global 패키지가 local 패키지 보다 우선권을 갖는 것을 확인했다.
이는 환경변수 (ex. PATH) 에 먼저 잡히기 때문이다.
사실 global 버전은 하나로 관리되기 때문에 시시각각 변할 수 있어 다른 프로젝트에 영향을 미칠 수 있다. 따라서 local project package 기준으로 실행되게끔 보장하는게 필요한데, 이 때 쓸 수 있는 방법이 2가지다.
{
"name": "devdependencies",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"ts": "ts-node -v"
},
"dependencies": {
"uuid": "^9.0.0"
},
"devDependencies": {
"ts-node": "^10.9.1"
}
}
위와 같이 package.json 파일 내에서 scripts
를 등록하면 모두 로컬 패키지 기준으로 잡힌다.
# 전역 설치
$ npm install -g ts-node@8.1.1
# local 설치
$ npm install ts-node@10.9.1
$ ts-node -v #8.1.1 ( global 우선)
# npm script 혹은 npx 를 사용하면 local 프로젝트 기준으로 잡힌다.
$ npm run ts # 10.9.1
$ npx ts-node -v #10.9.1
npx
global 설치시 명령어 실행 위치와 관계 없이 곧바로 사용이 가능하지만, 시스템 전체에서 강제로 글로벌에 설치된 버전을 사용해야한다는 단점이 존재한다.
이럴 때 npx 를 사용하면 다음과 같은 순서로 패키지를 찾아 CLI 를 실행한다.
Need to install the following packages:
pm2@5.3.0
Ok to proceed? (y) y
3번 케이스를 통해 설치한 경우 현재 package.json 에 의존성이 추가되지 않는 것에 유의하라.
npx 로 패키지 설치시 npm cache 디렉토리에 설치된다.
즉, Local Project, Global 이 아니라 npm-cache 에 설치되므로 시간이 지나면 사라진다.
Windows 기준: C:\Users{YourUserName}\AppData\Local\npm-cache_npx\
{
"dependencies": {
"pm2": "^5.3.0"
}
}
{
// 실제 production 에 설치
"dependencies": {
"dotenv": "^6.2.0",
"express": "^4.16.4",
"mongodb": "^4.0.0",
"node-fetch": "^2.6.1",
"pino": "^5.11.1",
"workerpool": "^6.1.5"
},
// 개발 및 테스트시에만 설치, npm scripts 에 사용 가능.
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.3.1",
"mocha": "^9.1.3",
"ts-node": "^8.10.2",
"typescript": "^3.9.9"
}
}
흔히 많이 하는 착각이
npm install
하면 dependencies 만 설치되고 devDependencies 에 있는건 설치되지 않는 줄 아닌데 실제로는node_modules
에 둘다 설치된다.
$ npm install uuid
덩그러니 uuid 패키지만 설치하면 다음과 같이 node_modules 에 설치된다.
설치 된다.
--dev 옵션으로 ts-node 설치해보자
$ npm install --dev ts-node
설치된 것은 물론이고 CLI 명령어도 인식된다.
# devDependencies 에 명시한 ts-node 가 node_modules 로 설치되고
# 현재 프로젝트에서 이 모듈을 사용한다.
$ ts-node -v
# 10.9.1
자세히 보면 node_modules/bin
디렉토리에 ts-node
와 관련된 실행파일이 설치된 것을 확인할 수 있다.
실행파일 위치 확인이 필요할 때는 다음 2가지 명령어를 쓰면 된다.
# linux
$ which <command>
# windows
$ where <command> # CMD 인 경우
$ get-command <command> # powershell 인 경우
$ npm list -a <package-name>
$ npm list -a uuid
+-- aws-sdk@2.940.0
| `-- uuid@3.3.2
+-- pm2@5.3.0
| `-- @pm2/io@5.0.0
| +-- @opencensus/core@0.0.9
| | `-- uuid@3.3.2 deduped
| `-- @opencensus/propagation-b3@0.0.8
| +-- @opencensus/core@0.0.8
| | `-- uuid@3.3.2 deduped
| `-- uuid@3.3.2 deduped
`-- swagger-stats@0.99.1
+-- request@2.88.2
| `-- uuid@3.3.2 deduped
`-- uuid@8.3.2
Q1. npm install 시 버전이 어떻게 결정되는가?
A1. package.json 의 dependencies, devDependencies 에 의해 결정된다.
Version | Compatibility | Version example | Code status |
---|---|---|---|
Major | Incompatible | 2.0.0 | New API not backward |
Minor | Compatible | 1.2.0 | new features but backward |
Patch | Compatible | 1.0.1 | Bug fix |
tilde(~): x.y.z 중 z (Patch version) 범위 내에서 버전 업데이트
caret(^): x.y.z 중 x (Major version) 이하 하위 호환성이 보장되는 범위 내에서 버전 업데이트
"dependencies": {
// 메이저 버전을 유지하면서 (하위 호환성을 유지하면서 패키지 설치 시점의 최신 모듈을 설치
"aws-sdk": "^2.489.0", // 2.y.z 중 가장 최신 버전이 설치됨.
}
2023-04-25 기준 aws-sdk 2.1360.0 이 설치된다.
Q2. 그럼 dependencies, devDependencies 에 없다면?
A2. 패키지가 package.json 에 전혀 명시되지 않았는데 설치되는 경우는 이미 package.json 에 존재하는 패키지가 다른 패키지에 대한 의존성이 있는 경우다.
이럴 때는 해당 패키지 내의 package.json에 따라 최신 버전으로 설치되고, 설치된 버전을 package-lock.json 에 기록한다.
Q2. uuid 모듈 버전이 3.3.2 도 있고 8.3.2 도 있다. package.json 에는 uuid 를 포함하지 않았다. 이럴 땐 어느 모듈로 설치되는가?
A2. 3.3.2 로 설치된다.
자세히 보면 3.3.2에 dedeuped
라는 문장이 옆에 붙어있다.
'중복 제거'라는 뜻으로 npm이 의존성 충돌이 발생하지 않는 대다수의 버전을 3.3.2로 인식하고 uuid@3.3.2 를 node_modules
경로에 설치하기로 결정한 것이다.
따라서 aws-sdk, pm2, swagger-stats/request@2.88.2 는 node_modules/uuid
경로의 패키지를 그대로 참조하게된다.
(항상 {project-root}/node_modules
에 설치되는 것은 아니다.)
Q3. 그럼, uuid 8.3.2 는 어디에 설치되나?
A3. 이때는 npm이 swagger-stats/node_modules/uuid
로 직접 설치한다.
반대로 swagger-stats 는 3.3.2가 아닌 8.3.2 가 직접 필요하다.
swagger-stats/package.json 에 의존성 정보중 일부다.
{
"version": "0.99.1"
"_requested": {
"type": "version",
"registry": true,
"raw": "swagger-stats@0.99.1",
"name": "swagger-stats",
"escapedName": "swagger-stats",
"rawSpec": "0.99.1",
"saveSpec": null,
"fetchSpec": "0.99.1"
},
"_requiredBy": [
"/"
],
"author": {
"name": "https://github.com/sv2"
},
"bugs": {
"url": "https://github.com/slanatech/swagger-stats/issues",
"email": "sv2@slana.tech"
},
"dependencies": {
"basic-auth": "^2.0.1",
"cookies": "^0.8.0",
"debug": "^4.3.1",
"moment": "^2.29.1",
"path-to-regexp": "^6.2.0",
"qs": "^6.10.1",
"request": "^2.88.2",
"send": "^0.17.1",
"uuid": "^8.3.2" // ✅ 8.3.2 이상 버전이 필요하다.
},
"description": "API Telemetry and APM. Trace API calls and Monitor API performance, health and usage statistics in Node.js Microservices, based on express routes and Swagger (Open API) specification",
"devDependencies": {
"@hapi/hapi": "^20.1.2",
"@hapi/inert": "^6.0.3",
"artillery": "^1.6.2",
"body-parser": "^1.18.2",
"chai": "^4.1.2",
"chokidar": "^3.5.1",
"concurrently": "^6.0.0",
"coveralls": "^3.1.0",
"cross-env": "^7.0.3",
"cuid": "^2.1.8",
"express": "^4.17.1",
"fastify": "^3.14.1",
"fastify-express": "^0.3.2",
"istanbul": "^0.4.5",
"mocha": "^8.3.2",
"ncp": "^2.0.0",
"nyc": "^15.1.0",
"prom-client": "^13.1.0",
"q": "^1.5.1",
"restify": "^8.5.1",
"serve-favicon": "^2.4.5",
"serve-static": "^1.13.1",
"should": "^13.2.3",
"supertest": "^6.1.3",
"swagger-parser": "^10.0.2",
"swagger-stats-ux": "^0.95.28"
},
}
대부분의 패키지가 3.3.2 버전을 참조하므로 {ProjectRoot}/node_modules/uuid
에 설치해버렸다.
반면에swagger-stats
는 8.3.2 버전이 필요하다.
이럴 때는 {ProjectRoot}/node_modules/swagger-stats/node_modules
패키지 자체 내에 node_modules 를 설치하여 관리한다.
이로써 한 패키지에 대해 여러 버전으로 runtime 실행이 가능한 것이다.
Q4. nodejs / npm 버전이 package 의존에 영향을 미치나?
A4. 미친다. npm 버전이 바뀌면 package-lock.json 의 lockfileVersion 1,2,3 도 바뀔 수 있다.
package-lock.json 은 다음 3가지가 모두 일치해야만 동일하게 유지된다.
1. npm version
2. package.json
3. npm install 시점
npm 버전은 node.js 버전 패치에 따라 자동으로 바뀌므로 유의해야한다.
Q5. npm audit fix 하면 어떻게 버전이 바뀌는가?
A5. semmentic versioning 대로 업데이트된다.
단, npm audit fix --force 하면 취약점이 존재하는 패키지의 메이저 버전도 바꿀 수 있다.
# package.json 에 sementic versioning 정책대로 업데이
$ npm audit fix
보안 이슈 있는 패키지 버전을 compatible 유지한체 업데이트함.
# 메이저 버전도 업데이트
$ npm audit fix --force
Q5. 프로젝트 전체에 걸쳐 특정 패키지 버전을 고정시키고 싶다. 즉, {ProjectRoot}/node_modules/{package}
내부에 설치되는 특정 패키지의 버전을 전역으로 고정시키고 싶다. 어떻게 해야하는가?
A5. overrides
속성을 사용하면된다.
⚠️ 단, nodejs 16.x - npm 8.x 버전부터 사용 가능하다.
uuid 를 프로젝트 전체에서 9.0.0 버전으로 고정한다.
{
"overrides": {
"uuid": "9.0.0"
},
"engines": {
"node": ">=16.18.0"
},
"devDependencies": {
"@types/express": "^4.17.17",
},
"scripts": {
"test": "jest",
"compile": "tsc -p ."
},
"dependencies": {
"express": "^4.18.2",
"pm2": "^5.3.0",
"workerpool": "^6.3.1"
}
}
$ npm list -a uuid
`-- pm2@5.3.0
`-- @pm2/io@5.0.0
+-- @opencensus/core@0.0.9
| `-- uuid@9.0.0 overridden
`-- @opencensus/propagation-b3@0.0.8
+-- @opencensus/core@0.0.8
| `-- uuid@9.0.0 deduped
`-- uuid@9.0.0 deduped
Q6. 본 패키지를 nodejs 특정 버전 이상에서만 실행시키고 싶다. 어떻게 해야하나?
A6. node 를 실행시키는 것 자체를 막을 수는 없는 대신, npm install
을 불가능하게 제한할 수는 있다.
package.json 에 아래와 같은 옵션을 두면된다.
"engines": {
"node": ">=16.18.0"
},
이때 npm
의 경우 .npmrc
를 다음 옵션과 함께 넣어줘야한다.
engine-strict=true
반면에 yarn 은 위 옵션만으로 npm install 을 제한한다.
node 버전을 14.21.3 으로 설정해놓고 npm install
을 하면 다음과 같은 에러가 발생한다.
npm ERR! code ENOTSUP
npm ERR! notsup Unsupported engine for @: wanted: {"node":">=16.18.0"} (current: {"node":"14.21.3","npm":"6.14.18"})
npm ERR! notsup Not compatible with your version of node/npm: @
npm ERR! notsup Not compatible with your version of node/npm: @
npm ERR! notsup Required: {"node":">=16.18.0"}
npm ERR! notsup Actual: {"npm":"6.14.18","node":"14.21.3"}
모듈 참조는 현재 디렉토리 -> Project Root 까지 탐색.
bin으로 등록되는 CLI 명령어 모듈은 Global -> Project Root 탐색
현재 디렉토리 기준으로 CLI 명령어 버전관리 또는 일회성 CLI 커맨드 (ex. eslint, create react-app) 사용시npx
사용.
{Project-Root}/node_modules
에 설치.{other-package}/node_modules
에 설치.