pnpm 모노레포 React 19 마이그레이션, 타입 충돌 문제 완벽 해결!
React 19 마이그레이션 중 pnpm 모노레포 환경에서 타입 오염(Type Pollution)으로 인한 타입 충돌 문제 발생
pnpm의 독특한 node_modules 구조와 호이스팅(Hoisting) 메커니즘 분석을 통해 문제의 근본 원인 파악
호이스팅 차단 및 packageExtensions를 활용하여 타입 충돌 문제 해결 및 안정적인 마이그레이션 환경 구축
tsconfig.json의 paths 설정, shared-workspace-lockfile=false 등 대안 검토 및 최종 해결책 도출
pnpm node_modules 구조 심층 분석
pnpm은 글로벌 스토어(Global Store)와 하드 링크(Hard Link)를 통해 디스크 공간을 절약하고, 심볼릭 링크(Symbolic Link) 기반의 엄격한 격리를 제공한다. pnpm의 node_modules는 3개의 레이어로 구성된다.
Layer 1: 각 앱의 node_modules(Strict Interface Layer)는 package.json에 명시된 의존성만 허용하며, 심볼릭 링크를 사용해 독립성을 보장한다.
Layer 2: .pnpm 가상 저장소(Virtual Store)는 모든 의존성을 격리 저장하며, 하드 링크를 통해 실제 패키지를 참조한다. peerDependency 조합에 따라 별도 컨텍스트를 생성하여 의존성 충돌을 방지한다.
Layer 3: 내부 호이스팅 레이어(Internal Hoisting)는 pnpm의 엄격한 구조를 따르지 않는 패키지를 위한 안전망 역할을 하지만, 동일 패키지의 여러 버전 중 하나만 호이스팅되어 타입 충돌의 원인이 된다.
타입 오염(Type Pollution) 문제의 근본 원인
React 19 마이그레이션 과정에서 발생한 타입 충돌은 pnpm의 호이스팅(Hoisting) 메커니즘과 TypeScript의 모듈 해석 방식이 결합되어 발생했다. TypeScript는 파일의 물리적 경로를 기준으로 상위 폴더를 탐색하며, Layer 3(호이스팅 레이어)에서 예상치 못한 버전의 @types/react를 참조하게 되면서 타입 에러가 발생했다.
문제 상황: React 18 앱에서 React 19의 @types/react를 참조하여 타입 불일치(Type Mismatch) 발생
원인 분석: Layer 3에 호이스팅된 @types/react의 버전이 문제
결론: pnpm의 node_modules 구조에 대한 깊이 있는 이해가 필요하다.
호이스팅 차단과 packageExtensions를 활용한 해결
타입 충돌 문제 해결을 위해, 먼저 호이스팅(Hoisting)을 차단하여 Layer 3에서 @types/react가 호이스팅되지 않도록 설정했다. 하지만, 이로 인해 @sentry/react와 같은 서드파티 라이브러리에서 React 타입을 찾지 못하는 새로운 문제가 발생했다.
1차 해결: .npmrc 설정을 통해 @types/react와 @types/react-dom의 호이스팅을 차단
2차 문제: 호이스팅 차단으로 인해 @sentry/react에서 React 타입(React Type)을 찾지 못해 any로 추론
최종 해결: packageExtensions를 사용하여 @sentry/react에 @types/react를 명시적으로 주입하여 의존성 문제를 해결했다.
packageExtensions의 한계와 대안 검토
packageExtensions를 통해 타입 오염 문제를 해결했지만, 누락된 패키지를 수동으로 주입해야 하는 유지보수(Maintenance)의 어려움이 존재한다. pnpm 공식 문서에서 제시하는 @pnpm/plugin-types-fixer 플러그인은 현재 실무 적용에 어려움이 있다.
packageExtensions의 한계: 누락된 의존성을 수동으로 관리해야 함
대안 1: tsconfig.json의 paths 설정: 모듈 해석 경로를 변경하는 방식, 호환성 에러 발생 가능성
대안 2: shared-workspace-lockfile=false 설정: 의존성 격리, 모노레포의 장점 상실
결론: 현재 환경에서는 packageExtensions가 최적의 해결책이며, 향후 pnpm v11 릴리즈를 통해 플러그인 사용을 고려할 수 있다.
React 타입 활용 방식에 따른 문제 발생 여부
@sentry/react와 @tanstack/react-query의 상반된 결과는 각 라이브러리가 React 타입을 코드 내에서 어떻게 활용하는지에 기인한다. @sentry/react는 JSX 컴포넌트에서 React 타입을 직접 사용하므로, 타입이 any로 추론될 경우 JSX 컴포넌트 타입 검사(JSX Component Type Check)에 실패하여 에러가 발생한다.
@sentry/react: JSX 컴포넌트에서 React 타입을 사용, 타입 에러 발생
@tanstack/react-query: 인터페이스나 훅 타입 참조에 React 타입을 사용, skipLibCheck 옵션으로 인해 에러 무시
핵심: React 타입 사용 방식에 따라 타입 에러 발생 여부가 달라진다.