Skip to content

프론트엔드 프로젝트 구조와 패키지 매니저의 필요성

브라우저가 이해하는 정적 파일을 만들기 위해 Node.js 기반 프로젝트를 구성하고, 패키지 매니저로 의존성을 관리하는 전체 흐름을 이해한다.

학습 목표

  1. 프론트엔드 프로젝트의 기본 구조(package.json, node_modules, src)를 이해한다.
  2. 패키지 매니저가 왜 필요한지, 어떤 문제를 해결하는지 파악한다.
  3. npm, yarn, pnpm의 등장 배경과 각각의 핵심 차별점을 시간순으로 정리한다.
  4. node_modules 구조의 변천과 의존성 해결(resolution) 방식을 이해한다.

1. 프론트엔드 프로젝트의 기본 구조

웹 프론트엔드 프로젝트의 최종 산출물은 HTML, CSS, JavaScript 같은 정적 파일이다. 브라우저가 이해할 수 있는 형태여야 하므로, 개발자가 작성한 소스 코드를 트랜스파일, 번들링, 최적화를 거쳐 변환해야 한다.

이 변환 과정을 위해 Node.js를 런타임 환경으로 사용하며, 프로젝트는 다음과 같은 구조를 갖는다.

my-project/
  package.json          # 프로젝트 메타 정보 + 의존성 선언
  package-lock.json     # 의존성 잠금 파일 (npm)
  node_modules/         # 설치된 패키지들
  src/                  # 개발자가 작성하는 소스 코드
    index.js
    App.jsx
    styles/
  public/               # 정적 에셋 (HTML 템플릿, 이미지 등)
  dist/                 # 빌드 결과물 (정적 파일)

package.json의 핵심 필드

필드역할
name패키지 이름 (npm 레지스트리에서 고유 식별자)
version시맨틱 버전 (major.minor.patch)
mainrequire() 시 진입점 파일
scripts실행 가능한 명령어 정의 (start, build, test 등)
dependencies프로덕션에 필요한 의존성
devDependencies개발 시에만 필요한 의존성

node_modules 폴더

npm install을 실행하면 package.json에 선언된 의존성이 node_modules 폴더에 다운로드된다. Node.js의 require() 함수는 다음 순서로 모듈을 탐색한다.

  1. 상대/절대 경로(./, ../, /)로 시작하면 해당 파일 경로에서 탐색
  2. 그 외에는 현재 디렉터리의 node_modules에서 탐색
  3. 없으면 **상위 디렉터리의 node_modules**로 이동하며 루트까지 반복

이 탐색 알고리즘이 워크스페이스에서 호이스팅과 팬텀 디펜던시 문제의 근본 원인이 된다.


2. 패키지 매니저의 역할

패키지 매니저는 다음 문제를 해결한다.

  • 설치: 외부 라이브러리를 npm 레지스트리에서 다운로드하여 프로젝트에 배치
  • 버전 관리: 시맨틱 버전 범위에 따라 호환 가능한 버전 선택
  • 의존성 기록: package.json과 lock 파일에 어떤 패키지가 필요한지 선언적으로 기록
  • 재현성: 다른 개발자나 CI 환경에서 동일한 의존성 트리를 재구성
  • 스크립트 실행: npm run build 같은 명령으로 개발 도구 실행을 표준화

3. 패키지 매니저의 발전 과정

3.1 npm (2010~)

Node.js와 함께 기본 설치되는 최초의 패키지 매니저이다.

  • npm v2: 중첩(nested) node_modules 구조 사용. 의존성의 의존성이 하위 폴더에 반복 설치되어 디렉터리 깊이가 극단적으로 깊어지는 문제 발생 (Windows에서 경로 길이 제한 초과)
  • npm v3: 평탄(flat) node_modules 구조로 전환. 모든 의존성을 가능한 한 최상위에 배치하여 중복 설치를 줄임
  • npm v5: package-lock.json 도입으로 재현 가능한 설치 지원 (yarn의 lock 파일 개념을 수용)
  • npm v7+: 워크스페이스 기능 추가

3.2 Yarn Classic (2016~)

Facebook, Google 등이 npm의 한계를 해결하기 위해 만든 패키지 매니저이다.

  • yarn.lock 파일로 일관된 의존성 재설치 보장
  • 병렬 설치로 설치 속도 대폭 향상
  • 오프라인 캐시 지원
  • 워크스페이스 기능 최초 도입 (모노레포 지원)
  • 단, 평탄한 node_modules 구조는 npm과 동일하여 Phantom Dependency 문제가 그대로 존재

3.3 pnpm (2017~)

기존 패키지 매니저에 불만을 가진 개발자가 만든 패키지 매니저이다.

  • 하드링크 + 심볼릭 링크 전략으로 디스크 공간 절약과 설치 속도 향상
  • 비-평탄(non-flat) node_modules 구조로 Phantom Dependency 문제 해결
  • 콘텐츠 주소 기반 스토어: 전역 스토어에 패키지를 한 번만 저장하고 하드링크로 연결
  • 워크스페이스 지원 (pnpm-workspace.yaml)

3.4 Yarn Berry (2020~)

Yarn 1.x와 완전히 다른 새로운 아키텍처이다.

  • Plug'n'Play (PnP): node_modules 폴더를 아예 사용하지 않고 압축 파일(.zip)로 의존성 관리
  • 설치 시간 최소화, Zero-Install 개념 도입
  • 엄격한 의존성 해석으로 Phantom Dependency 문제 해결

4. node_modules 구조와 의존성 해결 방식

4.1 npm v2 시절: 중첩(Nested) 구조

node_modules/
  A/
    node_modules/
      B/
        node_modules/
          C/
  • 각 패키지가 자신의 의존성을 하위 node_modules에 독립적으로 설치
  • 장점: 의존성 격리가 완벽
  • 단점: 디렉터리 깊이 폭발, 같은 패키지의 중복 설치, Windows 경로 제한 문제

4.2 npm v3+ / Yarn Classic: 평탄(Flat) 구조

node_modules/
  A/
  B/
  C/
  • 모든 의존성을 가능한 한 최상위 node_modules에 배치 (호이스팅)
  • 버전 충돌이 있는 경우에만 하위 node_modules에 중첩 설치
  • 장점: 디스크 절약, 깊이 문제 해결
  • 단점: Phantom Dependency 발생

Phantom Dependency란?

package.json에 직접 선언하지 않은 패키지를 require()로 불러올 수 있는 현상이다. 예를 들어 A만 설치했는데, A가 내부적으로 사용하는 B가 최상위 node_modules에 호이스팅되어 프로젝트에서 직접 require('B')가 가능해진다.

이는 A가 업데이트되면서 B를 더 이상 사용하지 않게 되면 갑자기 코드가 깨지는 위험이 있다.

4.3 pnpm: 비-평탄(Non-flat) 구조 + 심볼릭 링크

node_modules/
  .pnpm/                    # 가상 스토어 (Virtual Store)
    A@1.0.0/
      node_modules/
        A/                  # 실제 파일 (하드링크)
        B -> ../../B@2.0.0/node_modules/B  # 심볼릭 링크
    B@2.0.0/
      node_modules/
        B/                  # 실제 파일 (하드링크)
  A -> .pnpm/A@1.0.0/node_modules/A  # 심볼릭 링크
  • 프로젝트의 node_modules 최상위에는 직접 의존성만 심볼릭 링크로 노출
  • .pnpm/ 가상 스토어 내부에서 의존성 간의 관계를 심볼릭 링크로 구성
  • 실제 파일은 글로벌 스토어에서 하드링크로 연결하여 디스크 절약
  • Phantom Dependency 원천 차단

4.4 Yarn Berry: PnP (Plug'n'Play)

.pnp.cjs            # 의존성 맵 (패키지 이름 -> zip 경로)
.yarn/cache/         # 압축된 패키지 파일들
  axios-npm-1.6.0-abc123.zip
  lodash-npm-4.17.21-def456.zip
  • node_modules 폴더 자체가 없음
  • .pnp.cjs 파일이 의존성 해석 테이블 역할
  • 패키지는 .yarn/cache/에 zip 파일로 보관
  • Zero-Install: .pnp.cjs와 cache를 git에 커밋하면 yarn install 없이 바로 실행 가능

5. 패키지 매니저 비교 요약

비교 항목npmYarn ClassicpnpmYarn Berry
node_modules 구조평탄평탄비-평탄 (심볼릭 링크)없음 (PnP)
Lock 파일package-lock.jsonyarn.lockpnpm-lock.yamlyarn.lock
디스크 효율보통보통높음 (하드링크)높음 (zip)
설치 속도보통빠름매우 빠름매우 빠름
Phantom Dependency발생발생방지방지
워크스페이스v7+v1.0+지원지원
특징기본 내장병렬 설치콘텐츠 주소 스토어Zero-Install

핵심 정리

  1. 프론트엔드 프로젝트는 Node.js를 런타임으로 사용하여 소스 코드를 브라우저가 이해하는 정적 파일로 변환한다.
  2. 패키지 매니저는 외부 라이브러리의 설치, 버전 관리, 의존성 기록, 재현 가능한 빌드를 담당하는 필수 도구이다.
  3. npm(2010) -> Yarn Classic(2016) -> pnpm(2017) -> Yarn Berry(2020) 순으로 발전하며, 각각 이전 도구의 한계를 개선했다.
  4. node_modules평탄 구조는 Phantom Dependency 문제를 야기하며, pnpm의 비-평탄 구조와 Yarn Berry의 PnP가 이를 해결한다.
  5. 모노레포 환경에서는 워크스페이스 기능이 필수이며, 모든 주요 패키지 매니저가 이를 지원한다.

다음 단계

  • 02. npm 워크스페이스: npm의 워크스페이스 기능을 활용하여 모노레포를 구성하고, 호이스팅과 의존성 해석의 실제 동작을 확인한다.