개발 블로깅/오늘의 TIL

Yarn Workspace에서 package.json 틸드(~), 캐럿(^) 버전 명시 주의할 점

Hello이뇽 2022. 1. 26. 01:15

 

https://morioh.com/p/0c72d7b2974c

 

yarn workspace를 이용해서 모노레포를 구축하던 중, 'package.json 버전 명시'로 인해 발생한 문제가 있었다.

지금까지는 크게 신경쓰지 않던 요소였는데, 평소 간과하게 생각하던 녀석으로 인해 이번에 처음으로 크게 데여본 경험으로.. 모노레포 작업을 하며 좋은 인사이트를 얻은 것 같아 블로그로 정리해 보려고 한다.

 

이슈 배경

현재 yarn workspace에서는 yar berry(2.0)를 쓰고 있다.  yarn berry를 이용하게 되면 Zero install을 통해 의존성 패키지들을 더 가볍고 빠르게 설치 및 이용할 수 있다. (여기서는 zero install이 무엇인지 다루지 않는다.)

 

그러나 지금 작업 중인 모노레포에서는 yarn workspace 환경 세팅에 zero install까지 고려하여 모노레포 시스템을 구축하기에는 이미 사용 중인 의존성 패키지들이 너무 많고 Trouble Shooting 요소가 너무 많다.
그래서 우선 기존의 node_modules를 이용해 모노레포 환경을 구축한 뒤, 이후에 어느 정도 안정화가 되면 zero install로 전환하는 작업을 계획 중이다.

 

따라서, 현재는 각 서비스 단에서 공통으로 사용 중인 중복되는 모듈들은 최상단 node_modules에서 관리하고, 각 서비스에서만 사용되는 모듈은 각 서비스 디렉토리 내 node_modules에 설치되어 사용된다.

├─ node_modules
│  ├─ ...
│  ├─ ...
│  └─ ...
│
├── services/
│  ├─ serviceA
│  │  ├─ node_modules
│  │  ├─ ...
│  │  └─ ...
│  ├─ serviceB
│  │  ├─ node_modules
│  │  ├─ ...
│  │  └─ ...
...

 

위 디렉토리처럼, 최상단 root의 node_modules에는 services/ 안에 있는 각 service에서 사용되는 중복되는 모듈들을 최상단으로 호이스팅하여 사용되고, serviceA 혹은 serviceB 내에서만 각각 사용되는 모듈은 각 node_modules에서 설치되어 사용된다.

 

이슈 발생

그러던 중, serviceA에서 사용 중인 react-router-dom에 약간의 문제가 있었는데, 우선 현재 yarn workspace 내에서 react-router-dom 설치된 현황은 아래와 같다.

 

ServiceA

// service/serviceA/package.json
{
  ...
  "dependencies": {
    "react-router-dom": "^5.2.0",
    ...
  },
  "devDependencies": {
    "@types/react-router-dom": "^5.1.7",
    ...
  }
}

 

최상단 root의 node_modeuls

// node_modules/react-router-dom/package.json
{
  "name": "react-router-dom",
  "version": "6.2.1",
  ...
 }
// node_modules/@types/react-router-dom/package.json
{
    "name": "@types/react-router-dom",
    "version": "5.3.3",
}

 

최상단 node_modules에서 관리되고 있는 react-router-dom 버전은 "6.2.1" 버전이고, serviceA에서 사용 중인 버전은 "5.2.0"으로, Major 버전이 완전히 다른 상태이다.

 

따라서, serviceA의 react-router-dom은 serviceA 내 node_modules를 참조해서 써야 하는데, 이상하게 "5.x.x" 버전 대에는 존재해야 할 react-router-dom 기능들이 VScode에서 "not defined"로 나오는 이슈를 접하게 되었다.

 

VScode에서 serviceA 코드 내에서 react-router-dom을 찍어보면, serviceA의 node_modules에 있는 모듈로 참조하는 것이 아니라, root의 react-router-dom(v6.2.1)을 참조하고 있었다. 그래서 "6.x.x"부터 사라진 기능들을 코드에서 가져와 쓰려고 하니 "not defined"로 나오고 있었다.

 

 

https://blog.readytomanage.com/what-is-a-good-problem-solving-assessment

 

 

문제 접근

특이한 점은, 실제 Build를 돌렸을 때는 문제가 발생하지 않았고 정상적으로 실행까지 되는 것을 보니, 단순 VScode가 모듈 참조를 잘못하고 있는 것이란 것을 알 수 있었다.  

 

serviceA 내 node_modules도 들춰보니까, 우선 정상적으로 react-router-dom(v5.2.x)도 설치되어 있다.

 

VScode 설정 문제라고 판단하고 원인 파악을 위해 구글링하기 시작했으나, 관련된 내용이 도통 하나도 나오지 않는다.
그나마 관련이 있을까 싶었던 것은 Eslint의 workingDerectories 설정이었는데.. 아무리 봐도 모듈 경로 잘못 참조하는 거랑은 Eslint랑은 연관이 없어 보였다.. (쭉 보니 결국 Eslint Rule 지정 범위를 세팅하는 정도는 옵션이었을 뿐이었다.)

 

그렇게 구글링으로 계속 시간을 허비하다가 문득 다른 모듈들도 동일한 증상인지 확인해보았는데, 동일한 serviceA 내 동일한 File에서 사용 중인 reactreact-dom은 정상적으로 serviceA 내 node_modules를 참조하고 있었다..

 

"흐미..뭐지...?!"

 

VScode 설정 문제면 다른 모듈들도 문제가 발생해야 하는데, 다른 모듈들은 문제가 없고 react-router-dom 모듈만 발생하고 있다니..

 

react-router-dom 모듈 자체의 어딘가 문제가 있다고 판단이 되었으나, VScode 환경 설정 관련해서 특정 모듈에서만 발생하는 이슈를 어떻게 접근을 해야 할지 감이 오질 않았다..🤯

 

혹시나 이러한 증상에 관련해 구글링을 해보던 중(구글링 키워드 잡는 것도 너무 어려웠다...), 우연히 'Typescript 환경을 위한 @types 모듈이 자기 자신에 해당되는 모듈을 찾을 때, 자신의 경로에서부터 상단으로 탐색을 한다'는 Stack Overflow의 한 댓글을 보게 되었다.

 

이 점이 수상하여 @types/react-router-dom을 들추어 보았다.

 

 

없다..! serviceA에 '@types/react-router-dom'이 없다..!!

 

 

root node_modules에는 @types/react-router-dom 5.3.3 버전이 잘 설치되어 있으나, serviceA에는 5.1.7 버전이 존재하지 않았다.어떻게 된 일이지..? serviceA의 @types/react-router-dom을 다시 삭제 후 설치해 보아도 node_modules에 생성되지 않는다.

 

따라서 VScode가 serviceA의 node_modules 안에 @types/react-router-dom(v5.1.7)이 없기 때문에, 상단의 root node_modules의 @types/react-router-dom(v5.3.3)을 참조한 것이었고, 자신과 같은 경로에 있는 root의 react-router-dom(v6.2.1)을 바라보게 되면서 발생했던 문제였다.

 

그래서 VScode 에디터 자체에서는 에러로 인식을 했지만, 실제 빌드할 때는 serviceA 내 react-router-dom(v5.2.0)을 정상적으로 사용해서 빌드를 하기 때문에 문제가 없었던 것이었다.

 

그렇다면 왜 serviceA 내 node_modules에 @types/react-router-dom(v5.1.7)이 생성되지 않는 것일까?

 

원인 파악 및 해결

원인은 package.json의 버전 명시 중에 캐럿(^)에 문제가 있었다.

기본적으로 package.json에 각 모듈 버전을 명시할 때, 틸드(~), 캐럿(^), 고정 버전으로 명시를 할 수가 있다.

💡 틸드? 캐럿?
틸드 - "react": "~16.0.1"
캐럿 - "react": "^16.0.1"
고정 - "react": "16.0.1"

틸드, 캐럿, 고정 명시마다, 해당 버전이 업데이트돼서 새롭게 인스톨될 때, 새롭게 업데이트해서 받아들이는 버전 범위가 달라집니다.
틸드와 캐럿은 인스톨 시, Major 버전이 바뀌지 않는 범위 내에서 지정한 버전 중 가장 최신 버전을 설치합니다.

💡 Major? Minor?
Major Version- 명시된 버전에서 제일 앞자리. ex(1.x.x)
Minor Version - 명시된 버전에서 2번째 자리. ex(x.1.x) 
Patch Version - 명시된 버전에서 3번째 자리 ex(x.x.1)

Minor, Patch 버전의 변경사항은 주로 버그 해결이나 내부 코드 개선에 대한 사항이라, 이전 버전과의 사용성 면에서 큰 차이가 없습니다.
Major 버전의 변경은 해당 모듈에 대한 큰 변경사항이 있을 때 해당되므로, 이전 버전과의 사용성에 큰 차이가 발생할 수 있습니다.

 

serviceA에서 @types/react-router-dom 버전을 "^5.1.6"로 명시를 하니, major 버전의 이전 버전까지( > "6.x.x") 모두 호환을 하는 것으로 판단되었고, 그 범위가 root node_modules의 @types/react-router-dom(v5.3.3)을 포함하고 함께 호환된다고 판단되어 root node_modules로 호이스팅을 시켜버린 것이었다..

 

serviceA의 package.json에 @types/react-router-dom(^5.1.7)에서 캐럿(^)을 빼고, "5.1.7"로 다시 설치해보니, 정상적으로 serviceA에 @types/react-router-dom이 설치가 되었고, 추가로 VScode도 정상적으로 serviceA의 react-router-dom을 참조하는 것을 확인할 수 있었다.

 

yarn workspace에선 예민한 버전 관리...

사실 일반적인 단일 레포였더라면, 별도 호이스팅 되고 할 것이 없기 때문에 틸드(~), 캐럿(^)을 그렇게 큰 신경을 쓸 필요 없이 잘 사용해 왔을 것인데, 이번에 yarn workspace에서 버전 명시로 인해 꽤나 고생한 좋은 케이스 였던 것 같다...

yarn workspace가 중복되는 모듈을 상단으로 알아서 호이스팅 처리를 하는 만큼, 모노레포 안에서는 버전 관리에 신경을 제대로 써야겠다...
어쩌면 편하고 좋아 보이지만, 원래 알아서 해주고 편한 것들이 문제가 발생했을 때 더욱 골치더라..(다른 말로, 독 안에 든 성배..)

 

 

반응형