Yarn berry로 모노레포 구성하기

Minboy·2023년 11월 12일
32

별글 프로젝트

목록 보기
1/3
post-thumbnail

서론

🌟 별 하나에 글 하나 프로젝트

네이버 부스트캠프 그룹 프로젝트를 시작하면서, 어떤 패키지 매니저와 빌드툴을 사용할지 팀원들과 많은 고민 끝에 Vite와 Yarn berry를 사용하기로 결정했다. 이번 글에서는 왜 Yarn berry를 사용하게 되었는지와 Yarn berry로 프로젝트를 구성한 과정을 설명하려고 한다.

Package

이미지 출처 - pixabay 무료이미지

패키지에 대해 설명하라고 하면, 가져다 쓸 수 있는 코드 뭉치 정도로만 알고 있었는데, 이번 기회에 자세하게 그 정의를 알아보고 싶었다.

Module(모듈)

옛날 옛적에는, 자바스크립트로 큰 프로그램을 만들지 않았고 그 크기는 상당히 작았다. 하지만 웹의 생태계가 엄청난 성장을 이루면서 자바스크립트로 대규모 프로그램을 만드는 경우도 많아졌고, 그에 따라 코드를 분리해야할 필요성이 생겨났다.

이러한 배경속에 등장한 것이 모듈이다. export 키워드를 통해 변수, 함수를 모듈로 내보내 동일한 파일이 아닌 다른 파일에서도 사용할 수 있고, requireimport 키워드로 모듈을 가져와 사용할 수 있다. 모듈은 이렇게 분리되어 사용될 수 있는 변수나 함수를 말한다.

JavaScript modules - JavaScript | MDN

Package(패키지)

패키지는 모듈의 집합체이다. 특정 기능을 구현하는데 필요한 모듈들을 묶어놓은 모음집인 것이다. 만약 그래프를 그리고 싶다면 그래프 관련 패키지를 가져와서 사용하면 되고, 서버와 통신을 하고 싶다면 통신 관련 패키지를 가져와 사용하면 내가 직접 기능들을 하나하나 구현하지 않아도 쉽게 서비스를 만들어 낼 수 있다.

Package Manager

현대의 웹 프로젝트에서는 적지 않은 수의 패키지들을 사용한다. 많은 패키지들은 내부적으로 다른 패키지들을 사용하고 있는 경우들이 있는데, 여러 패키지들을 함께 사용하면서 패키지들 간의 의존성들이 복잡하게 꼬여 해결하기 어려운 상황이 발생하게된다. 발생할 수 있는 문제들로는 버전 충돌, 간접 의존성등 여러 문제가 있고 이러한 의존성 문제를 Dependency Hell 이라고 한다.

의존성 문제를 해결하기 위해 패키지들을 관리해주어야할 필요성이 생겨났고, 그 역할을 수행하는 것이 Package Manager, PM이다.

Why Yarn berry

패키지 매니저도 종류가 여러가지이다. 대표적으로 npm, yarn, pnpm 등등이 존재한다. 각각의 특징을 짧게 이야기 해보면

npm

이미지 출처 - WIKIPEDIA

Node.js 패키지의 공식 패키지 관리자로 가장 널리쓰이는 패키지 매니저이다. 쉽게 사용할 수 있고, 이용자가 많아 트러블 슈팅에도 용이하다.

Yarn

이미지 출처 - Yarn repository

병렬 패키지 설치 및 캐시를 활용해 npm보다는 빠른 속도로 의존성을 관리한다. 보안 측면에서도 약간의 이점이 있다고 한다.

pnpm

이미지 출처 - pnpm 공식 사이트

심볼릭 링크 라는 개념을 이용해 의존성을 저장하고 여러 프로젝트에서 공유함으로써 중복 설치와 디스크 공간 낭비를 줄여준다. 패키지를 중복설치하지 않고 메모리를 효율적으로 관리하여 빠른 설치와 업데이트를 가능하게 한다.

위 세가지 패키지 매니저도 각각의 장점이 있는 좋은 패키지 매니저들이지만, 우리는 Yarn berry를 선택했다. 그 이유를 알아보자.

PnP(Plug’n’Play) & Zero install

기존의 PM들은 node_modules라는 디렉토리에 모든 패키지들을 저장하는 방식을 가지고 있다. 이 방식은 node_modules가 굉장히 무거워져 비효율적인 의존성 검색을 가져오는 문제를 일으킨다. 실제로 node_modules 디렉토리를 확인해보면 각각의 패키지들이 연속적으로 node_modules를 가지고 있는 것을 확인해볼 수 있는데, 많은 수의 패키지들이 서로 의존하고 있다면 디렉토리 구조가 복잡해지는 것은 당연할 것이다.

이러한 문제를 해결하기 위해 최근의 PM들은 호이스팅 등의 개념을 이용하는데, Yarn berry는 PnP 라는 방식으로 이 문제를 해결했다.

이미지 출처 - Polygon

PnP , Plug’n’Play는 하드웨어를 꽂기만 하면 별도의 사용자 조작이나 프로그램 설치 없이 바로 사용가능한 방식을 말한다.

Yarn berry는 패키지 설치 시 node_modules 를 이용하는 대신, .yarn/cache 에 패키지를 압축형태로 저장하고, .pnp.js 파일에 의존성의 위치를 기록한다.

(.pnp.js의 일부, 패키지의 이름, 버전, 위치 등의 정보가 기록되어있다.)

이름 그대로 패키지를 플러그해두는 것으로 복잡한 설정이나, 추가 작업없이 패키지를 바로 사용할 수 있게 해주는 것이다. 이 방식을 통해 Yarn berry는

  1. node_modules의 복잡한 디렉토리 구조를 생성할 필요가 없고, .pnp.js 파일에서 모든 것을 관리하므로 패키지 설치 시간이 훨씬 짧게 걸린다.
  2. 패키지를 여러 프로젝트에 별도 설치하는 대신 중앙화된 캐시에서 관리할 수 있어 디스크 공간을 절약할 수 있다.

와 같은 장점을 얻을 수 있다.


또한 Yarn berry는 패키지를 압축 형태인 zip파일의 형태로 저장하므로 공간을 훨씬 효율적으로 사용할 수 있는데, 이때문에 모든 패키지를 git 으로 관리 할 수 있다!

이 말이 무슨 말인가 하면, 프로젝트에서 사용하는 패키지들을 모두 git 으로 추적, github 에 올릴 수 있다는 말이다. 따라서 프로젝트를 clone, pull 시에 따로 패키지를 인스톨하는 과정 없이 바로 프로젝트를 시작할 수 있고, 이를 통해 CI/CD 시에 많은 시간을 절약할 수 있다. 이를 Zero install 이라고 한다.

github 레포지토리에 올라가있는 .yarn/cache 안의 패키지 압축파일의 모습이다.


우리 팀은 이러한 장점들 때문에 Yarn berry를 PM으로 선택했다. pnpm도 Yarn berry만큼의 효율성과 장점들을 갖고 있지만, 이전에 yarn을 사용해본 경험이 있기에, 커맨드와 같은 부분에서 조금이라도 익숙한 Yarn berry를 사용하는 것이 합리적이라고 생각했다.

Yarn berry로 모노레포 구성하기

우리 팀은 하나의 루트 폴더에서 client 코드와 server 코드를 디렉토리로 구분하여 관리하기로 하였다. 이렇게 하나의 저장소에서 두 개 이상의 프로젝트가 저장되는 것을 모노레포라고한다. Yarn berry와 함께 모노레포를 구성하는 과정을 살펴보자.

Yarn berry는 yarn의 다른 버전일 뿐이므로, yarn이 설치되어 있다면 따로 준비할 것은 없다. 만약 yarn이 설치되어 있지 않다면 npm i yarn -g 커맨드를 통해 yarn을 설치해주자.

monorepo라는 이름으로 디렉토리를 생성하고 이동해주었다. 여기서 프로젝트를 생성할 것이다.

yarn init -y 커맨드를 통해 yarn 프로젝트로 초기화 해주자. package.json 파일이 생성된다. 아직까지는 Yarn berry가 아닌 yarn classic을 사용중이다.

yarn set version berry 명령을 통해 해당 프로젝트에서 사용하는 yarn의 버전을 berry로 변경할 수 있다.

yarn -v 명령어 또는 package.json 을 확인해보면 Yarn berry를 사용중임을 확인 할 수 있다. (yarn의 3.x 버전 이상은 Yarn berry이다.)

모노레포를 구성하기 위해서는 우선 모든 프로젝트가 위치할 workspace 를 지정해야한다. packages 라는 이름으로 디렉토리를 만들어 워크스페이스로 사용하겠다.

packages 라는 이름으로 디렉토리를 생성하고, package.json“workspaces” : [ “packages/” ] 를 추가해주자. 이제 packages에 추가되는 모든 디렉토리를 워크스페이스로 인식할 것이다.

이제 packages 폴더로 이동해 프로젝트를 생성해주자. 프로젝트를 원하는 방식으로 생성해주면 된다. 우리는 vitenest를 이용한 클라이언트, 서버 프로젝트를 생성해주었다.

만약 프로젝트에서 TypeScript 를 이용한다면, 이런 오류를 만날 것이다. VSCode 환경에서 Yarn berry를 사용하려면 몇가지 설정이 추가적으로 필요하다.

  1. yarn plugin import typescript

    타입스크립트 플러그인을 가져와주자. 이는 크게 상관은 없지만, 자체 types가 없는 패키지를 추가할 때 자동으로 @types/ 패키지를 package.json에 종속성으로 추가해준다.

  2. VSCode 익스텐션 중 ZipFS 설치

압축으로 관리되는 패키지를 살펴보기 위해 해당 확장 프로그램이 필요하다.

  1. yarn dlx @yarnpkg/sdks vscode

    위 명령어를 통해 sdk를 설치해주어야한다.


함께 진행해보자.

먼저 yarn add typescript -D 명령어를 통해 타입스크립트를 추가해준다.

이후 yarn install 명령어를 통해 아까 생성한 프로젝트들에서 필요로 했던 의존성들을 모두 설치해준다.

다음으로 yarn dlx @yarnpkg/sdks vscode 명령어를 통해 sdk를 설치하면, 다음과 같은 알럿창이 표시된다.

타입스크립트 버전에 관한 내용인데, Allow를 눌러주자. 만약 이러한 알럿창이 표시 되지 않았거나 Dismiss또는 X를 잘못눌러 꺼버렸다면, cmd + shift + p 를 통해 명령 팔레트를 표시하고 TypeScript: Select TypeScript Version… 을 선택하자.

이후 Use Workspace Version을 선택해주면 된다.

위 과정을 모두 잘 따라왔다면, 더이상 오류는 발생하지 않고 프로젝트도 잘 실행될 것이다. 추가로 워크스페이스 명령어를 실행하는 방법을 알아보자.

workspace

워크스페이스(위의 예시에서는 packages 디렉토리) 안의 각각의 프로젝트에 존재하는 스크립트를 루트 디렉토리에서도 실행할 수 있다. 각각의 프로젝트 이름을 확인 후, (프로젝트 생성 시에 입력한다. 만약 이름이 기억나지 않는다면 각 프로젝트의 package.json을 확인해보자.

name 필드에 적혀있는 것이 프로젝트의 이름이다.)

루트 폴더에서 yarn workspace 프로젝트이름 스크립트이름 명령어를 통해 각 프로젝트의 스크립트를 실행할 수 있다. 현재 우리 프로젝트에는 client 프로젝트와 server 프로젝트가 존재하고, 각각 dev 스크립트와 start 스크립트가 존재한다. 루트 디렉토리에서 실행시켜 보자.

vitenest 가 각각 잘 작동하는 모습이다. 이렇게 루트 디렉토리에서 워크스페이스의 각 스크립트를 실행하거나, 또는 해당 프로젝트 디렉토리로 이동해서 일반적인 방식으로 (yarn vite 또는 yarn start) 스크립트를 실행할 수 있다.

이건 내가 원한 Zero install이 아니야..

이렇게 모든 프로젝트 설정을 마쳤다고 생각했지만, 무언가 이상한 점을 발견했다. Yarn berry에 대해 학습하면서 .pnp.js 파일을 살펴보았는데,

packageLocation 필드가 좀 이상한 것을 발견했다. 패키지 저장 위치가 저장소 내부가 아닌, 내 로컬머신에 설치되고 가져오고 있는 것이었다. 또한 path가 상대경로로 되어있었는데, 여기서 의문이 들었다.

  1. 만약 내가 이 프로젝트를 다른사람과 공유할 때, 그 사람이 나와 같은 위치가 아닌 다른 위치에 프로젝트를 clone하면 저 상대경로는 틀린 경로가 될 것이다.
  2. 저 경로의 위치로 가보면,

이렇게 내 로컬 머신 안의 .yarn/berry/cahce라는 곳이 나오는데, 이 폴더는 git 으로 관리되고 있지 않으므로 만약 상대방의 머신에 패키지가 설치되어 있지 않다면 패키지 인스톨 과정을 거쳐야한다.

이 방식은 우리가 원했던 Zero install이 아니다! 분명히 문제가 될 것같다는 직감속에 실제로 github 레포지토리를 생성, 저장소를 올리고 내려받아 실행시켜보았다.

예상했던대로 의존성을 하나도 찾지 못하는 문제가 발생했다. 이 문제를 어떻게 해결해야하나 고민 끝에, 한가지 결론에 도달했다.

우리가 원했던 Zero install을 이루려면, 당연하게도 저 .yarn/cache 디렉토리는 프로젝트 내에 위치하여 git 으로 관리되어야만 한다. 현재의 방식은 로컬 머신의 글로벌 캐쉬 디렉토리에 패키지들이 저장되는 방식이라 git 으로 관리되지 못하는 것이다.

우리 프로젝트 내의 .yarn 디렉토리에는 cache 폴더가 존재하지 않는다!

해결책

어떻게 해야 프로젝트 내에 .yarn/cache 디렉토리를 생성하고 그 위치에 패키지들을 저장할 수 있을지 찾아보았다.

열심히 자료를 찾아보던중, 하나의 스택오버플로우 글에서 힌트를 얻었다.

Should i push the .yarn/cache folder to Github - Yarn2

.yarnrc.yml 파일에서 yarn과 관련된 설정들을 명시할 수 있는데, 변경할 수 있는 필드값들 중 enableGlobalCache: true 라는 값이 있다는 것을 위 글에서 알게되었다.

우리는 글로벌 캐시에서 의존성을 관리하는 것이 아닌, 프로젝트 내에서 의존성 관리를 하고싶으므로 enableGlobalCache 값을 false로 주면 되지 않을까? 라는 생각 아래에 그대로 시도해보았다.

이후 앞서 했었던 과정들을 동일하게 수행하면

우리가 원했던 대로 프로젝트 내의 .yarn/cache 디렉토리가 생성되고, 이곳에 패키지들의 정보가 저장된다.

드디어 git 을 통해 이것들을 추적할 수 있고, github 에 레포지토리를 생성해 push 후 clone을 통해 테스트해봐도 따로 추가적인 인스톨이나, 설정 필요없이 바로 프로젝트를 시작할 수 있었다.

대부분의 자료에서 이 옵션에 대한 설명이 없는데, 아마 디폴트 값이 이전에는 false였다가 최근들어 true로 바뀐것이 아닐까..? 하는 생각을 해본다.
확실히 아직 사용하는 사람들이 많지 않다보니 자료가 많이 부족하다는 단점은 존재하는 것 같다. 하지만 하나씩 헤쳐나가다보면 지식도 늘고 뿌듯함도 얻을 수 있다 😎

설정 끝!

References

당신의 PM 선택에 도움이 되는 글
동료 캠퍼분의 글인데 정말 큰 도움이 되었다! 너무 좋은 글이라 여러번 읽었다.
yarn 공식 홈페이지
Naver D2 - 모던 프론트엔드 프로젝트 구성 기법 - 모노레포 도구 편
Yarn berry (yarn pnp) 환경으로 React + Typescript 프로젝트 세팅하기

profile
펭귄이 세상을 지배할겁니다.

8개의 댓글

comment-user-thumbnail
2023년 11월 12일

제 글이 레퍼런스가 되었다니 영광입니다!!
아무래도 yarn berry의 큰 장점중 하나는 플러그인 친화적인 기능일수 있을거 같은데요!
토스 기술 블로그의 https://toss.tech/article/node-modules-and-yarn-berry 요 글과
https://github.com/toss/yarn-plugin-workspace-since 이 두개의 글도 한번 봐주시면 좋을거 같습니다!

1개의 답글
comment-user-thumbnail
2023년 11월 12일

멀고도 험난하네요

1개의 답글
comment-user-thumbnail
2023년 11월 12일

역시 내 최대 업적 ㅎ 너무 디테일한 설명 감사합니다

1개의 답글
comment-user-thumbnail
2023년 11월 12일

캬 프로젝트도 기대가 됩니다!! 😄🐰

1개의 답글

관련 채용 정보