Explanation

내 마음을 불편하게 만드는 package-lock.json

원래 의도와는 정반대로 Lock 파일은 왠지 내 마음을 불편하게 한다. 마음의 평화를 위해 몇몇 포인트를 정리했다.

내 마음을 불편하게 만드는 package-lock.json

package-lock.json 파일 때문에 고민하는 남자의 사진

Lock 파일은, 친절하게도, JavaScript 패키지 매니저가 기본적으로 사용하도록 되어있는 데다가, 그 목적이 개발자가 느끼기에는 너무나도 직관적이라, 큰 거부감 없이 받아들이고 사용하게 되는 경향이 있다. 하지만 이런 친절함이나 직관성과는 별개로, 올바르게 사용하고 있는 건지에 대한 확신을 얻기가 어렵다. 이 글은 신경 쓰이는 Lock 파일의 몇몇 포인트를 정리한 것이다.

The assumption of an absolute determinism is the essential foundation of every scientific enquiry.

Max Planck

Lock 파일

pacakge-lock.json 파일은 패키지 매니저로 관리되는 디펜던시를 모든 환경에서 정확히 동일하게 사용하도록 하며, npm install 실행 시에 자동으로 만들어진다. 이 파일에는 디펜던시를 어떻게 해소할지에 대한 자세한 정보가 들어있기 때문에 다른 시간대, 다른 환경에서도 필요한 디펜던시 트리(Tree)를 정확하게 결정할 수 있다.

여기서 디펜던시 트리를 정확히 결정한다는 말은 우리가 원하는 node_modules/ 디렉토리의 모양이 있고, npm install이 언제나 똑같은 node_modules/ 디렉토리를 만들어낸다는 뜻이다. 예를 들면, 우리는 npm install이 나와 동료들의 컴퓨터, CI 서버에 똑같은 버전의 React를 설치하기를 기대하지, 여러 환경들에 제각각 다른 버전의 React를 설치하는 것을 원하지 않는다.

package.json 만으로는 안 되는 이유

package.json에만 의존하는 것은 다소 나이브한 접근이다. package.json은 디펜던시의 버전을 표기할 때 SemVer를 사용하는데, 정확한 버전 뿐만 아니라 범위를 명시하는 것도 가능하다. ^16.9.0 식으로 사용되는 ^npm install <package> 명령어의 기본 설정으로 이후 버전을 허용하되 메이저 버전은 그대로 유지하라는 신호이다. 이렇게 보면 Lock 파일 없이 package.json 만으로도 디펜던시 트리를 결정하는 것도 문제가 없어 보인다. 메이저 버전이 달라지지 않도록 할 수 있다면, 즉 브레이킹 체인지를 들여오는 것만 피할 수 있다면, 각각의 환경들이 제각각 다른 버전을 사용하더라도 문제가 없어야 하기 때문이다.

하지만 이것은 어디까지나 이론상의 일일 뿐이고, 현실에서는 마이너나 패치 업데이트가 우리 프로그램의 동작에 실제로 영향을 미치는 경우가 허다하다. 애초에 ^이나 ~ 같은 접두사 없이 고정된 버전을 설치하는 것을 생각해 볼 수도 있겠으나 이 방식으로는 직접적으로 의존하는 패키지의 버전만 고정할 수 있을 뿐, 간접적으로 의존하는 것들까지 고려한 디펜던시 트리를 관리할 수는 없다.

pacakge-lock.json이 변경되는 조건

아마도 이게 마음을 불편하게 하는 주범인 듯 한데, Lock 파일이 모든 걸 결정해야 맞는 것 같은데 그렇지 않아 보이는 때도 있다는 것이다. 커맨드라인 명령어를 사용해서 패키지를 추가하거나 제거하는 등의 경우 이외에도 Git 변경내역에 Lock 파일이 보일 때가 간혹 있는데, 바로 package-lock.json과 package.json이 제대로 동기화되지 않았을 때 npm install이 실행된 경우이다.

React를 사용하는 상황을 가정해보면 이렇다. npm install react로 최신(latest) 버전의 React를 설치한다. 참고로 --save 플래그는 더이상 붙이지 않아도 된다.

  • package.json – react@^16.9.0 엔트리가 dependencies 필드에 추가된다.
  • package-lock.json – React의 버전이 16.9.0으로 고정되고, React의 디펜던시와 그 아래의 모든 디펜던시 버전 역시 고정된다.

이제 이후에 실행되는 npm install의 결과는 언제나 16.9.0 버전의 React가 포함된 똑같은 node_modules/ 디렉토리이고, package-lock.json 파일이 바뀔 일도 없다. 하지만 package.json의 내용이 package-lock.json과 어긋나도록 변경되면 얘기가 달라진다.

package.json 파일을 열어 React의 버전을 ^16.9.0에서 16.8.0으로 바꾼다. 이제 package.json은 16.8.0, package-lock.json은 16.9.0 버전의 React를 명시하고 있다. 이렇게 두 파일이 상반되는 패키지 정보를 갖고 있는 경우에는 package.json의 내용이 우선시된다. npm install을 실행하면 package-lock.json 파일에 16.8.0이 React의 고정된 버전으로 명시되고, 해당 버전이 node_modules/ 디렉토리에 설치된다.

package.json의 내용이 변경되었더라도 package-lock.json이 여전히 그에 부합한다면 아무 일도 일어나지 않는다. 만일 앞에서 package.json의 React의 버전을 16.8.0이 아니라 ^16.8.0으로 바꿨다면, 이 때 package.json은 package-lock.json과 상충되지 않는다. package-lock.json에는 React의 버전이 16.9.0으로 명시되어 있고, 이는 package.json에 명시된 ^16.8.0 범위를 벗어나지 않기 때문이다. 따라서 npm install은 아무런 효력이 없다.

npm ci

npm ci는 CI 환경이나 디펜던시를 처음 설치할 때 npm install 대신 사용할 수 있는 명령어로, 기존의 node_modules/ 디렉토리가 있다면 없애고 새로 설치한다. pacakge-lock.json의 내용이 package.json의 내용과 상충된 경우, 이 명령어는 Lock 파일을 변경하고 패키지를 설치하는 대신 에러를 내고 작업을 중단해버린다. 즉, 이 명령어는 npm install과 달리 package-lock.json 파일을 변경하지 않는다.

병합 충돌 시 지우면 안 되는 이유

package-lock.json 때문에 코드 병합 충돌이 생겼을 때 상황을 모면하는 가장 손쉬운 방법은 아마도 파일을 아예 지워버린 후 npm install로 새 package-lock.json을 생성하는 것이다. 하지만 이 경우, 마지막으로 package-lock.json이 변경된 시점으로부터 경과된 시간에 따라 정도는 다르겠지만, 디펜던시 트리의 많은 부분을 의도치 않게 바꿔버릴 수 있다. 때문에 예측가능한 방식으로 디펜던시를 관리하려는 Lock 파일의 애초 목적에 반하는 부분이 있다.

yarn.lock

Yarn의 lock 파일은 yarn.lock으로 불린다. package-lock.json과 달리 JSON이 아닌 자체 형식을 사용하는데, 모든 패키지들의 목록을 일렬로 나열할 뿐 트리 형태로 패키지들의 의존 관계를 표현하지 않는다. 다시 말해, package-lock.json은 그 자체로 이미 디펜던시 트리를 표현하도록 만들어진 아티팩트인 반면, yarn.lock은 yarn install이 실행될 때 package.json의 정보를 참조하여 트리를 결정한다.

TL;DR

  • package.json이 있는 상태에서 npm install을 실행하면 package-lock.json이 생성된다.
  • npm install을 다시 실행했을 때, package.json에 명시된 버전을 만족하는 새로운 버전이 있어도 업데이트되지 않는다.
  • 직접 수정한 package.json의 내용이 package-lock.json의 내용과 어긋나는 경우, package.json의 내용에 맞게 pacakge-lock.json이 변경된다.
  • CI 환경에서는 package-lock.json을 업데이트하지 않는 npm ci 명령어를 사용할 수 있다.
  • Lock 파일을 삭제 후 다시 생성하는 일은 없어야 한다.
  • yarn.lock 파일은 package-lock.json과 달리 그 자체로 디펜던시 트리를 결정할 수 없다.

참고

Join the Newsletter

I will send you only good things.

Your thoughts? Please leave a reply.