npm에서 pnpm 전환기

미키오·2024년 9월 2일
0

Gapple

목록 보기
1/4
post-thumbnail

0. 들어가며

최근 시작한 프로젝트에서 Next Auth v5로 네이버 로그인을 구현하면서 마주한 오류이다.

[auth][cause]: OperationProcessingError: "response" body "expires_in" property must be a positive number

구글링을 해보니 NextAuth.js에 공식적으로 지원되는 인증 공급자 중 OAuth 2.0 프로토콜을 기반으로 하는 Azure AD B2C (Azure Active Directory Business to Consumer)에서 발생하는 에러였다.

위 네이버 개발자 페이지의 문의 내용처럼 expires_in 필드의 타입이 OAuth 2.0 표준인 number 타입이 아닌, 네이버에서 string으로 반환하는 문제가 존재한다.

네이버 로그인에서도 공식적으로 해당 문제를 인식하고 있으나 당장은 변경이 불가능하다는 깃허브 discussion을 보게 되어 기존 패키지의 정의된 종속성을 다른 버전이나 포크로 변경하는 오버라이딩을 진행하기로 했다.

// package.json
            
  "overrides": {
    "oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
  }

그러나 위의 방식처럼 oauth4webapi의 종속성을 Jacob Kim님의 포크 버전으로 대체하려했지만 이번에는 내 프로젝트의 npm 내부 버그로 인해 온전하지 못하다는 것을 알게 되었다.

해당 페이지에 나온 버그 내용들을 요약하자면,

  • 초기 설치 시만 적용됨:
    • overrides 속성은 패키지를 처음 설치할 때만 적용되고, 이후 설치 시에는 무시되는 경향이 있다.
  • package-lock.json의 영향:
    • 초기 npm install 실행 시 package-lock.json이 없는 경우 overrides가 정상적으로 작동한다. 하지만 이 파일이 생성된 후 다시 npm install을 실행하면 overrides가 무시되는 현상이 발생한다.
  • 업데이트 시 일관성 부족:
    • npm update를 실행할 때는 overrides가 적용되기도 하지만, 이것이 일관성 있게 적용되지 않아 사용자에게 혼란을 준다.
  • 모노레포 관리 문제:
    • 특히 대규모 모노레포에서 패키지 버전을 관리하는 데 있어 overrides가 정상적으로 작동하지 않으면, 패키지 중복이나 의존성 충돌, 보안 관련 추가적인 문제를 일으킬 수 있다.
  • CI 파이프라인에서의 문제:
    • 로컬 개발 환경에서는 overrides가 잘 작동할 수 있으나, CI 파이프라인과 같은 다른 환경에서는 overrides 설정이 제대로 적용되지 않아 보안 취약점이 발생하거나 파이프라인 실패를 초래할 수 있다.

개발 특성상 일관성과 신뢰성이 중요한데 npm의 이러한 불안정함![]
은 너무나도 큰 단점으로 다가왔다.

1. npm의 특징

npm은 JavaScript의 표준 패키지 관리자로, 모든 Node.js 환경에서 널리 사용된다. 또한 npm은 초기 설정이나 사용법이 간단하며, Node.js를 설치할 때 자동으로 포함되므로 별도의 설치 과정이 필요 없다.

필자 또한 낮은 진입장벽 덕분에 개발을 시작한 2년 동안 npm만 사용했다.

Flat 방식

또다른 npm의 특징으로는 v3 버전 이후로 종속성을 평평하게(flat) 관리하는 방식을 채택하고 있다는 점이다. 이는 node_modules 디렉터리 안에서 가능한 한 많은 패키지를 최상위 레벨에 배치하여 중복을 최소화하는 것을 목표로 한다. 그러나 이 방식은 몇 가지 문제점을 동반한다 :

디스크 사용량과 중복:

  • npm은 각 프로젝트의 node_modules가 독립적으로 존재하므로, 여러 프로젝트에서 동일한 패키지를 사용할 경우 각 프로젝트의 node_modules에 중복된 패키지가 저장된다. 이는 여러 프로젝트를 관리할 때 디스크 공간의 비효율적인 사용을 초래한다.
  • 평평한 구조는 최상위 레벨에서 가능한 많은 패키지를 공유하려 하지만, 버전 충돌이 발생하면 하위 레벨에 중복된 패키지를 생성해야 하기 때문에 본래 목적에 부합한 완벽한 중복 제거는 이루어지지 않는다.

예를 들어, 클라이언트 코드로 이루어진 프로젝트A와 서버 코드로 이루어진 프로젝트B가 있다고 가정해보자.

프로젝트 A: 웹 애플리케이션 (React)

  • 사용 패키지:
    • react - 사용자 인터페이스 구성
    • axios - 외부 API와의 HTTP 통신
    • redux - 상태 관리

프로젝트 B: API 서버 (Node.js/Express)

  • 사용 패키지:
    • express - 서버 프레임워크
    • axios - 다른 서비스와의 HTTP 통신
    • mongoose - MongoDB와의 데이터 상호작용

공통 패키지: axios

두 프로젝트 모두 axios를 사용하여 외부 서버나 서비스와 HTTP 통신을 한다. 이때 npm을 사용할 경우, axios 패키지가 각 프로젝트의 node_modules 디렉터리에 별도로 저장되어 디스크 공간이 중복 사용된다.

비효율적인 종속성 해결:

  • npm은 종속성을 해결할 때 모든 패키지의 호환 가능한 버전을 찾아 평평한 구조로 만들어야 한다. 이 과정에서 호환되는 버전을 찾지 못하면 더 많은 레벨의 중첩을 생성하게 되며, 이는 node_modules의 구조가 복잡해지고 관리가 어려워질 수 있다.
  • 또한, 이러한 복잡성은 종종 종속성 충돌이나 버전 문제를 일으킬 수 있으며, 설치 프로세스가 예측 불가능하게 될 수 있다.

예를 들어 최근에 과거 코드를 살짝 손보면서 마주했던 사례를 가져와봤다.

프로젝트 구성:

  • 프로젝트 C : React를 사용하는 웹 애플리케이션
  • 종속성 (dependency) :
    • react@16.8.0
    • react-dom@16.8.0
    • 라이브러리 library-X가 필요하며, 이는 react@15.6.2를 필요로 함

문제 상황:

프로젝트 A는 react의 최신 버전인 16.8.0을 사용하고 싶지만, 동시에 library-X를 사용하고 싶다. 그런데 library-X는 구버전인 react@15.6.2에 의존한다.

이때 npm은 어떻게 작동될까?

  • 최상위 종속성 확인: npm은 우선적으로 프로젝트의 package.json에서 선언된 react@16.8.0react-dom@16.8.0을 설치한다.
  • 충돌 발견: library-X의 종속성을 해결하려 할 때, npm은 이미 설치된 react의 버전과 library-X가 요구하는 react 버전 사이의 충돌을 발견한다.
  • 중첩된 node_modules 생성: npm은 호환 가능한 버전을 찾지 못하므로, library-X 내부의 node_modules 디렉터리에 react@15.6.2를 설치하여 버전 충돌을 해결하려고 한다.

결과:

  • 중첩 구조: 이제 프로젝트는 두 개의 다른 버전의 react를 가지고 있으며, 이는 node_modules/library-X/node_modules/react@15.6.2와 같은 구조로 저장된다.
  • 관리 복잡성: 이러한 중첩 구조는 프로젝트의 종속성 트리를 꼬이게 만들고, 버그의 원인이 될 수 있으며, 로컬에서는 괜찮을 수 있어도 빌드와 배포 과정에서 예상치 못한 문제를 일으킨다.
  • 성능 문제: 런타임에 두 버전의 react가 동시에 존재하게 되면 예상치 못한 동작이나 성능 저하를 초래한다.

이러한 이유들로 2년 간 동거동락했던 npm을 손절하기로 했다!

2. pnpm의 특징

pnpm의 공식 문서 motivation에 의하면 pnpm의 가장 큰 특징으로는 디스크 공간 절약, 설치 속도 향상, 비평평한 node_modules가 있다.

디스크 공간 절약

pnpm은 내용 주소 가능 저장소(content-addressable store)를 사용하여 디스크 공간을 절약한다. 앞선 예시처럼, 두개의 프로젝트 모두가 axios 패키지를 가지고 있어서 npm을 사용할 경우 이 종속성의 복사본이 그대로 디스크에 중복 저장된다. (100개의 프로젝트에서 하나의 종속성을 사용해도 100개의 복사본이 디스크에 저장된다.)

그러나 pnpm을 사용할 경우 종속성의 다른 버전에 대해, 달라진 파일만 저장소에 추가된다. 예를 들어, 종속성이 100개의 파일을 가지고 있고 새 버전에서 단 하나의 파일만 변경되었다면, pnpm update는 하나의 새 파일만 저장소에 추가한다. 즉, 전체 종속성을 복제하지 않는다는 뜻이다.

또한, 모든 파일은 디스크의 한 장소에 저장된다. 패키지가 설치될 때, 그 파일들은 이 장소에서 하드 링크 (파일의 물리적 위치에 대한 직접적 포인터) 되어 추가적인 디스크 공간을 사용하지 않는다. 이를 통해 같은 버전의 종속성을 프로젝트 간에 공유할 수 있다. 즉, 파일을 네트워크에서 다운로드하고 저장하는 대신, 이미 다운로드된 파일을 재사용할 수 있다는 뜻이다.

설치 속도 향상

pnpm은 세 단계의 설치 과정을 수행한다:

  1. 종속성 해결: 필요한 모든 종속성이 식별되어 저장소로 가져온다.
  2. 디렉터리 구조 계산: node_modules 디렉터리 구조가 종속성을 기반으로 계산된다.
  3. 종속성 링킹: 모든 남은 종속성이 저장소에서 node_modules로 하드 링크된다.

비평평한 node_modules 디렉터리 생성

npm이나 Yarn Classic을 사용할 때, 모든 패키지는 모듈 디렉터리의 루트로 호이스팅된다. 그 결과, 소스 코드는 프로젝트에 종속성으로 추가되지 않은 종속성에 접근할 수 있다.

기본적으로 pnpm은 심볼릭 링크(파일 또는 디렉토리의 경로를 가리키는 파일)를 사용하여 프로젝트의 직접 종속성만 모듈 디렉터리의 루트에 추가한다.

이는 다음과 같이 작동한다.

프로젝트 설정:

  • 프로젝트 D
  • 종속성:
    • express 버전 4.17.1
    • lodash 버전 4.17.20

설치 과정:

  1. 종속성 해결: pnpmexpresslodash의 적절한 버전을 식별한다.
  2. 중앙 저장소에 저장: 해당 패키지들은 중앙 콘텐츠 주소 가능 저장소에 저장된다.
  3. 심볼릭 링크 생성:
    • node_modules의 루트에는 expresslodash에 대한 심볼릭 링크가 생성된다.
    • 이 링크들은 저장소에 저장된 실제 위치를 가리킨다.

파일 구조:

/project
  /node_modules
    /express -> .pnpm/express@4.17.1/node_modules/express
    /lodash -> .pnpm/lodash@4.17.20/node_modules/lodash

이 구조에서 expresslodash는 심볼릭 링크를 통해 접근된다. 이렇게 함으로써 pnpm은 중복 저장을 방지하고, 각 패키지가 필요로 하는 종속성을 정확히 제공하면서 프로젝트의 node_modules 디렉터리를 깔끔하게 유지할 수 있다.

3. npm에서 pnpm으로 마이그레이션하기

npm에서 pnpm으로 마이그레이션하는 과정은 상당히 간단하다.

먼저, pnpm을 설치해야 한다.

npm install -g pnpm

기존의 npm 프로젝트에서 pnpm으로 전환하기 전에, 기존의 node_modules 폴더와 package-lock.json 파일을 삭제하는 것이 좋다. 이는 pnpm이 자체적인 방식으로 종속성을 관리하고 새로운 종속성 트리를 생성하기 때문이다.

rm -rf node_modules
rm package-lock.json

이후 프로젝트 디렉토리에서 다음 명령을 실행하여 pnpm을 사용해 종속성을 설치한다.

pnpm install

해당 명령은 npm install 처럼 이 명령은 package.json에 명시된 모든 종속성을 설치한다. 또한 pnpm의 콘텐츠 주소 가능 저장소를 사용하여 종속성을 관리하게 해준다.

pnpm으로 전환한 후, 프로젝트가 정상적으로 작동하는지 확인한다.

pnpm run build
pnpm start
pnpm run dev


다행히 네이버와 무사히 연결되었다!

📚 Bibliography

pnpm 공식 문서

https://pnpm.io/

npm 공식 문서

https://www.npmjs.com/

네이버 개발자 포럼

https://developers.naver.com/forum/posts/35298

Jacob.kim

https://jacob.kim/blog/next-auth-expires-in/ko

github discussions

https://github.com/npm/cli/issues/5850

https://github.com/nextauthjs/next-auth/discussions/9313

profile
교육 전공 개발자 💻

0개의 댓글

관련 채용 정보