Node.js를 사용하여 개발을 하다 보면 자주 접하게 되는 파일이 바로 package.json이다. 이 파일은 자바스크립트 프로젝트의 메타데이터를 담고 있으며, npm 패키지 관리와 관련된 중요한 역할을 한다. 그럼 package-lock.json은 뭐고, 이 두 파일의 차이점은 무엇일까 ?
보통 개발을 할 때 처음에 'npm install'하면 자동으로 생기기 때문에 그냥 넘어가기 쉬운데 이번 포스팅에서 package.json과 package-lock.json의 정의부터 주요 구성 요소, 사용 예시, 중요성, 관련 명령어, 관리 및 업데이트 방법까지 자세히 알아보겠다.
package.json은 Node.js 프로젝트의 핵심 파일로, 프로젝트의 정보와 설정, 설치된 모든 패키지의 정보를 담고 있다. 이를 통해 협업 시에 팀원들이 같은 패키지 버전을 설치할 수 있도록 보장한다. 이 파일은 프로젝트의 이름, 버전, 설명, 메인 파일, 스크립트, 의존성 등을 정의하는데 쉽게 말해서 package.json은 프로젝트의 "설명서"와 같은 역할을 하는 것이다.
package.json 파일은 여러 가지 중요한 속성으로 구성되어 있는데, 주요 속성은 다음과 같다.
예를 들어 아래와 같은 형식으로 작성될 수 있다.
{
"name": "my-awesome-web-app",
"version": "1.0.0",
"description": "My awesome web application project",
"main": "dist/index.js",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.3.4"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-react": "^7.20.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.11.0",
"eslint": "^8.30.0",
"jest": "^29.3.1"
},
"scripts": {
"start": "webpack-dev-server --open --mode development",
"build": "webpack --mode production",
"test": "jest",
"lint": "eslint src/**/*.js",
"deploy": "npm run build && firebase deploy",
"clean": "rm -rf dist",
"prepublishOnly": "npm test && npm run lint && npm run build"
}
}
처음 보면 뭔가 복잡해보일 수 있지만 사실 그냥 사용한 패키지(외부 라이브러리)의 이름과 버전들, 프로젝트의 이름과 버전 등이 적혀있는 것일 뿐이다. 위 예시에는 main 속성이 없는데, package.json에서는 name과 version 속성이 필수 항목이고 그 외에는 선택 항목이다. 이 두 가지가 없으면 npm에서 패키지를 식별하고 설치할 수가 없기 때문에 무조건 필요하다. 나머지 속성도 파일 자체를 만들 때 필수는 아니지만 실제 프로젝트에서는 scripts나 dependencies 같은 것은 거의 필수처럼 쓰이고 있다.
참고로 name은 url로 사용되고 설치할 때 디렉토리 이름이 되기 때문에 url이나 디렉토리에서 쓸 수 없는 이름을 사용하면 안된다. ( 대문자 안됨 + 보통 점이나 밑줄로는 시작하지 않음 )
보통 프로젝트의 루트 디렉토리에 위치한다. 그리고 scripts 섹션을 통해 다양한 명령어를 정의할 수 있는데 아래 예시를 보자.
"scripts": {
"start": "webpack-dev-server --open --mode development",
"build": "webpack --mode production",
"test": "jest",
"lint": "eslint src/**/*.js",
"deploy": "npm run build && firebase deploy",
"clean": "rm -rf dist",
"prepublishOnly": "npm test && npm run lint && npm run build"
}
start 명령어는 npm start로 실행, test 명령어는 npm test로 실행, 나머지 명령어는 npm run 명령어 로 실행할 수 있다.
package-lock.json에는 package.json에 적힌 패키지들의 정확한 설치 기록을 저장한 파일이다. 프로젝트의 "상세 설명서"라고 볼 수 있다. 즉, package.json 에 적힌 패키지들의 정확한 설치 기록을 저장한 파일이다. 아래 표로 보면 더욱 이해하기 쉬울 것이다.
| 파일명 | 역할 | 비유 |
|---|---|---|
| package.json | 프로젝트 정보와 어떤 패키지를 사용했는지 명시 | 설명서 |
| package-lock.json | 실제 설치된 패키지의 정확한 버전 및 설치 기록 | 상세 설명서 |
더 정확하게 말하자면 Node.js 프로젝트에서 설치된 모든 패키지들의 정확한 버전과 의존성 트리를 기록해놓는 파일이다. 굳이 이게 필요한 이유는 뭘까 ?
( 참고로 우리가 설치하는 패키지들(예: react, axios)은 혼자 작동하는 게 아니라 다른 작은 패키지들에 의존하고 있다. 그리고 그 패키지도 또 다른 패키지에 의존할 수 있고, 이렇게 꼬리에 꼬리를 물고 이어지는 관계를 의존성 트리라고 한다. )
package.json 파일에는 패키지 이름이랑 버전의 범위( 예: "axios": "^1.3.4" )가 적혀 있다. 여기서 ^ 같은 기호는 "1.3.4 버전 이상이면서 2.0.0 버전 미만"처럼 버전의 범위를 의미하는데 문제는 시간이 지나면서 패키지의 새로운 마이너(minor)나 패치(patch) 버전이 계속 나올 수 있다는 것이다 !
문제가 발생할 수 있는 예시 상황
개발자 A가 패키지를 사용하였다. 몇 달 후 개발자 B가 해당 프로젝트에 npm install 을 하여 사용하려고 한다. 그 사이에 해당 패키지가 업데이트 버전이 나왔고, 개발자 B가 npm install을 하면 업데이트 된 패키지를 설치하게 되는 것이다. 이러한 상황이라면 같은 프로젝트인데도 개발 환경마다 설치되는 패키지 버전이 달라져서 예상치 못한 버그가 발생할 수도 있다.
즉, package.json의 버전 범위로 인한 환경별 버전 불일치 문제를 방지하기 위하여 package-lock.json에는 명확한 설치 버전이 기록된다. package-lock.json은 npm install을 실행한 그 시점에 실제로 설치된 모든 패키지들의 정확한 버전, 어디서 다운로드했는지, 그리고 무결성 검증 값까지 상세하게 기록된다.
위와 같은 원리로 다른 개발자가 npm install을 할 때 package-lock.json에 기록된 정확한 버전 그대로 패키지를 설치할 수 있게 된다. 결론적으로 package-lock.json은 어떤 환경에서든 동일한 의존성 트리를 보장해서 개발 환경 간의 불일치나 배포 시 문제를 막아주는 역할을 하는 것이다.
