의존성 관리하기
생성일: 2024-06-10
수정일: 2024-06-10
모노레포에서 의존성은 두 종류로 나눌 수 있다.
- 외부 의존성(External dependencies)
- npm 같은 공개 레지스트리에서 가져오는 패키지다.
- 널리 사용되는 라이브러리를 프로젝트에서 활용할 수 있게 해준다.
- 내부 의존성(Internal dependencies)
- 같은 모노레포 안에 있는 다른 패키지를 의존성으로 사용한다.
- 패키지 간에 코드를 공유하기가 훨씬 쉬워진다.
{
"dependencies": {
"next": "latest", // 외부 의존성
"@acme/ui": "*" // 내부 의존성
}
}
의존성 설치 모범사례
의존성은 사용되는 곳에 직접 설치한다
모노레포에서 의존성을 설치할 때는, 그 의존성을 실제로 사용하는 패키지에 직접 설치하는 게 좋다.
이 원칙은 외부 패키지든 내부 패키지든 동일하게 적용된다.
이렇게 하면 각각의 package.json
파일이 해당 패키지가 필요로 하는 의존성을 정확하게 명시하게 된다.
Note
몇몇 패키지 매니저(예: Yarn, pnpm)는 의존성을 설치할 때 각 패키지의 디렉토리가 아닌 프로젝트 루트에 node_modules
를 만든다.
하지만 이는 단지 패키지 매니저의 동작 방식일 뿐, 우리가 의존성을 어디에 명시해야 할지와는 별개의 문제다.
여전히 의존성은 그것을 실제로 사용하는 패키지의 package.json
에 명시하는 것이 원칙이다.
다음과 같이 여러 패키지에 동시에 의존성 설치할 수 있다.
npm install jest --workspace=web --workspace=@repo/ui --save-dev
여기에는 몇 가지 이점이 있다.
- 명확성 향상:
- 의존성이 해당 패키지의
package.json
에 등록되어 있으면 그 패키지가 어떤 의존성을 가지고 있는지 파악하기 쉽다.
- 의존성이 해당 패키지의
- 유연성 증대:
- 모노레포가 커지면, 모든 패키지가 외부 의존성을 동일한 버전으로 사용하도록 강제하기 어려워진다.
- 서로 다른 팀이 같은 코드베이스에서 작업하다 보면, 각자의 우선순위와 일정, 요구사항이 달라질 수밖에 없다.
- 이럴 때 의존성을 각 패키지에 직접 설치하면, 팀별로 유연하게 버전을 관리할 수 있다.
- 예컨대 UI 팀은 최신 TypeScript로 업그레이드하고, 웹 팀은 일단 기능 개발에 집중하고 TypeScript는 나중에 올리는 식이다.
- 중요한 건, 패키지별로 의존성을 관리하면 이런 유연성을 확보할 수 있다는 것이다.
- 더 나은 캐싱 능력:
- 레포지토리의 루트에 너무 많은 의존성을 설치하면 의존성을 추가, 업데이트 또는 삭제할 때마다 워크스페이스 루트가 변경되어 불필요한 캐시 누락이 발생한다.
- 사용하지 않는 의존성 정리:
- Docker 사용자의 경우 Turborepo의 정리(pruning) 기능을 사용하면 Docker 이미지에서 사용하지 않는 의존성을 제거하여 더 가벼운 이미지를 만들 수 있다.
- 의존성이 의도한 패키지에 설치되면 Turborepo는 잠금 파일을 읽고 필요한 패키지에서 사용되지 않는 의존성을 제거할 수 있다.
Note
Docker를 사용할 때는 이미지의 크기를 고려해야한다.
이미지가 클수록 빌드, 배포 시간이 길어지며, 레지스트리의 스토리지 비용까지 늘어난다.
문제는, 도커 이미지를 만들 때 사용하지도 않는 파일들까지 함께 포함되기 쉽다는 것이다.
Dockerfile
에서 COPY
명령으로 패키지 디렉토리를 통째로 복사하면, 쓰지도 않는 의존성까지 이미지에 들어가 버린다.
바로 여기서 Turborepo의 prune
이 도움이 된다.
prune
기능을 사용하면, Turborepo는 각 패키지의 실제 의존성을 분석해서, 정말로 필요한 것들만 도커 이미지에 포함시킨다.
예를 들어 레포지토리에 web
, api
, db
세 개의 패키지가 있는데, 지금 도커 이미지를 빌드하는 건 web
뿐이라고 해보자.
이때 Turborepo는 web
패키지의 의존성만 따로 추려서 도커 이미지에 포함시킨다. api
나 db
에서 쓰이는 의존성은 과감히 버린다.
이 과정에서 Turborepo는 의존성 정보를 정확히 알고 있어야 한다. 이를 위해 Turborepo는 lockfile을 사용한다.
만약 각 패키지별로 의존성이 잘 나뉘어 설치되어 있다면, Turborepo는 lockfile만 보고도 어떤 패키지가 어떤 의존성을 쓰는지 정확히 판단할 수 있다.
하지만 만약 모든 의존성이 루트에 뭉텅이로 설치되어 있다면? lockfile만 봐서는 어떤 의존성이 web
에서 쓰이는지 알 수 없다.
즉 Turborepo의 prune
을 효과적으로 사용하려면, 의존성을 각 패키지에 잘 분배해서 설치해야 한다.
루트의 의존성은 최소화한다
각 패키지에서 사용하는 의존성을 그 패키지에 직접 설치하는 원칙을 따른다면, 자연스레 루트 디렉토리에는 의존성이 거의 없게 될 것이다.
루트에는 오직 레포지토리 전체를 관리하기 위한 도구들, 예를 들어 turbo
, husky
, lint-staged
같은 것들만 설치하면 된다.
의존성 관리하기
Turborepo는 의존성을 관리하지 않는다
Turborepo 자체는 의존성 관리에 직접 관여하지 않는다.
의존성 관리는 전적으로 npm, yarn, pnpm 같은 패키지 매니저의 역할이다.
패키지 매니저가 하는 일을 좀 더 구체적으로 말하자면 다음과 같다.
- 패키지의 버전을 확인하고 알맞은 버전을 다운로드한다.
- 필요한 경우 심볼릭 링크를 만들어 패키지 간 참조가 가능하게 해준다.
- import나 require 구문을 해석해서 적절한 모듈 코드를 로드한다.
반면 Turborepo는 이런 일들에 직접 관여하지는 않는다.
Turborepo의 주된 관심사는 태스크 실행과 캐싱, 증분 빌드 같은 것들이다. 의존성 관리는 어디까지나 패키지 매니저에게 위임한다.
앞서 언급한 내용들은 모노레포를 효과적으로 관리하기 위한 제안일 뿐, Turborepo가 강제하는 사항은 아니다.
node_modules 위치
패키지 매니저(npm, yarn 등)는 프로젝트에 필요한 라이브러리(의존성)를 관리한다.
이 의존성들은 package.json
파일에 명시되어 있다.
패키지 매니저는 package.json
을 읽고, 명시된 의존성을 다운로드하여 node_modules
디렉토리에 저장한다.
node_modules
는 프로젝트 루트 또는 개별 패키지 내에 위치할 수 있다.
프로젝트 구조와 패키지 매니저 설정에 따라 node_modules
의 위치가 달라질 수 있다.
스크립트와 작업이 필요한 의존성을 찾을 수 있다면, node_modules
의 위치에 상관없이 패키지 매니저는 제대로 작동하고 있는 것이다.
코드에서 `node_modules` 참조
코드에서 node_modules
를 직접 참조하는 것은 좋지 않은 방법이다. 그 이유는 다음과 같다:
node_modules
의 위치는 변경될 수 있다. 패키지 매니저나 프로젝트 구조에 따라node_modules
의 위치가 달라질 수 있기 때문이다.- 직접 참조하면 코드의 이식성이 떨어진다. 다른 개발자나 다른 환경에서는
node_modules
의 위치가 다를 수 있기 때문에, 코드가 제대로 작동하지 않을 수 있다. - 패키지 매니저의 기능을 제대로 활용할 수 없다. 패키지 매니저는 의존성을 자동으로 관리해주는 도구인데,
node_modules
를 직접 참조하면 이 기능을 사용할 수 없게 된다.
따라서 node_modules
를 직접 참조하기보다는, Node.js 모듈 시스템을 사용하는 것이 좋다.
의존성을 동일한 버전으로 유지하기
모노레포는 여러 개의 패키지나 프로젝트가 하나의 저장소에 함께 있는 구조다.
이 경우, 각 패키지들이 서로 다른 버전의 의존성을 가지고 있으면 관리가 복잡해질 수 있다.
때문에 모노레포 관리자들은 모든 패키지에서 의존성 버전을 동일하게 유지하는 것을 선호하는 경우가 있다.
이를 달성하는 방법은 다음과 같다.
관련 도구 사용
syncpack
, manypkg
, sherif
와 같은 도구들은 모노레포 내의 패키지들 간 의존성 버전을 동기화하는 데 특화되어 있다.
패키지 매니저 사용
npm
, yarn
과 같은 패키지 매니저들은 의존성 버전 관리 기능을 제공한다.
npm install typescript@latest --workspaces
IDE 사용하기
IDE의 리팩터링 도구를 사용하면 저장소의 모든 package.json
파일에서 한 번에 의존성의 버전을 찾아 바꿀 수 있다.
검색 패턴에 "next": ".*"
와 같은 정규식을 package.json
파일에 사용하여 원하는 버전으로 교체할 수 있다.
완료되면 npm install
등 패키지 매니저의 설치 명령을 실행하여 lockfile을 업데이트해야 한다.