@types/react 한테 폭행당하기 (react 18 간접의존성 이슈)
✏️

@types/react 한테 폭행당하기 (react 18 간접의존성 이슈)

Description
누구인가.. 누가 wildcard를 사용하였는가..
Published
Published April 13, 2022

펑펑

얼마전 리액트 18이 릴리즈 되었는데 관련해서 재밌는 이슈가 있어서 기록차 글을 남겨봅니다.
현재 사내에서는 Monorepo로 모든 프로젝트를 하나의 Repo로 관리하고 있습니다. 장단이 있겠지만 현재 구조의 단점으로는 플로우 정책에 따라 브랜치에 하루에도 수십수백 개의 커밋이 머지되어 흘러들어오기 때문에 인내심과 툴의 도움 없이는 히스토리를 추적하기가 매우 매우 힘이 듭니다. 😂
며칠 전 수정사항이 없음에도 제가 담당하고 있는 프로젝트(Next.js)의 빌드가 펑펑 터지기 시작했습니다. 빠르게 톺아보니 react 18 버전의 타입 선언을 참조하고 있어서 모노리포에서 디팬던시 컨플릭트가 원인인가 보다 하고 히스토리를 봤더니 특별히 문제가 될건 없어 보였습니다.

@types/react 는 잘못이 없다.

모든 디팬던시를 제거하고 다시 설치해보니 로컬에서도 빌드가 터지기 시작합니다. (현재 저희 회사 repo는 몇 가지 이유 때문에 lock 파일을 ignore 하여 사용합니다 ^^;)
아직 리액트 18 버전의 스펙이나 바뀐 점들도 제대로 보지 않았고 지금 당장 업데이트할 계획이 없기 때문에 에러 자체보다는 에러의 원인을 조금 더 파보기 시작합니다.
❯ yarn why @types/react ├─ @types/emoji-mart@npm:3.0.9 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/hoist-non-react-statics@npm:3.3.1 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/loadable__component@npm:5.13.4 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/reach__router@npm:1.3.10 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-beautiful-dnd@npm:13.1.2 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-beforeunload@npm:2.1.1 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-copy-to-clipboard@npm:5.0.2 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-dom@npm:17.0.13 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-dom@npm:17.0.14 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-dom@npm:18.0.0 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-is@npm:17.0.3 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-lazy-load-image-component@npm:1.5.2 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-lines-ellipsis@npm:0.15.1 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-redux@npm:7.1.23 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-router-dom@npm:5.3.3 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-router@npm:5.1.18 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-scroll@npm:1.8.3 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-slick@npm:0.23.8 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-swipeable-views@npm:0.13.1 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-syntax-highlighter@npm:11.0.4 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-syntax-highlighter@npm:11.0.5 │ └─ @types/react@npm:18.0.1 (via npm:*) │ ├─ @types/react-textarea-autosize@npm:4.3.6 │ └─ @types/react@npm:18.0.1 (via npm:*) │ └─ @types/react-transition-group@npm:4.4.4 └─ @types/react@npm:18.0.1 (via npm:*)
그리고 많은 리액트 관련 디팬던시의 타입들이 @types/react 를 wildcard로 보고 있단것을 발견합니다.
즉, 수많은 패키지들의 @types@types/react 의 버전을 * 로 명시하여 리액트 18의 릴리즈에 맞게 릴리즈 된 최신버전(18버전)의 @types/react 를 참조하여 에러가 발생하였습니다.
예를들면 @types/react-dom 패키지의 디팬던시로 @types/react: "*"가 명시되어 있고 패키지 매니저는 가장 최신버전인 18버전의 @types/react 를 다운로드 하게되고 * 로 명시된 모든 패키지들은 18버전을 바라보면서 수많은 에러를 뱉었습니다.
메이저 업데이트라 react와 @types/react한테 뭐라 하기도 그렇고 분명 이유가 있을 것 같은데 다른 types들이 왜 wildcard로 버전을 명시했는지 궁금합니다.

일단 해결은 하자

notion image
Definitely Typed 이슈에 가보니 역시나 관련 이슈가 많이 등록된 걸 볼 수 있었습니다.
원인을 파악한 후 어떻게 해결을 할까 의견을 나누다가 우선 18버전으로 넘어가기 전까지는 yarn의 resolutions 을 이용해서 @types/react의 버전을 강제하기로 합니다. (npm의 경우 "overrides" 를 이용하면 될 것 같습니다.)
... "resolutions": { "@types/react": "17.0.42" }, ...
수정 후 기대했던 대로 17버전을 바라보아 문제를 해결할 수 있었습니다.
물론 업데이트 이전부터 lock 파일이 관리되었다면 큰 문제가 없었을 것이고 문제가 발생하더라도 lock파일의 * 의 매칭을 수정하면(근데 이건 생각해보니 수동으로 수정하면 안될것 같긴하네요.) 해결 가능하겠지만 앞서 말씀드렸듯이 현재 사내 모노리포는 정책상 lock파일을 커밋하지 않기때문에 발생한 이슈라고 생각이 되었습니다.

lock lock

아마 같은 이슈를 겪는 분들은 lock파일을 관리하지 않았거나 새로 프로젝트를 시작했는데 react를 17버전을 사용하는 경우일것으로 추측 됩니다.
디팬던시의 버전 명시를 *latest 혹은 , 명확한 버전이 아닌 version range를 사용하고 시맨틱 버저닝을 제대로 지키지 않았을 경우 등의 상황에서 프로젝트의 패키지의 버전을 바꾸지 않았음에도 불구하고 얼마든지 영향을 줄 수 있습니다. 특히 디팬던시의 디팬던시와 같이 간접적인 의존성은은 더욱 까다로울 수 있겠습니다.
lock파일의 필요성에 대해서 이론적으로만 대충 알았지 크게 와 닿지 않았었는데 역시 사람은 직접 몸으로 겪어봐야지 확실하게 알게 되는것 같습니다.