일반적으로 리액트 프로젝트를 생성하면, package.json과 package-lock.json 파일이 생성된다.
package.json은 패키지 명과 버저닝 관리, 스크립트 관리, 프로젝트 의존성 관리 등을 위해 사용되는 파일 정도로 인식하고 있었다.
package-lock.json 파일은 그 구조를 살펴보면 생김새가 package.json과 비슷하게 생겼다. 하지만 지금까지 이 파일이 어떤 용도인지 정확하게 알아볼 생각도, 필요성도 느끼지 못했다.
1년 넘게 리액트 프로젝트를 진행했는데, 진행하고 있는 프로젝트의 파일도 제대로 이해하지 못한다는 사실에 부끄럽다는 생각이 들었다. 따라서 이참에 한번 알아보기로 했다.
해당 포스팅은 기본적으로 package.json의 용도를 이해한다는 전제 하에 작성된다. 때문에 이에 관련한 자세한 설명은 생략한다.
package-lock.json의 역할을 알기에 앞서, npm의 의존성 버전 관리에 대해 이해할 필요가 있다.
"dependencies": {
"express": "^4.18.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ws": "^8.16.0"
}
package.json 파일의 의존성 메타데이터를 살펴보면, 버전 앞에 '^' 혹은 '~' 기호가 붙어 있는 것을 발견할 수 있다. 이는 의존성 버전 관리를 위한 기호로 각각 캐럿(^)과 틸드(~)로 불린다.
npm의 패키지는 0.0.0 형식으로 버전을 관리를 한다. 앞에서부터 메이저, 마이너, 패치 버전으로 통용된다.
보통 메이저는 패키지의 API 변경과 같은 큰 변화가 일어났을 때, 마이너는 추가적인 기능 업데이트 혹은 리팩토링과 같은 작은 업데이트가 발생했을 때, 그리고 패치는 버그 혹은 이슈를 처리했을 때 그 버전 정보를 업데이트해주게 된다.
캐럿(^)은 패키지의 새로운 버전이 release됐을 때 마이너와 패치 버전 범위 내에서 업데이트해 주는 것을 명시한다. 예를 들어, 현재 패키지의 버전이 1.8.2라고 가정하면, 최대 1.9.9 버전까지 업데이트한다.
틸드는(~)는 패키지를 패치 버전 범위 내에서 업데이트해 준다. 가령, 현재 패키지의 버전이 1.8.2라고 가정하면, 최대 1.8.9 버전까지 업데이트한다.
버전 앞에 캐럿 혹은 틸드를 아무 것도 붙이지 않는다면, 해당 패키지는 새로운 버전이 release돼도 업데이트하지 않는다.
결론부터 말하자면, package-lock.json은 npm 프로젝트의 의존성 트리를 관리하기 위해 사용된다.
의존성 트리는 node modules에 해당되는 프로젝트의 의존성과, 의존성이 의존하고 있는 하위 의존성들과의 상관관계를 기록하고, 그에 대한 정확한 버전을 기록한다.
package.json이랑 뭐가 달라?
인터넷을 뒤져보며 가장 헤깔렸던 부분이다. "package.json에서 의존성의 버전을 이미 관리하고 있는데, package-lock.json은 왜 필요한 거지?" 하는 의문이 들었다.
지금까지 나는 npm install 명령어를 실행했을 때, package.json 파일의 의존성 리스트를 기준으로 node_modules를 추가하거나 업데이트하는 줄 알았다. 하지만 npm install은 package-lock.json에 기록된 의존성 메타데이터를 기준으로 의존성 트리를 형성하게 된다.
즉, package-lock.json은 의존성들에 대한 현재 정확한 버전을 기록하기 위한 파일이며, package.json의 dependencies는 의존성들의 업데이트 범위를 지정해주기 위해 존재한다.
npm update 명령어를 실행했을 때, package.json의 각 의존성에 지정된 버전 범위 내로 최신 release가 존재한다면, 이를 업데이트해주고 또 이를 다시 package-lock.json 파일에 현재 최신 버전을 업데이트하게 된다.
Package.json vs Package-lock.json
What is NPM's package-lock.json?