카테고리 없음

dependencies vs devDependencies vs peerDependencies — React 프로젝트에서의 진짜 의미와 실전 활용

bomsbro 2025. 6. 25. 01:51

프론트엔드 개발을 하다 보면 package.json에서 dependencies, devDependencies, peerDependencies를 마주하게 됩니다. 대부분의 개발자들이 "런타임에 필요하면 dependencies, 개발할 때만 필요하면 devDependencies"라고 알고 있지만, 실제로는 훨씬 복잡하고 미묘한 차이가 있습니다.

특히 React 프로젝트에서는 이런 구분이 거의 무의미해지는 경우가 많은데요, 오늘은 그 이유와 함께 Webpack의 alias, external 설정까지 한번에 정리해보겠습니다.

📦 세 가지 의존성의 정확한 정의

dependencies - 프로덕션 런타임 의존성

{
  "dependencies": {
    "react": "^18.2.0",
    "axios": "^1.4.0",
    "lodash": "^4.17.21"
  }
}

언제 사용하나요?

  • 앱이 실제로 실행될 때 반드시 필요한 패키지
  • 사용자가 앱을 사용할 때 코드에서 import/require 되는 라이브러리
  • 서버 환경에서 npm install --production 시에도 설치되는 패키지

devDependencies - 개발 및 빌드 도구

{
  "devDependencies": {
    "vite": "^4.4.0",
    "typescript": "^5.0.0",
    "@types/react": "^18.2.0",
    "eslint": "^8.45.0"
  }
}

언제 사용하나요?

  • 빌드, 테스트, 타입 체크, 린팅 등 개발 과정에서만 필요한 도구
  • 최종 번들에는 포함되지 않는 것이 일반적
  • npm install --production 시에는 설치되지 않음

peerDependencies - "너가 설치해줘!" 의존성

{
  "peerDependencies": {
    "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
  }
}

언제 사용하나요?

  • 라이브러리나 플러그인을 만들 때 사용
  • "이 패키지를 사용하려면 특정 버전의 다른 패키지가 필요해요"라고 선언
  • 실제로는 설치하지 않고, 사용자가 직접 설치해야 함

🤔 React 앱에서 구분이 무의미해지는 이유

1. npm install --production을 거의 사용하지 않음

전통적인 Node.js 서버 vs React SPA의 차이:

# 전통적인 Node.js 서버 배포
npm install --production  # 런타임에만 필요한 패키지만 설치
node server.js           # 서버에서 직접 node_modules 참조

# React SPA 배포 과정
npm install              # 모든 의존성 설치 (빌드를 위해)
npm run build           # 빌드 (devDependencies 필요)
# → dist/ 폴더의 정적 파일만 CDN/웹서버에 업로드
# → node_modules는 서버에 존재하지 않음

왜 React에서 --production이 무의미한가:

  1. 정적 호스팅 방식

    # 최종 배포물
    /var/www/html/
    ├── index.html
    ├── assets/
    │   ├── main.js     # 번들된 JavaScript
    │   └── main.css    # 번들된 CSS
    └── (node_modules 없음!)
  2. CI/CD 파이프라인에서의 현실

    # GitHub Actions 예시
    - name: Install dependencies
      run: npm ci              # --production 안 씀
    - name: Build
      run: npm run build       # TypeScript, Vite 등 devDeps 필요
    - name: Deploy
      run: aws s3 sync dist/ s3://my-bucket/  # 정적 파일만 업로드
  3. Docker 멀티스테이지 빌드에서도 마찬가지

    # 빌드 스테이지
    FROM node:18 AS builder
    COPY package*.json ./
    RUN npm ci                    # 모든 의존성 설치
    COPY . .
    RUN npm run build            # devDependencies 필요
    
    # 운영 스테이지
    FROM nginx:alpine
    COPY --from=builder /app/dist /usr/share/nginx/html
    # node_modules는 복사하지 않음!

--production이 의미있는 경우:

  • Node.js API 서버
  • SSR 서버 (Next.js production 모드)
  • Lambda 함수 (용량 제한 때문)

React SPA에서는:

  • 빌드 과정에서 모든 도구가 필요
  • 최종 산출물에는 node_modules가 포함되지 않음
  • 따라서 --production 최적화의 실효성이 없음

2. 빌드 과정에서 devDependencies가 필수

npm run build  # TypeScript 컴파일, Vite 빌드 등이 필요

빌드 과정에서 TypeScript, Vite, Webpack 등의 devDependencies가 반드시 필요하기 때문에, 결국 모든 의존성을 설치해야 합니다.

3. 도구들이 "편의성"을 우선시

Create React App, Vite, Next.js 같은 도구들은 개발자 편의를 위해 의존성 구분을 엄격하게 강제하지 않습니다.

🚨 흔한 오해: "devDependencies는 번들에 안 들어간다"

이것은 완전히 잘못된 생각입니다!

// 이 코드가 있다면
import { format } from 'date-fns'

// date-fns가 devDependencies에 있어도 번들에 포함됩니다!

번들 포함 여부는 package.json의 위치가 아니라 다음에 의해 결정됩니다:

  1. 실제 import/require 여부
  2. Webpack의 externals 설정
  3. Tree-shaking 결과

devDependencies는 단순히 "이 패키지는 개발용이에요"라는 메타데이터일 뿐입니다.

🔧 peerDependencies의 진짜 동작 원리

peerDependencies는 "버전 체크" 도구

많은 개발자들이 착각하는 부분이 바로 여기입니다. peerDependencies는 번들에서 패키지를 제외하지 않습니다!

// my-ui-library/package.json
{
  "name": "my-ui-library",
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

이 라이브러리를 설치할 때:

npm install my-ui-library
# React 17을 사용하는 프로젝트라면
# ⚠️  WARN: react@17.0.2 does not satisfy peer dependency react@^18.0.0

peerDependencies의 실제 역할:

  1. npm install 시 버전 호환성 경고
  2. 패키지 매니저가 올바른 버전인지 체크
  3. 개발자에게 "이 버전을 써야 해요" 알림

진짜 번들 제외는 externals로!

peerDependencies만으로는 번들에서 제외되지 않습니다:

// webpack.config.js - 라이브러리 빌드 설정
module.exports = {
  externals: {
    // 이것이 진짜 번들에서 제외하는 설정!
    'react': {
      commonjs: 'react',
      commonjs2: 'react',
      amd: 'react',
      root: 'React'
    },
    'react-dom': {
      commonjs: 'react-dom',
      commonjs2: 'react-dom',
      amd: 'react-dom',
      root: 'ReactDOM'
    }
  }
}

실제 라이브러리 개발 시나리오

1단계: peerDependencies 선언 (버전 체크용)

{
  "peerDependencies": {
    "react": ">=16.8.0"
  }
}

2단계: externals 설정 (번들 제외용)

// rollup.config.js 또는 webpack.config.js
externals: ['react', 'react-dom']

3단계: 사용자가 설치할 때

# 사용자 프로젝트
npm install react@17.0.2  # React 17 설치
npm install my-ui-library # 경고 발생하지만 설치됨

# 실제 번들에서는 사용자의 React 17 사용
# externals 설정 덕분에 중복 포함 방지

일반 React 앱에서 peerDependencies?

// 일반 React 앱에서는 정말 의미없음
{
  "peerDependencies": {
    "react": "^18.0.0"  // 누가 이걸 체크하나요?
  }
}

일반 앱에서는:

  • 앱이 최상위 패키지라 peer dependency를 체크할 상위가 없음
  • 직접 dependencies에 명시하는 것이 맞음

용도:**

  • 절대 경로 설정 (@/components/Button)
  • 특정 버전의 라이브러리로 강제 지정
  • 개발/프로덕션 환경별 다른 모듈 사용

externals - 번들에서 완전 제외

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  }
}

의미:

  • import React from 'react' 코드를 만나면
  • 번들에 React 코드를 포함시키지 않고
  • 런타임에 전역 변수 React를 참조

주로 언제 사용?

  • 라이브러리 개발시 peerDependencies와 함께
  • CDN으로 로드된 라이브러리 활용시

📋 정리: 언제 무엇을 사용할까?

상황 dependencies devDependencies peerDependencies + externals
React 앱 개발 런타임 라이브러리 빌드 도구, 타입 거의 사용 안함
라이브러리 개발 꼭 필요한 런타임 빌드 도구, 테스트 도구 공유 의존성 (React 등)
Node.js 서버 런타임 라이브러리 빌드 도구 거의 사용 안함
CLI 도구 런타임 라이브러리 빌드 도구 플러그인 시스템에서 사용

💡 실전 팁

1. 의존성 정리 명령어

# 사용하지 않는 패키지 찾기
npx depcheck

# 보안 취약점 체크
npm audit

# 최신 버전 체크
npx npm-check-updates

2. 라이브러리 번들 크기 분석

# 번들 크기 시각화
npx webpack-bundle-analyzer dist/

# 패키지 크기 확인
npx bundlephobia axios

3. peerDependencies 관리

# peer dependencies 자동 설치
npx install-peerdeps my-ui-library

# peer dependencies 체크
npm ls --depth=0 2>/dev/null | grep -E "(UNMET|invalid)"

🎯 결론

핵심 포인트 정리:

  1. dependencies vs devDependencies: React 앱에서는 실질적 차이가 적지만, 가독성과 협업을 위해 구분
  2. peerDependencies: 라이브러리의 버전 체크 도구, 번들 제외 기능은 없음
  3. externals: 진짜 번들에서 제외하는 설정
  4. 번들 포함 여부: package.json 위치가 아닌 import 여부와 bundler 설정이 결정
  5. React 앱: npm install 하나로 충분, 복잡한 의존성 관리 불필요
  6. 라이브러리: peerDependencies + externals 조합으로 완벽한 의존성 관리

다음번에 package.json을 볼 때는 이 글을 떠올려 보세요. 단순해 보이는 의존성 관리 뒤에 숨어있는 복잡한 메커니즘을 이해하면, 더 나은 프로젝트 구조를 만들 수 있을 것입니다.


이 글이 도움이 되었다면 좋아요와 구독 부탁드립니다! 궁금한 점이 있으시면 댓글로 남겨주세요.