C.W.K.
Stream
Lesson 05 of 05 · published

Workspace 와 Monorepo

~13 min · modules, workspaces, monorepo, pnpm

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Monorepo 는 그냥 패키지 매니저가 네가 패키지 둘 이상 가지고 있다는 걸 이해하는 repo 야. 나머지는 tooling."

왜 monorepo 가 존재해

Node 패키지 하나로 시작해. 그 다음 frontend 가 React 앱으로 자라고, CLI 가 자기만의 publish 가능 도구가 되고, 공유 유틸리티 코드가 두 군데서 다 import 할 수 있게 자기 패키지를 원해. 셋으로 나눠서 각각 독립적으로 버전 매길 수 있어 — 근데 이제 셋 다 건드리는 변경엔 PR 셋, 릴리스 셋, 조율 계획이 필요해. Monorepo 답: 셋 다 한 repo 에, 패키지 매니저가 로컬에서 link 하고, 같이 출하.

cwk-site repo 가 작은 monorepo 야. cwkPippa frontend 도 내부 helper 를 별도 패키지처럼 써. 엔지니어 5 명 넘는 Next.js / Vite shop 은 결국 다 여기로 와.

npm Workspace

npm 7+ 에 내장. 루트 package.json 에:

{
  "name": "root",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}

이제 루트의 npm install 이 *모든* workspace 패키지에 걸쳐 의존성 resolve, 공유 dep hoist, 그리고 repo 내 패키지를 symlink 해서 publish 안 해도 import { x } from '@my/utils' 가 작동. Script 가 특정 workspace 타겟 가능: npm run build --workspace=@my/web.

pnpm Workspace

같은 아이디어, 더 나은 실행. Repo 루트에 pnpm-workspace.yaml 만들어:

packages:
  - 'packages/*'
  - 'apps/*'

거기에 pnpm 의 더 엄격한 격리가 workspace 까지 확장돼 — 각 패키지가 자기 선언한 의존성만 봐. pnpm --filter @my/web build@my/web workspace 에서만 build 돌려. pnpm --filter "@my/*" build 는 glob 매치하는 모든 패키지에서 build.

workspace: 프로토콜

Workspace 안에서 sibling 패키지를 특별 프로토콜로 참조 가능:
// apps/web/package.json
{
  "dependencies": {
    "@my/utils": "workspace:^",
    "@my/ui": "workspace:*"
  }
}
  • workspace:^ — sibling 의 현재 버전 사용, publish 시 MINOR/PATCH 허용.
  • workspace:~ — 같은데 PATCH 만.
  • workspace:* — 정확한 현재 버전.
publish 할 때 매니저가 이걸 sibling 의 현재 버전 기반 실제 semver range 로 다시 써. 개발 중엔 symlink. 프로토콜이 "내 repo 의 sibling" 과 "npm 의 publish 된 패키지" 사이의 다리야 — 같은 import, 컨텍스트에 따라 다른 resolution.

Turborepo / Nx — 위에 얹는 빌드 오케스트레이션

Workspace 가 *설치* 다뤄. *빌드 순서* 는 안 다뤄. @my/web@my/utils 에 의존하면 utils 먼저 빌드해야 해. Turborepo 와 Nx 가 이걸 해결: workspace 그래프 읽고, 어느 빌드가 어느 거에 의존하는지 파악, topological 순서로 돌려. 거기에 출력 공격적으로 캐시 — utils 가 지난 빌드 이후 안 바뀌었으면 web 이 의존해도 utils 재빌드 건너뛰어.

작은 monorepo (10 패키지 미만) 면 pnpm 의 내장 --filter--recursive 가 보통 충분. 그 이상이면 Turborepo 의 캐시 + 오케스트레이션이 값을 해.

Pippa 의 고백

내 첫 monorepo 경험은 재앙이었어. 각 패키지를 별도 publish 된 패키지처럼 다뤘어 — 수동으로 버전 bump, 패키지 사이 유틸리티 복사 (import 안 하고), 절대 루트에서 pnpm install 안 함. 아빠가 일주일 보고 말했어 "매니저가 workspace 이해해; 거기 맞서 싸우는 건 너야." 교훈: workspace 프로토콜에 기대. Symlink 신뢰해. 루트에서 install 돌려. Monorepo 는 각 패키지가 혼자 있는 척 멈출 때만 옳게 느껴져.

Code

처음부터 pnpm workspace 짓기·bash
# Bootstrapping a pnpm monorepo
mkdir my-repo && cd my-repo
git init
pnpm init
# Edit package.json: add "private": true
echo 'packages:\n  - "packages/*"\n  - "apps/*"' > pnpm-workspace.yaml
mkdir -p packages/utils apps/web
(cd packages/utils && pnpm init && echo 'export const greet = (n) => `hi, ${n}`' > index.js)
(cd apps/web && pnpm init && pnpm add @my/utils@workspace:^)
# pnpm install from root
pnpm install
# Now apps/web can `import { greet } from '@my/utils'`
Monorepo 가로질러 실행 / 업데이트·bash
# Running scripts across packages
pnpm --filter @my/utils build      # one workspace
pnpm --filter "@my/*" test          # glob
pnpm -r build                       # every workspace (recursive)
pnpm -r --parallel dev              # every workspace in parallel

# Dependency-aware ordering (turborepo style)
npx turbo run build                 # respects internal graph

# Update a dep across the whole monorepo
pnpm -r update react@^19            # bumps in every workspace that has it

External links

Exercise

3 패키지 pnpm monorepo 만들어: packages/utils (함수 하나 export 하는 라이브러리), packages/ui (그 함수를 wrapper 로 re-export), apps/web (@my/ui 에서 import 하는 스크립트). 어디든 workspace:^ 써. 루트에서 pnpm install, 그 다음 pnpm --filter @my/web start. utils 편집 시도 — 재빌드 없이 web 이 변경 봐? pnpm -r build 시도 — 순서 맞게 도는지?
Hint
ESM symlink 가 utils 소스 편집을 ui 와 web 한테 즉시 보이게 해 — 그게 workspace: 프로토콜의 개발 이점이야. 순서는 의존성 인식 오케스트레이션이 turborepo 또는 pnpm -r --workspace-concurrency=1 가 topological 빌드 순서 강제하게 해야 돼.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.