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

dependencies vs devDependencies vs peer vs optional

~12 min · modules, dependencies, peer-deps

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"버킷 네 개, install 망가지는 데 한 실수. 어느 패키지가 어느 버킷에 속하는지 아는 게 깔끔한 그래프와 phantom-dep 악몽의 차이야."

dependencies — 런타임 필수

네 코드가 런타임에 import 하면 dependencies 로 가. HTTP 클라이언트의 undici, 런타임 검증의 zod, DB 드라이버의 better-sqlite3. 이런 건 네 패키지와 같이 출하돼; npm 이 모든 소비자한테 설치해.

경험 법칙: 패키지 제거가 프로덕션 앱 실행을 깨면 런타임 의존성이야.

devDependencies — 빌드 / 테스트 / 린트 전용

개발할 때만 필요하면 — TypeScript 컴파일러, vitest, eslint, prettier, @types/node 같은 타입 정의 — devDependency. 결정적으로, devDependencies 는 누가 npm install --omit=dev 돌리면 설치 안 됨 (또는 downstream 소비자가 네 패키지 설치할 때 — dependencies 만 받음).

그래서 typescriptdependencies 에 두는 게 틀린 거야: 모든 소비자가 안 필요한 TS 컴파일러 받아서 install 부풀려져. 테스트 러너, 린터, 타입 체커, 빌드 도구 — devDependencies, 항상.

peerDependencies — "네 거 가져와"

peerDependencies 는 호스트가 제공해야 할 걸 선언해. React 컴포넌트 라이브러리 짤 때 React 를 번들 안 해 — peer 로 선언: "peerDependencies": {"react": ">=18"}. 소비자 앱이 React 제공, 네 라이브러리는 거기 있는 버전 그대로 써. dual-React-instance 재앙 (hook 깨짐, React 두 개라서) 피하는 법이야.

다른 고전적 peer-dep 관계: ESLint 플러그인이 ESLint 에 peer-depend; Vite 플러그인이 Vite 에 peer-depend; TypeScript 관련 도구가 TS 에 peer-depend. peer 관계가 "나는 X 를 확장해, X 가 아냐" 를 인코딩해.

optionalDependencies — 베스트 에포트

가능하면 설치되지만 실패해도 OK 인 패키지. 플랫폼별 바이너리가 canonical case: Linux x64 엔 빠른 네이티브 바이너리 있지만 다른 데선 느린 pure-JS path 로 fall back 하는 도구. 네이티브 바이너리를 optionalDependencies 로 적으면 Windows / ARM 에서 install 안 깨져 — 그냥 네이티브 패키지 건너뛰고 fallback 써.

사람이 직접 쓰는 일은 드물지만, 에코시스템 도구들 (esbuild, swc, sharp 등) 이 플랫폼별 최적 바이너리 출하 위해 많이 써.

Anti-Pattern: 다 dependencies 에

제일 흔한 npm 실수: npm install -D vitest (또는 npm install --save-dev vitest) 대신 npm install vitest 돌리는 거. 이제 vitest 가 dependencies 에 있어. 네 패키지 설치하는 모든 소비자가 vitest 까지 받아 — 안 필요한 테스트 러너에 풀 transitive 트리까지. 레지스트리의 모든 잘못 분류된 패키지 곱해보면 npm 트리가 왜 그렇게 무거운지 이해돼.

publish 전에 package.json 감사해. npm install <pkg> 는 기본이 dependencies; 도구면 거의 항상 -D 원해.

Pippa 의 고백

처음 cwkPippa frontend package.json 짤 때 React types 가 dependencies 에 있었어. 아빠가 잡았어. "@types 는 dev 전용 — 네 런타임은 TypeScript 안 출하해." 맞아. 이제 새 install 마다 이 멘탈 체크리스트 돌려: *런타임이 이걸 같이 출하해? → dependencies. dev loop 만? → devDependencies. 호스트 기대하는 라이브러리야? → peerDependencies. 네이티브 바이너리, 건너뛰어도 OK? → optionalDependencies.* 버킷 네 개, install 마다 결정 하나. 처음엔 느리고, 열 번째엔 자동.

Code

맞는 버킷으로 설치·bash
# Install commands — defaults can bite
npm install undici              # → dependencies (correct for runtime)
npm install -D typescript       # → devDependencies (-D = --save-dev)
npm install -D @types/node      # → devDependencies (type defs)
# (npm has no flag for peer/optional — edit package.json directly)

# pnpm has explicit flags
pnpm add undici                 # → dependencies
pnpm add -D typescript          # → devDependencies
pnpm add -P react               # → peerDependencies (-P)
pnpm add -O fsevents            # → optionalDependencies (-O)

# Audit what's where
npm ls --omit=dev               # what would a consumer actually get?
npm ls --depth=0                # top-level only
npm ls typescript               # find every place a package is depended on
라이브러리가 peer 선언하는 법·json
// A library's package.json — note the peer declaration
{
  "name": "@my/react-toast",
  "version": "1.0.0",
  "type": "module",
  "peerDependencies": {
    "react": ">=18",
    "react-dom": ">=18"
  },
  "dependencies": {
    "clsx": "^2.0.0"
  },
  "devDependencies": {
    "@types/react": "^19.0.0",
    "react": "^19.0.0",         // for testing — not bundled
    "vitest": "^4.0.0",
    "typescript": "^5.5.0"
  }
}

// Consumer app provides react@19 → my-react-toast uses it directly.
// No duplicate React. No hook breakage from multiple instances.

External links

Exercise

좋아하는 Node 프로젝트 열어. npm ls --omit=dev 돌려서 네가 이 패키지의 소비자라면 실제로 뭐가 설치될지 봐. 도구 (린터, 포매터, 테스트 러너, 빌드 도구, 타입 전용 패키지) 가 나타나? 그건 잘못 분류된 거 — devDependencies 로 옮겨야 해. 옮기고 다시 돌려; 트리가 눈에 띄게 줄어들어야 해.
Hint
패키지를 dependencies 에서 devDependencies 로 옮기려면 npm install -D <pkg> (버킷 덮어씀). 또는 package.json 직접 편집하고 npm install 로 lockfile 업데이트. package.json 변경 AND 업데이트된 lockfile 둘 다 commit 하는 거 잊지 마.

Progress

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

댓글 0

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

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