현록

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.32.0.0 전까지 허용하지만, ^0.2.30.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.30.3.0 전까지만 허용하고, ^0.0.30.0.4 전까지만 허용한다.

package.json은 허용 범위를 말하고, package-lock.json은 실제 설치된 버전을 말한다.
둘을 같이 봐야 npm 의존성 버전을 제대로 이해할 수 있다.

참고 자료

관련 포스트
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
package.json scripts와 npm run 정리 thumbnail
package.json scripts와 npm run 정리
package.json의 scripts와 npm run 명령어를 초보자 기준으로 정리합니다. npm run dev, npm start, npm test, node_modules/.bin, -- 인자 전달 방식까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci란? thumbnail
npm ci란?
npm ci는 CI, 테스트, 배포처럼 같은 의존성 트리를 반복해서 설치해야 하는 환경에 어울리는 clean install 명령어입니다. package-lock.json 조건, npm install과의 차이, .npmrc 플래그 주의점을 정리합니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI란? thumbnail
TTV와 TTI란?
TTV는 Time to View이며, 사용자가 화면을 보는 시점을 의미한다. TTI는 Time to Interact이며, 사용자가 웹에서 특정 동작을 수행할 수 있는 시점을 의미한다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.