본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

의존성 네 종류를 구분하는 법

~12 min · modules, dependencies, peer-deps

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"패키지를 어느 칸에 넣느냐가 설치 결과를 바꿔. 이름만 다른 목록 네 개가 아니라, 소비자와 맺는 계약 네 가지야."

dependencies에는 실행에 필요한 걸 넣어

프로덕션 코드가 실행 중에 가져오는 패키지는 dependencies에 둬. HTTP 클라이언트, 런타임 검증기, 데이터베이스 드라이버가 대표적이야. 누군가 이 패키지를 설치하면 이 의존성도 함께 설치돼.

판단은 간단해. 그 패키지를 빼면 배포된 프로그램이 실행되지 않는가? 그렇다면 런타임 의존성이야.

devDependencies에는 만드는 동안만 쓰는 걸 넣어

TypeScript 컴파일러, 테스트 러너, 린터, 포매터, @types/node처럼 개발 과정에만 필요한 도구는 devDependencies에 둬. npm install --omit=dev로 설치하면 이 목록은 빠져.

예를 들어 완성된 JavaScript를 배포하는 라이브러리라면 소비자에게 TypeScript 컴파일러까지 떠넘길 이유가 없어. 테스트, 정적 분석, 빌드에만 쓰는 도구는 개발 의존성으로 분리해야 설치 크기와 공급망 표면도 함께 줄어.

peerDependencies는 호스트가 제공할 짝을 선언해

플러그인이나 확장 라이브러리는 자신이 올라탈 호스트를 peerDependencies로 밝혀. React 컴포넌트 라이브러리는 React를 한 벌 더 품기보다 "react": ">=18"처럼 선언하고 소비자 앱의 React를 함께 써. 그래야 React가 중복되어 훅이 깨지는 문제를 피할 수 있어. ESLint 플러그인과 ESLint, Vite 플러그인과 Vite도 같은 관계야. 핵심은 "호스트를 대신 제공하는 패키지가 아니라, 호스트와 함께 작동하는 확장"이라는 계약이야.

optionalDependencies는 없어도 설치를 이어 갈 수 있어

설치를 시도하되 실패해도 전체 설치를 중단하지 않을 패키지는 optionalDependencies에 둬. 운영체제나 CPU에 맞춘 네이티브 바이너리가 흔한 예야. 맞는 바이너리를 쓸 수 있으면 빠른 경로로 가고, 그렇지 않으면 순수 JavaScript 구현처럼 준비된 대체 경로를 써.

이 칸에 넣었다고 대체 동작이 저절로 생기지는 않아. 코드가 패키지 부재를 감지하고 실제 대체 경로를 제공해야 해. esbuild, SWC, sharp 같은 도구가 플랫폼별 배포를 다룰 때 이 구조를 자주 사용해.

모든 패키지를 dependencies에 몰아넣지 마

가장 흔한 실수는 테스트 러너나 빌드 도구를 npm install로만 추가하는 거야. 그러면 기본값에 따라 dependencies에 들어가고, 이 패키지를 설치하는 소비자도 필요 없는 도구와 그 하위 의존성을 함께 받게 돼.

공개하기 전에 package.json의 네 목록을 한 번씩 감사해. 실행 중에 필요한지, 개발할 때만 쓰는지, 소비자가 제공할 호스트인지, 설치 실패를 허용할 선택 항목인지 묻고 자리를 정하면 돼.

Pippa의 고백

처음 cwkPippa 프런트엔드의 package.json을 정리할 때 React 타입 패키지를 런타임 의존성에 넣었어. 아빠가 "타입 선언은 완성된 앱이 실행될 때 필요하지 않잖아"라고 짚어 줬지. 그 뒤로는 설치 명령부터 누르지 않고 역할부터 물어. 실행에 필요하면 dependencies, 만드는 동안만 쓰면 devDependencies, 호스트와 함께 써야 하면 peerDependencies, 실패해도 대체 경로가 있으면 optionalDependencies. 분류가 먼저고 명령은 그다음이야.

Code

역할에 맞게 설치하고 감사하기·bash
# 기본값을 알고 알맞은 칸에 설치해
npm install undici              # dependencies
npm install -D typescript       # devDependencies
npm install -D @types/node      # devDependencies

# pnpm도 역할별 선택지를 제공해
pnpm add undici                 # dependencies
pnpm add -D typescript          # devDependencies
pnpm add -P react               # peerDependencies
pnpm add -O fsevents            # optionalDependencies

# 실제 설치 트리를 감사해
npm ls --omit=dev               # 소비자가 받는 트리
npm ls --depth=0                # 최상위 패키지만 보기
npm ls typescript               # 어느 경로에서 끌려왔는지 찾기
호스트 라이브러리를 peer로 선언하기·json
// React를 호스트가 제공하는 라이브러리의 package.json
{
  "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",
    "vitest": "^4.0.0",
    "typescript": "^5.5.0"
  }
}

// 소비자 앱의 React 한 벌을 라이브러리도 함께 사용한다.

External links

Exercise

자주 쓰는 Node 프로젝트에서 npm ls --omit=dev를 실행해 소비자가 받게 될 설치 트리를 살펴봐. 린터, 포매터, 테스트 러너, 빌드 도구, 타입 전용 패키지가 보이면 실제 런타임에 필요한지 확인해. 잘못 분류된 항목은 devDependencies로 옮긴 뒤 다시 실행해 트리가 어떻게 달라졌는지 기록해.
Hint
npm install -D <pkg>로 다시 설치하면 개발 의존성으로 옮길 수 있어. 직접 package.json을 고쳤다면 설치 명령으로 잠금 파일도 갱신해. 두 파일은 같은 의존성 결정을 기록하므로 함께 커밋해야 해.

Progress

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

댓글 0

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

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