Frontend

엉켜있던 Peer dependencies 디버깅하기

mechaniccoder 2024. 4. 13. 18:39

최근 개발 환경에서 아래와 같은 에러를 마주했습니다.

No QueryClient set, useQueryClientProvider to set one

 

에러 정보를 기반으로 추론했을 때 useSuspenseQuery를 호출하려 하는데 루트에 QueryProvider가 없어서 발생하는 에러인 것 같습니다. 그런데 이상합니다. 분명 루트에는 QueryProvider가 감싸져 있습니다.  그런데도 계속 에러가 발생하는 것을 보고 packages 관련 문제라는 것을 직감했습니다. 빠르게 방향을 잡을 수 있었던 이유는 과거에 모노레포 환경에서 node_modules packages hoisting 문제를 겪고 해결한 경험이 있었기 때문입니다. 그때는 yarn v1을 패키지 매니저로 사용했었고 현재는 pnpm을 사용하지만 같은 맥락의 에러라고 판단했습니다. (엄밀히 말하면 위 에러는 node_modules hoisting이 원인은 아니었습니다.)

 

가장 먼저 node_modules/.pnpm에서 설치된 @tanstack/react-query를 확인해 봤습니다. 2개의 @tanstack/react-query가 설치되어 있는 것을 확인했습니다. 위의 @tanstack/react-query는 react-dom@18.2.0, react@18.2.0 두 개의 패키지가 peer dependencies이며 아래의 @tanstack/react-query는 두 개의 패키지에 더해 react-native@0.73.6을 추가로 peer dependencies로 가집니다. (How peers are resolved)

node_modules
  .pnpm
    @tanstack+react-query@4.36.1_react-dom@18.2.0_react@18.2.0
    @tanstack+react-query@4.36.1_react-dom@18.2.0_react-native@0.73.6_react@18.2.0

 

여기까지 얻은 정보를 바탕으로 에러의 원인을 추론할 수 있습니다. useSuspenseQuery를 import 할 때 사용한 @tanstack/react-query와 루트에서 QueryProvider를 import 할 때의 @tanstack/react-query가 서로 다르기 때문에 useSuspenseQuery에서 QueryProvider의 prop으로 넘긴 QueryClient를 찾을 수 없어서 에러가 발생한 것입니다. 다이어그램으로 살펴보죠.

 

그럼 위의 추론이 맞는지 pnpm-lock.yml에서 확인해보겠습니다. useSuspenseQuery를 가지는 @suspensvie/react-query에서 react-dom@18.2.0, react@18.2.0, react-native@0.73.6을 peer dependencies로 가지는 @tanstack/react-query를 사용하고 있습니다.

  /@suspensive/react-query@1.18.3(@suspensive/react@1.18.3)(@tanstack/react-query@4.36.1)(react@18.2.0):
    peerDependencies:
      '@suspensive/react': ^1.18.3
      '@tanstack/react-query': ^4
      react: ^16.8 || ^17 || ^18
    dependencies:
      '@suspensive/react': 1.18.3(react@18.2.0)
      '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0)(react-native@0.73.6) // peer dependencies
      react: 18.2.0

루트에서 사용한 @tanstack/react-query는 react-native@0.73.6을 peer dependency로 가지지 않기 때문에 위에서 추론한 원인이 사실임을 확인했습니다.

      '@tanstack/react-query':
        specifier: ^4.32.6
        version: 4.36.1(react-dom@18.2.0)(react@18.2.0)

해결하는 과정은 간단했습니다. 아무 패키지나 삭제하고 재설치하면 pnpm이 peer dependencies를 재조정하기 때문에 같은 peer dependencies를 가지도록 수정됐습니다. 

그런데 뭔가 이상합니다. peer dependencies로 react, react-dom은 이해하겠는데 react-native는 뭘까요? 모바일이 아닌 웹에서 개발하기 때문에 react-native를 peer dependencies로 가질 이유가 없어보입니다. 원인을 찾기 위해 @tanstack/react-query package.json에 peer dependency field를 확인해봤습니다.

  "peerDependencies": {
    "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
    "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
    "react-native": "*"
  },
  "peerDependenciesMeta": {
    "react-dom": {
      "optional": true
    },
    "react-native": {
      "optional": true
    }
  }

 

react-native는 optional이라 문제 될 건 없습니다. 자동으로 peer dependency를 설치했더라도 optional peer dependency는 제외하고 설치되기 때문입니다.

여기서 react-native를 peer dependency로 가지는 다른 패키지가 있다는 것을 추론할 수 있습니다. 다시 한번 pnpm-lock.yml 파일을 확인하여 어디서 react-native를 peer dependency로 가지는지 보겠습니다.

2024.4.14 업데이트
manual하게 찾는 것 대신에 아래 cli를 활용해서 react-native를 dependency로 가지는 패키지를 쉽게 찾을 수 있습니다.
$ pnpm list -r --depth Infinity | grep "react-native"​

 

찾았습니다! @react-spring/native 패키지에서 react-native를 peer dependency로 가지고 있더군요. 이 녀석 때문에 react-native를 optional로 가지는 다른 패키지에 영향을 주고 있던 것이었습니다. (앞서 @tanstack/react-query에 react-native가 peer dependency로 명시됐던 것)

  /@react-spring/native@9.7.3(react-native@0.73.6)(react@18.2.0):
    peerDependencies:
      react: ^16.8.0  || >=17.0.0 || >=18.0.0
      react-native: '>=0.58'
    dependencies:
  // ...
      react-native: 0.73.6(@babel/core@7.24.4)(@babel/preset-env@7.24.4)(react@18.2.0)

react-spring 문서를 확인해 보면 react-spring을 설치하는 경우 모든 종류의 패키지를 다운로드하기 때문에 그 안에 react-native에 대한 패키지도 같이 설치가 됩니다. 따라서 아래처럼 웹에 대한 패키지만 설치하는 것을 권장하고 있습니다.

# Install the entire library
pnpm install react-spring

# or just install your specific target (recommended)
pnpm install @react-spring/web

react-spring을 제거하고 @react-spring/web을 설치하여 react-native와 관련된 모든 패키지가 제거됐습니다!

 

마치며

발생하는 문제는 여러 계층의 원인이 있습니다. 이를 타고 올라가 보면 근본적인 원인을 발견하게 되고 이를 해결하는 것이 바로 문제를 해결했다라고 말할 수 있을 것입니다. 이번에 공유드린 경험을 빗대어 보면 다음과 같습니다.

문제: @tanstack/react-query 에러가 발생했다.
원인: peer dependency(react-native)가 다른 @tanstack/react-query가 2개가 존재한다.

문제: 왜 react-native를 peer dependency로 가질까?
원인: react-spring을 설치할 때 @react-spring/native가 같이 설치되는데 이 패키지에서 react-native를 peer dependency로 가지기 때문이다.

해결: react-spring을 제거하고 @react-spring/web을 설치해 @react-spring/native가 같이 설치되지 않도록 한다.

사실 @tanstack/react-query를 원인으로 해결하여 끝내려 했는데 몇 주가 지난 지금 찝찝함에 다시 디버깅해 보니 근본적인 원인을 파악할 수 있었습니다. 근본적인 원인을 찾아 해결하려고 하지 않았던  것을 반성하고 이번 경험을 통해 문제해결력을 성장시킬 수 있어서 제겐 값진 경험이었던 것 같습니다.