package.json에서 ^와 ~ 차이
package.json을 열어보면 의존성 버전 앞에 ^나 ~가 붙어 있는 경우가 많다.
예를 들어 ^1.2.3이나 ~1.2.3 같은 형태다.
처음 보면 정확히 1.2.3을 설치한다는 뜻인지, 아니면 다른 버전도 허용한다는 뜻인지 헷갈린다.
핵심은 버전 범위다.^와 ~는 정확히 한 버전만 고정하는 기호가 아니라, npm이 설치해도 되는 버전의 범위를 표현하는 기호다.
semantic versioning의 기본
npm 패키지 버전은 보통 semantic versioning 형식을 따른다.
형식은 MAJOR.MINOR.PATCH다.
1.2.3
│ │ └─ patch
│ └─── minor
└───── major일반적으로 patch는 버그 수정, minor는 하위 호환되는 기능 추가, major는 호환되지 않는 변경을 의미한다.
물론 모든 패키지가 이 약속을 완벽하게 지키는 것은 아니지만, npm의 버전 범위는 이 약속을 기준으로 이해하면 쉽다.
1.2.3처럼 아무 기호가 없으면 정확히 그 버전을 의미한다.
반면 ^1.2.3이나 ~1.2.3은 특정 범위 안에서 더 높은 버전을 허용한다.
틸드 범위
~는 tilde range라고 부른다.~1.2.3은 보통 patch 업데이트를 허용한다.
~1.2.3 => >=1.2.3 <1.3.0즉 1.2.4, 1.2.9 같은 버전은 가능하지만, 1.3.0은 범위 밖이다.
minor 버전이 올라가는 것은 허용하지 않는다고 보면 된다.
{
"dependencies": {
"some-package": "~1.2.3"
}
}이 설정은 1.2.x 안에서만 움직이게 한다.
패키지의 minor 업데이트도 조심스럽게 받고 싶다면 ~가 ^보다 보수적이다.
캐럿 범위
^는 caret range라고 부른다.^1.2.3은 보통 minor와 patch 업데이트를 허용한다.
^1.2.3 => >=1.2.3 <2.0.0즉 1.2.4, 1.3.0, 1.9.9 같은 버전은 가능하지만, 2.0.0은 범위 밖이다.
major 버전이 바뀌면 호환되지 않는 변경일 수 있다고 보기 때문이다.
{
"dependencies": {
"some-package": "^1.2.3"
}
}대부분의 npm 패키지는 기본 설치 시 ^ 범위로 저장되는 경우가 많다.
패키지가 semantic versioning을 잘 지킨다면, ^는 버그 수정과 하위 호환 기능 추가를 자연스럽게 받아오기 좋은 기본값이다.
0.x 버전의 예외
^를 이해할 때 가장 헷갈리는 부분은 0.x 버전이다.
semantic versioning에서 0.y.z는 초기 개발 단계로 취급된다.
공식 semver 문서도 0.y.z에서는 어떤 변경이든 일어날 수 있고, public API가 안정적이라고 보지 않는다.
그래서 caret range도 0.x에서는 더 조심스럽게 동작한다.
^0.2.3 => >=0.2.3 <0.3.0
^0.0.3 => >=0.0.3 <0.0.4^1.2.3은 2.0.0 전까지 허용하지만, ^0.2.3은 0.3.0 전까지만 허용한다.^0.0.3은 사실상 patch 하나 안에서만 움직인다.
이 차이는 npm이 이상하게 동작하는 것이 아니다.0.x에서는 minor 변경도 깨지는 변경일 수 있다고 보는 관례를 반영한 것이다.
package-lock.json과의 관계
package.json은 버전 범위를 저장한다.package-lock.json은 실제로 설치된 정확한 버전을 저장한다.
예를 들어 package.json에는 아래처럼 적혀 있을 수 있다.
{
"dependencies": {
"some-package": "^1.2.3"
}
}하지만 package-lock.json에는 실제로 설치된 1.4.2 같은 버전이 기록될 수 있다.1.4.2가 >=1.2.3 <2.0.0 범위 안에 들어가기 때문이다.
그래서 협업에서는 package-lock.json을 함께 커밋하는 것이 중요하다.package.json만 있으면 범위 안에서 다른 버전이 설치될 수 있고, package-lock.json이 있으면 같은 의존성 트리를 더 안정적으로 재현할 수 있다.
lock 파일의 역할은 package-lock.json 파일의 역할에 따로 정리해두었다.
CI에서는 lock 파일 기준으로 설치하는 npm ci를 자주 사용한다.
이 흐름은 npm ci란?과도 연결된다.
고정 버전이 필요한 경우
항상 ^가 정답은 아니다.
재현성이 더 중요하거나, 작은 업데이트에도 깨질 가능성이 큰 패키지라면 정확한 버전을 쓰는 것이 낫다.
{
"dependencies": {
"some-package": "1.2.3"
}
}이렇게 쓰면 1.2.4도 자동으로 허용하지 않는다.
업데이트를 직접 확인하고 올리고 싶을 때는 고정 버전이 더 단순하다.
반대로 라이브러리나 앱에서 보안 패치와 버그 수정을 자연스럽게 받고 싶다면 ^나 ~가 편하다.
어느 쪽이든 중요한 것은 팀에서 의도를 알고 선택하는 것이다.
선택 기준
일반적인 애플리케이션에서는 ^를 많이 쓴다.
하위 호환을 믿고 minor와 patch 업데이트를 허용하는 방식이다.
조금 더 보수적으로 patch 업데이트만 받고 싶다면 ~를 쓴다.
minor 업데이트에서 동작이 바뀔까 걱정되는 패키지에 어울린다.
정확한 재현성이 가장 중요하다면 기호 없이 고정 버전을 쓴다.
다만 고정 버전을 쓰더라도 하위 의존성까지 모두 직접 고정되는 것은 아니므로, package-lock.json을 함께 관리해야 한다.
정리
^1.2.3은 보통 1.x 안에서 minor와 patch 업데이트를 허용한다.
범위로 쓰면 >=1.2.3 <2.0.0에 가깝다.
~1.2.3은 보통 1.2.x 안에서 patch 업데이트만 허용한다.
범위로 쓰면 >=1.2.3 <1.3.0에 가깝다.
0.x에서는 ^도 더 좁게 동작한다.^0.2.3은 0.3.0 전까지만 허용하고, ^0.0.3은 0.0.4 전까지만 허용한다.
package.json은 허용 범위를 말하고, package-lock.json은 실제 설치된 버전을 말한다.
둘을 같이 봐야 npm 의존성 버전을 제대로 이해할 수 있다.
참고 자료











