패키지 매니저부터 모노레포까지 (04) - workspace는 왜 필요해졌나
패키지가 여러 개로 나뉘기 시작하면 왜 workspace가 필요해지는지, 단일 프로젝트 구조의 한계와 함께 정리합니다.
패키지 매니저 이야기를 따라가다 보면 결국 workspace를 만나게 됩니다. 패키지가 하나일 때와 여러 개일 때는 관리 기준 자체가 달라지기 때문입니다.
들어가며
앞선 글에서는 npm, Yarn, pnpm이 왜 서로 다른 방향으로 발전했는지를 봤습니다. 그런데 패키지 매니저를 조금 더 오래 쓰다 보면, 어느 순간 도구 이름보다 더 중요한 질문이 생깁니다.
프로젝트 안에 패키지가 하나가 아니라 여러 개라면 어떻게 관리해야 할까요.
앱 하나만 만들 때는 이 질문이 잘 드러나지 않습니다. 루트에 package.json 하나를 두고, 필요한 라이브러리를 설치하고, npm run dev나 pnpm dev를 실행하면 대부분 충분합니다. 하지만 코드베이스가 커지고 역할이 나뉘기 시작하면 상황이 달라집니다.
- 앱과 공통 UI를 분리하고 싶을 수 있습니다
- 서버와 웹을 나눠 관리하고 싶을 수 있습니다
- 여러 패키지를 같은 저장소에서 함께 배포하고 싶을 수 있습니다
여기서부터는 "패키지 매니저를 무엇으로 쓸까"만으로는 설명이 부족해집니다. 여러 패키지를 한 번에 다루는 구조, 즉 workspace가 왜 필요해지는지가 더 중요해집니다.
이번 글에서는 monorepo로 바로 뛰지 않고, 그보다 한 단계 앞에서 왜 단일 프로젝트 구조만으로는 버티기 어려운 시점이 오고 workspace가 어떤 문제를 풀기 위해 등장하는지부터 정리해 보겠습니다.
패키지가 여러 개가 되는 순간, 문제는 폴더 수가 아니라 연결과 설치와 실행 기준을 어떻게 하나로 묶을 것인가로 바뀝니다.
패키지가 여러 개가 되면 무엇이 달라지는가
프로젝트 안의 패키지가 여러 개가 된다는 말은, 단순히 폴더가 늘어났다는 뜻이 아닙니다. 관리해야 할 약속과 경계가 한 번에 늘어난다는 뜻에 더 가깝습니다.
예를 들어 처음에는 이런 구조로 시작할 수 있습니다.
my-app/
package.json
src/이 단계에서는 기준이 비교적 단순합니다. 설치도 한 번, 실행도 한 방향, 배포도 한 단위로 생각하면 됩니다.
그런데 프로젝트가 자라면 이런 욕구가 생깁니다.
- 웹 앱과 관리 콘솔이 같은 로직을 공유하고 싶다
- 여러 앱이 공통으로 쓰는 UI 컴포넌트를 따로 두고 싶다
- 사내 공통 유틸리티를 별도 패키지처럼 관리하고 싶다
그러면 구조는 자연스럽게 이렇게 바뀌기 시작합니다.
my-project/
package.json
apps/
web/
admin/
packages/
ui/
utils/겉으로 보기에는 폴더만 늘어난 것 같지만, 실제로는 프로젝트를 바라보는 질문 자체가 달라집니다.
어떤 패키지가 누구를 의존하는가
apps/web가 packages/ui를 쓸 수는 있습니다. 그런데 그 연결을 어떻게 관리할까요. 로컬 파일 경로로 직접 참조할지, 매번 수동으로 링크할지, 아니면 패키지 매니저가 알아서 연결하게 둘지 결정해야 합니다.
설치는 한 번에 할 것인가, 따로 할 것인가
패키지가 여러 개인데 각 폴더에서 따로 install을 해야 한다면 금방 불편해집니다. 같은 저장소 안에서 작업하는데도 설치 기준이 흩어지고, lockfile도 여러 개 생기고, 누가 어디서 무엇을 설치했는지 추적하기 어려워집니다.
스크립트와 개발 흐름은 어떻게 맞출 것인가
패키지가 여러 개면 dev, build, test도 한 군데서 끝나지 않습니다. 어떤 명령은 전체를 대상으로 돌려야 하고, 어떤 명령은 특정 패키지만 골라 실행해야 합니다. 이때부터는 단일 패키지 기준의 작업 방식이 답답해지기 시작합니다.
결국 패키지가 여러 개가 되는 순간, 문제는 폴더 정리가 아니라 연결과 설치와 실행 기준을 어떻게 통일할 것인가로 바뀝니다.
workspace는 무엇을 해결하는가
workspace는 이 문제를 풀기 위한 패키지 매니저의 기본 장치라고 보면 됩니다.
npm 공식 문서는 workspace를 "하나의 최상위 루트 패키지 안에서 로컬 파일 시스템의 여러 패키지를 관리하도록 지원하는 기능 집합"이라고 설명합니다. 핵심은 이것입니다. 여러 패키지를 하나의 프로젝트처럼 다룰 수 있게 해 준다는 점입니다.
조금 쉽게 풀어보면, workspace는 "여러 package.json을 한 팀으로 묶어 관리하는 규칙"에 가깝습니다.
예를 들어 루트 package.json에 이렇게 적을 수 있습니다.
{
"private": true,
"workspaces": ["apps/*", "packages/*"]
}이 설정이 있으면 패키지 매니저는 apps와 packages 아래의 각 패키지를 따로 떨어진 프로젝트로만 보지 않고, 같은 작업 공간 안의 패키지로 인식합니다.
그 결과 얻는 이점은 분명합니다.
로컬 패키지 연결을 자동화할 수 있습니다
npm 공식 문서도 workspaces의 핵심 효과 중 하나로 자동 symlink를 설명합니다. 예전에는 npm link 같은 수동 작업을 해야 했던 흐름을, 이제는 설치 과정 안에서 더 자연스럽게 처리할 수 있습니다.
즉 packages/ui를 apps/web에서 쓰고 싶을 때, 사람이 매번 손으로 연결하는 대신 패키지 매니저가 같은 작업 공간 안의 패키지라고 이해하고 이어 주는 것입니다.
설치 기준을 하나로 모을 수 있습니다
여러 패키지를 따로 설치하지 않고, 루트에서 한 번에 관리할 수 있다는 점도 큽니다. Yarn Classic은 workspaces의 장점으로 "single install"과 "single lockfile"을 강조했고, npm도 루트 기준에서 여러 workspace를 다루는 흐름을 공식 기능으로 제공합니다.
이건 체감상 꽤 큰 차이입니다. 설치가 한 군데로 모이면 lockfile도 흩어지지 않고, 리뷰와 협업도 훨씬 단순해집니다.
여러 패키지를 같은 저장소 안에서 개발하기 쉬워집니다
코드가 나뉘어 있어도, 작업은 자주 함께 일어납니다. ui 패키지를 고치고 web 앱에서 바로 확인하고 싶을 때가 대표적입니다. workspace는 이런 흐름을 "한 저장소 안에서 여러 패키지를 함께 움직이는 개발"로 바꾸는 기본 토대를 제공합니다.
그래서 workspace는 폴더를 묶는 기능이라기보다, 여러 패키지를 하나의 개발 흐름 안에 묶는 기능이라고 보는 편이 더 정확합니다.
패키지 매니저마다 workspace 경험이 왜 다른가
여기서 다시 패키지 매니저 이야기가 연결됩니다. 모두 workspace를 지원한다고 해도, 실제 경험은 꽤 다를 수 있습니다.
npm은 기본 흐름을 제공하는 쪽에 가깝습니다
npm은 루트 package.json의 workspaces 설정을 읽고, 설치와 실행을 여러 workspace에 연결해 주는 기본 기능을 제공합니다. 특정 workspace에 의존성을 추가할 때 -w를 붙이거나, npm run test --workspaces처럼 여러 workspace를 대상으로 명령을 실행할 수 있는 것도 이 흐름 안에 있습니다.
즉 npm의 강점은 "기본적인 workspace 흐름을 표준적으로 가져간다"는 데 있습니다.
Yarn은 workspace를 더 중심 개념으로 밀어붙입니다
Yarn 공식 사이트는 workspaces를 핵심 기능으로 전면에 놓고 있습니다. Modern Yarn 문서도 workspaces를 모노레포의 핵심 도구처럼 설명하고, workspace: 프로토콜, workspaces foreach, focus 같은 기능을 더 적극적으로 제공합니다.
즉 Yarn은 workspace를 단순 지원 기능보다, 프로젝트 관리 방식 전체와 연결해서 다루는 인상이 강합니다.
pnpm은 workspace와 monorepo 체감이 강한 편입니다
pnpm 홈페이지도 workspaces와 monorepo를 주요 강점으로 내세웁니다. 설치 속도, 디스크 효율, 로컬 패키지 연결 경험이 함께 묶여 있기 때문에, 여러 패키지를 한 저장소에서 관리할 때 장점이 더 분명하게 느껴지는 편입니다.
그래서 실무에서는 같은 workspace 지원이라고 해도 체감이 다릅니다.
- npm은 기본 축
- Yarn은 workspace 중심 기능이 풍부함
- pnpm은 효율과 monorepo 체감이 강함
이 차이는 도구의 우열이라기보다, 각 패키지 매니저가 여러 패키지를 다루는 작업을 어디까지 중심 문제로 보느냐의 차이로 이해하는 편이 좋습니다.
단일 프로젝트 구조의 한계
물론 모든 프로젝트가 처음부터 workspace를 써야 하는 것은 아닙니다. 패키지가 하나고, 배포 단위도 하나고, 공통 코드를 별도 패키지처럼 관리할 필요가 없다면 단일 프로젝트 구조가 더 단순하고 좋습니다.
문제는 구조를 나눠야 할 이유가 생겼을 때입니다.
예를 들어 이런 순간이 반복되면 단일 구조가 버거워질 수 있습니다.
- 앱 두 개가 같은 코드를 복사해서 쓰기 시작한다
- 공통 UI를 수정할 때 여러 저장소를 동시에 고쳐야 한다
- 하나의 저장소 안에서 앱과 라이브러리를 함께 테스트하고 싶다
- 배포는 나뉘는데 개발은 항상 같이 이루어진다
이 시점부터는 "그냥 폴더로만 나누자"가 오래 버티기 어렵습니다. 연결 규칙이 없고, 설치 기준이 없고, 실행 흐름이 흩어지기 때문입니다. workspace는 바로 그 흐름을 다시 하나로 묶어 주는 장치입니다.
즉 workspace는 거대한 구조를 위한 사치가 아니라, 패키지가 여러 개가 되는 순간 필요한 최소한의 질서에 더 가깝습니다.
다음 글 예고
이번 글에서는 monorepo까지 가지 않고, 그 전 단계에서 왜 workspace가 필요해지는지를 먼저 봤습니다. 하나의 패키지일 때는 보이지 않던 문제가, 여러 패키지를 함께 다루기 시작하면 갑자기 커지기 때문입니다.
다음 글에서는 여기서 한 걸음 더 나가 보겠습니다. 여러 패키지를 한 작업 공간으로 묶는 이야기에서, 왜 많은 팀이 결국 monorepo 구조까지 함께 고민하게 될까요. 마지막 글에서는 그 연결을 정리해 보겠습니다.
참고
- npm Workspaces: npm이 workspace를 어떻게 정의하고 어떤 명령 흐름을 제공하는지 설명합니다.
- package.json workspaces: 루트
package.json의workspaces필드가 어떤 역할을 하는지 설명합니다. - Yarn Workspaces: Modern Yarn이 workspaces를 어떤 철학과 기능으로 다루는지 볼 수 있습니다.
- Yarn Classic Workspaces: Yarn Classic이 단일 설치와 단일 lockfile을 어떻게 설명했는지 확인할 수 있습니다.
- pnpm Workspaces: pnpm이 workspace를 어떤 흐름으로 다루는지 설명합니다.
- pnpm: pnpm이 monorepo와 workspace를 어떤 강점으로 내세우는지 확인할 수 있습니다.