Yarn?

/:frontend

Yarn?

JavaScript로 프로젝트를 하면 패키지 관리는 필수적이다. 패키지를 관리하기 위해 툴을 사용하는데, 보통 가장 먼저 접하게 되는 것은 NPM이다. Node.js의 공식 패키지 관리자인 만큼, Node.js를 설치하면 같이 설치돼서 많이 사용하는 것 같다.

비공식 패키지 관리자로는 대표적으로 2016년에 페이스북이 개발한 Yarn이 있다. 현재는 Yarn2, Yarn Berry, Yarn3.x으로 불리는데 다 똑같은 것을 의미한다. 이 글에서는 Yarn2로 지칭하겠다.

Yarn2가 등장한 이유는 당연히 NPM에 뭔가 문제가 있기 때문이다. 뭐가 문제인지, Yarn2는 어떻게 해결했는지 한 번 알아보자.

node_modules

NPM은 node_modules 디렉토리로 모든 패키지를 관리한다.

패키지를 많이 설치한 프로젝트를 실수로 깃허브에 올려본적 있는 사람을 알텐데, node_modules 디렉토리 구조는 엄청 큰 공간을 차지한다. 용량을 많이 차지할 뿐만 아니라 node_modules 디렉토리 구조를 만들기 위해서는 많은 I/O 작업이 필요하다. 미리 설치된 것이 있어도 모든 디렉토리의 내용을 비교해봐야 한다. 그리고 파일 시스템을 통해 중첩된 node_modules 디렉토리를 찾아 올라가면서 패키지를 하기 때문에 패키지를 찾는 과정이 엄청나게 비효율적이다.

Yarn1에서도 이 방식으로 패키지를 관리했었다. NPM과 Yarn1에서는 중복설치되는 패키지를 아끼기 위해 끌어올리는(hoisting) 기법을 사용한다.

의존성 트리가 위의 모습이라면, A(1,0)과 B(1.0)이 두 번 설치되니까 디렉토리의 트리 모양을 오른쪽으로 바꿔준다. 하지만 이렇게 되면서 package-1에서는 원래 접근할 수 없었던 B(1.0)을 불러올 수 있게된다. 호이스팅이 일어나서 직접 의존하지 않는 모듈을 접근할 수 있는 현상을 유령 의존성(phantom dependency)라고 한다. 원하지도 않던 의존성이 생겼으니 당연히 패키지 관리에 혼란이 일어날 수 있다.

Plug’n’Play(PnP)

위의 문제를 Yarn2에서는 PnP를 이용해 해결한다.

Yarn2는 node_modules를 생성하지 않고, .yarn/cache 폴더에 의존성의 정보를 저장하고 .pnp.cjs 파일에 의존성을 찾을 수 있는 정보를 기록한다. .pnp.cjs 파일을 이용하면 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디 위치하는지 바로 알 수 있다.

패키지 매니저는 의존성 트리를 생성할 때 이미 모든 의존성에 대한 정보를 알고 있으니, 패키지를 찾는 작업을 Node에게 맡기지 않고 정보를 제공하는 것이다. .pnp.cjs 파일에는 패키지들에 대한 lookup 테이블로 볼 수 있다.

이제 Yarn2는 install시 단 하나의 파일만 생성하면 된다. 설치 시간의 관건이 디스크 I/O가 아닌 프로젝트에서 사용하는 패키지의 개수가 되는 것이다.

ZipFS

Yarn PnP의 의존성은 .yarn/cache에 zip 압축 파일로 관리된다.

Zip 파일로 의존성을 관리하면 디렉토리 구조를 생성할 필요가 없기 때문에 설치가 빨라지고, 각 패키지는 버전마다 하나의 파일을 가지기 때문에 중복해서 설치되지 않는다. 또한 압축파일인 만큼 디스크 용량도 크게 아낄 수 있다.

용량이 작기 때문에 이제 의존성도 git으로 관리할 수 있다. Pull만 받으면 install 과정없이 바로 같은 환경을 세팅할 수 있는 것이다. Yarn2에서 의존성을 버전 관리에 포함하는 것을 Zero-Install이라고 한다.

마치며

이번 프로젝트에 Yarn을 사용해보려고 NPM과 차이점을 찾아봤다. 큰 차이점이 없을 줄 알았는데, 패키지를 관리하는 방식이 아예 달라서 신기했다. .pnp.cjs 파일로 패키지를 찾는 것이 마치 DB의 인덱싱이나 운영체제의 page table을 적용한 것 같았다. 결국 모두 기본기의 응용인 것 같다.

참조F