TL;DR: .npmrc 파일을 만들고
node-linker=hoisted
를 추가할 것.
Expo 로 리액트 네이티브 프로젝트를 빌드하는데 갑자기 에러가 난다.
/home/expo/workingdir/build/android/app/build/generated/autolinking/src/main/java/com/facebook/react/PackageList.java:14: error: cannot find symbol
import expo.core.ExpoModulesPackage;
^
symbol: class ExpoModulesPackage
location: package expo.core
// ... (생략) ...
/home/expo/workingdir/build/android/app/build/generated/autolinking/src/main/java/com/facebook/react/PackageList.java:72: error: cannot find symbol
new ExpoModulesPackage(),
^
symbol: class ExpoModulesPackage
location: class PackageList
2 errors
오류를 해결하기 위해 여러 가지 방법을 시도해 보았다. EAS 빌드 캐시를 초기화 하기도 하고(--clear-cache
) pnpm-lock.yaml 파일을 이전 버전으로 사용해서 그런가 싶어서 lockfile 강제 재생성도 하고, expo doctor 도 돌려보고... 그래도 여전히 위의 에러가 계속 나오면서 빌드가 되지 않았다.
그러다 이 깃허브 이슈에서 나와 같은 이슈를 가진 사람들을 찾았다. 나와 그 사람들의 공통점은 react-native 를 쓰면서 pnpm 을 패키지 매니저로 사용하는 사람들이었다.
node-linker=hoisted
를 .npmrc 파일에 추가하고 나니 빌드가 성공했다!
일반적으로 npm 이나 yarn 은 프로젝트에 필요한 모든 라이브러리를 복사해서 node_modules 폴더 안에 전부 펄쳐놓는다. 그래서 폴더 구조가 비교적 평평하고 뚱뚱하고 단순하다. React Native 의 안드로이드 빌드 시스템은 이 방식에 맞춰 설계되었다.
하지만 pnpm 은 심볼릭 링크 구조를 사용한다. 모든 라이브러리의 원본은 컴퓨터의 한 곳(보통 ~/Library/pnpm/store
)에 저장하고, 프로젝트의 node_modules 폴더에는 실제 파일 대신 그 원본을 가리키는 바로가기(심볼릭 링크)만을 생성한다.
여기서 문제가 발생하는데, React Native 의 안드로이드 빌드 시스템(Gradle)이 이 심볼릭 링크를 이해하지 못하는 것이다.
Gradle 이 빌드를 위해 expo-dev-client
같은 라이브러리의 네이티브 코드(.java 파일 등)를 찾으러 node_modules 에 갔는데 실제 파일은 없고 "조오기 가면 있음" 이라는 쪽지만 있으니 파일을 찾을 수 없다는 에러를 내뱉는 것이다.
해결책: .npmrc
파일과 node-linker: hoisted
.npmrc
파일은 pnpm 의 동작 방식을 설정하는 파일이다. 그리고 여기에 node-linker=hoisted
라고 적어주는 것은 pnpm 에게 이렇게 말하는 것과 같다.
"pnpm아, 너의 효율적인 쪽지(바로가기) 방식은 정말 훌륭하지만, 이 React Native 프로젝트는 그걸 이해 못 하니까, 이번만은 번거롭더라도 npm이나 yarn처럼 필요한 라이브러리 전부를 복사해서 내 책상(node_modules) 위에 직접 펼쳐놔 줘."
즉 pnpm 이 node_modules 폴더를 npm/yarn 과 똑같은 평평한 구조로 만들도록 강제하는 옵션인 것이다. 이렇게 하면 Gradle 이 node_modules 에서 바로가기가 아닌 실제 파일들을 찾을 수 있게 되므로 빌드 에러를 해결할 수 있었다.
사실 위의 깃허브 이슈 글을 보고 빌드 에러 자체는 금방 해결했지만 이 옵션이 뭔지, pnpm 의 동작을 어떻게 바꾸는 건지 찾아보는 데 시간이 더 많이 걸렸다. 모든 라이브러리를 중앙 저장화하는 pnpm 의 신념을 좋아해서 평소에 pnpm 으로 개인 프로젝트를 구성하는데 lockfile 에서 문제를 겪은 적은 여러 번 있었지만 node_modules 구조가 달라서 고생한 건 처음이었다.
그리고 이렇게 다른 구조 때문에 문제를 겪는 사람들이 많아서인지 pnpm 공식 홈페이지에도 해당 솔루션이 소개되어 있었다. 링크 에 보면 ghost dependency와 같이 node_modules 관련한 문제가 있는 경우 node-linker=hoited
옵션을 줘서 node_modules 를 flat 하게 만들으라고 소개하고 있다. 역시 돌고 돌아 공식문서가 정답이라는 건 변함 없는 진리인가보다.