목차
패키지 매니저란?
- 패키지 매니저는 패키지를 다루는 작업을 편리하고 안전하게 수행하기 위해 사용되는 툴이다.
- 패키지 매니저의 도움으로 패키지를 설치, 제거, 업데이트 및 업그레이드하고, 프로젝트 설정을 구성하고, 스크립트를 실행하는 등의 작업을 수행할 수 있다.
- 대부분의 자바스크립트 패키지 매니저는 Node.js 실행 환경(runtime)에서 돌아가며 package.json이라는 파일에 프로젝트가 의존하고 있는 패키지 목록을 명시한다.
패키지란?
- 라이브러리가 코드의 작성을 위해 사용되는 코드의 묶음이라면, 패키지는 코드의 배포를 위해 사용되는 코드의 묶음이다.
패키지 매니저가 하는일
- 패키지의 dependency 관리
- 패키지의 보안관리 ㅡ 신뢰할 수 있고(authenticity), 손상되지 않음(integrity)을 보장
- 여러 패키지를 기능에 따라 그룹으로 묶어 정리
- 패키지 압축 해제
- Software repository로부터 패키지를 찾고, 다운로드하고, 설치하고, 업데이트하는 역할
Software repository란 패키지를 저장하고 관리하는 저장소이다.
원격저장소라고 생각하면 될듯! 내가 npm install이나 yarn add를 실행하면 원격저장소에서 다운받게됨
NPM
- 자바스크립트 언어를 위한 패키지 관리자로, Node.js의 기본 패키지 관리자이다.
1. NPM은 파일시스템을 이용해서 의존성을 관리한다.
파일 시스템이란?
파일 시스템은 파일을 포함하도록 할당된 하드 디스크의 한 구역입니다.
디렉토리에 파일 시스템을 마운트하여 이 구역에 액세스합니다.
파일 시스템이 마운트된 후에는 이 구역이 일반 디렉토리와 똑같습니다.
- 만약 require()를 사용해서 특정 패키지를 불러올때, Node.js에서 제공하는 require.resolve() 함수를 사용한다.
- require.resolve() 함수는 NPM이 검색하는 디렉토리를 반환하는데, 그 예시를보면 아래와 같다.
- 쉽게 말하자면 js파일에서 require나 import를 사용해서 모듈을 읽어오려고 하면, node는 타겟 모듈을 내 컴퓨터의 디렉토리에서 찾는다
- npm은 패키지를 찾지못하면 상위 디렉터리의 node_modules 폴더를 계속 검색하기때문에, 패키지를 찾을때까지 I/O호출이 반복된다.
- 그래서 의존성 검색이 때로는 비효율적으로 동작할지도 모른다.
2. 어떤 의존성을 찾을 수 있는지는 해당 패키지의 디렉터리 환경에 따라 달라질 수 있다.
- 디렉토리가 어떤 node_modules를 포함하고있는지에 따라 의존성을 불러 올 수 있기도 하고, 없기도한다. 그리고, 다른버전의 의존성을 잘못 불러올 수 있는 여지도 존재한다
예시
npm install moment-timezone
위 코드를 실행해보면, node_module에는 moment-timezone과 moment가 추가가 된다.
왜냐하면 moment-timezone에서 moment를 사용하기때문에 같이 설치가 되는것이다.
그리고 나서는, js 파일에서 moment를 require해서 사용해보면 잘 실행이된다!
나는 moment를 따로 설치하지않았음에도 사용할 수 있게되었다.
왜냐하면 node의 기본 패키지매니저는 npm이고, npm은 파일시스템을 이용해서 의존성을 관리하기때문에 node_module에 있는 moment에 접근이 가능하게되는것이다.
이건 아래에 나오는 유령 의존성문제에서 다시 다룬다.
Yarn v1(classic)
- 페이스북에서 만든 자바스크립트 패키지 매니저
- yarn.lock 파일 생성
- 프로젝트마다 다른 버전의 yarn 사용할 수 있다.
- yarn은 성능 및 보안문제와같은 npm의 단점을 보완하기위해 나왔다. (그런데 지금은 npm이 각성해서 두개의 격차가 줄었다고한다.)
- 속도(performance)
- Yarn은 패키지를 병렬로 설치하는데 이것이 npm보다 빠른 이유 중 하나이다.
- npm에서 이러한 작업은 패키지별로 순차적으로 실행된다. 즉, 다음 작업으로 이동하기 전에 패키지가 완전히 설치될 때까지 대기하게되는것이다.
- 안정성(stability)
- yarn.lock은 모든 디바이스에 같은 패키지를 설치하는 것을 보장하기 때문에 버전의 차이로 인해 생기는 버그를 방지해줄 수 있다.
- yarn이 출시되는 시점엔 npm은 lock파일이 없었음!
- 지금은 npm도 lock파일을 생성해준다. 아래의 history를 보면 npm은 version 5부터 package-lock.json을 제공했다는것을 알 수 있다.
그럼 yarn.lock파일과 package-lock.json은 어떤 역할을 하는걸까?
우선 package.json를 먼저 보자!
package.json
- 우리가 어떤 패키지(오픈소스)를 사용하는지, 어떤 버전을 사용하는지 등을 기록함으로써 어느 곳에서도 동일한 개발 환경을 구축할 수 있게 해준다.
- package.json을 보면 의존성과 그 의존성에대한 버전정보가 적혀있는것을 볼수있는데, 자세히 들여다보면 특정 버전이 적혀있는것이 아니라, 버전 범위가 적혀있다는것을 확인할 수 있다. (carot^, tilde~)
- ~version은 patch버전까지의 업데이트를 허용한다.
- ^version은 minor버전까지의 업데이트를 허용한다.
여기서 발생할 수 있는 문제를 간단하게 얘기해보자면,
만약 A와 B가 하나의 프로젝트로 협업을 하고있을때, 두사람은 동일한 package.json을 공유하고있을것이다.
만약 A가 먼저 의존성들을 설치하고나서, B가 그 이후시점에 의존성들을 설치했다고 하자
그렇게되면 A가 의존성을 설치한시점과, B가 의존성을 설치한 시점 사이에 몇몇개의 라이브러리의 버전이 올라갔다면
두 사람의 node_module에는 동일모듈에대해 다른버전의 코드가 들어있을수도 있게 되는것이다.
그럼 두사람은 동일한 환경에서 작업을한다고 할 수 없게 되는것이다.
(근데 이건 나혼자 예측한거라 잘못된 예시일수도있음. 잘못되었다면 댓글남겨주세영~)
그래서 이러한 문제를 lock파일을 사용해 해결하고자 한것이다.
그럼 아래의 설명이 이해가되시겠찌용?
- 패키지 버전 불일치를 방지하기 위해 설치된 정확한 버전이 패키지 잠금 파일에 고정되어 있다.
- 모듈이 추가될 때마다 npm과 Yarn은 각각 package-lock.json및 yarn.lock파일을 생성(또는 업데이트)한다.
- 허용된 버전 범위를 유지하면서 다른 시스템이 정확히 동일한 패키지를 설치하도록 보장할 수 있다.
유령 의존성 (Phantom Dependency) : node_modules
- npm 및 yarn v1은 중복해서 설치되는 node_moduels를 보완하기 위해서, 의존성들에 대해 hoisting 기법을 사용한다. (hoisting은 코드가 실행하기 전 변수선언/함수선언 이 해당 스코프의 최상단으로 끌어 올려진 것 같은 현상을 말한다.)
- 왼쪽 트리에서 A와 B는 두번 설치되므로 디스크공간을 낭비한다. 그래서 npm과 yarn 1은 디스크공간을 아끼기위해 원래 트리모양을 오른쪽트리처럼 바꾼다.
아까 위에서 언급했었던 moment-timezone을 설치하는 예시를 생각해보면,
moment-timezone은 A(1.0)이고 moment를 B(1.0)이라고 생각하면 이해가 쉬울것이다.
moment는 moment-timezone 폴더안에 있는게아니라, moment-timezone과 같은 레벨로 추가된다.
- 그래서 직접적으로 의존성이없는 라이브러리를 import해서 사용할수 있게되는데, 이를 유령 의존성이라고 한다. 즉, package.json에 명시하지않은 라이브러리를 사용할수있게 되는것이다.
- package.json에 명시되지않는 라이브러리를 사용하게되면, package.json에서 dependency를 삭제/업데이트 할때 어떤 사이드이펙트가 발생할지 모른다.
- 내가 moment를 설치하지않고, moment-timezone을 설치한 다음, moment를 쓰고있었는데, moment-timezone을 지워버리면 moment도 지워지니깐 moment를 쓰고있던 곳에서는 에러가나게된다!
yarn berry는 유령 의존성문제를 Plug’n’Play 전략을 이용해서 해결한다.
yarn v2~(yarn berry)
- yarn 패키지 매니저의 2번째 버전이다.
- yarn v1과는 따로 관리된다.
- 아래의 이미지는 yarn v1 github에서 캡쳐한 이미지인데, yarn v1은 더이상 유지보수가 이루어지지않는다.
yarn berry의 특징을 살펴보자
Plug’n’Play (PnP)
- Yarn berry는 node_modules를 생성하지 않는다. (옵션 설정에 따라서 node_modules를 설치하도록 할 수 있음)
- .yarn/cache 폴더에 패키지, 라이브러리를 zip파일로 저장하고, .pnp.cjs파일에 의존성 정보를 찾을 수 있는 정보가 기록된다.
위 이미지에서 볼 수 있듯이, 패키지 이름과 패키지 버전정보를 가진 이름의 zip파일로 추가가된것을 알 수 있다.
위의 예시는 yarn add react를 실행한 결과이고 react가 loose-envify를 사용하기때문에 같이 설치된것을 확인할 수 있다. (js-tokens는 loose-envify에서 사용하는거임)
- .pnp.js: dependencies들을 찾을 수 있는 정보가 기록된다. 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디에 위치하는지를 바로 알 수 있다.
- 패키지 이름 + 패키지 버전 + 패키지의 실제 위치 + 패키지의 종속성을 담은 맵 형태의 데이터들의 나열이다.
- 특정 패키지와 의존성에 대한 정보가 필요할 때 바로 알수있다.
- .pnp.js를 활용하여 기존 방식보다 훨씬 빠르게 애플리케이션의 패키지들을 설치 및 실행할 수 있다.
- 모든 패키지들이 명시되어있기때문에, 각 패키지들도 최상단으로 끌어올려져서 완전히 플랫한 구조를 갖는다.
- 그리고 js파일에서 내가 설치하지않은 모듈을 require하거나 import하고나서 yarn node를 실행해보면 해당 모듈을 설치하지않았다고 에러가 뜬다.
- 모듈을 import 할 때 파일시스템이 아닌, pnp.js에서 정보를 읽기때문에 그런것같다! pnp.js에는 내가 직접 설치한 모듈정보도 나열되어있음
ZipFS (Zip FileSystem)
- Yarn PnP시스템에서 의존성은 Zip으로 관리된다.
- Zip으로 의존성을 관리했을때의 장점
- 각 패키지는 버전마다 하나의 Zip 아카이브만을 가지기 때문에 중복해서 설치되지 않는다.
- 각 Zip 아카이브가 압축되어 있기때문에, 스토리지 용량을 아낄수있다.
Zero-install
- 그러니깐 이제 node_modules를 .gitignore에 추가할 필요가없게되는거임!(왜냐하면 pnp전략을 사용하게되면 node_modules자체가 생성되지않으니깐!)
- 그리고 zip파일의 용량이 작기때문에 .yarn/cache 폴더 자체를 github이나 gitlab같은 코드 원격 저장소에 그대로 올려도 된다.
- 그래서 내가 올린 코드를 다른사람이 로컬에 받아서 실행할때 npm install이나 yarn add 를 따로 할 필요없이, 바로 실행이 가능하다.
정리하자면
- 용량과 파일의 숫자가 적기때문에 Yarn Berry를 사용하면 의존성을 git으로 관리할 수 있다.
- 다른 환경에서도 별도의 yarn install을 통한 설치가 필요없도록 repository에 commit을 하도록 요구함.
- Zero-Installs을 사용하면 반복적으로 의존성 설치 작업이 이루어지는 CI 단계에서 시간을 단축할 수 있다.
그런데 찾다보니 pnpm이라는것도있넹..?
이것도 추가해야될듯
10월안에 추가안하면 나 진짜 빵꾸똥꾸임
참고
자바스크립트 패키지 관리자의 역사 < 이 글이 정리가 꼼꼼히 되어있는듯
node_modules로부터 우리를 구원해 줄 Yarn Berry