개발/리액트

[리액트] 유령 의존성부터 시작된 yarn berry 도입기

핸디(Handy) 2022. 2. 16. 22:40

동료 개발자의 기능을 확인하다가 제 환경에서만 동작하지 않는 에러를 발견했습니다.

이번 글에서는 

문제 발생부터 원인 파악, 그리고 해결한 방법. 그다음에 유령의존성이 발생한 이유,  yarn berry를 도입하여 휴먼에러를 방지한 후기에 대해 정리해보았습니다.

문제 발생

문제의 발견은 git pull를 하고 제 컴퓨터에서 yarn start를 한 시점부터 시작됩니다.

특정 기능이 추가돼서 확인차 기능을 사용했는데 다음과 같은 에러와 함께 기능이 동작을 안 합니다.

[Mobx] Observable arrays cannot be frozen

해당 에러가 발견된 위치를 추적해보니 immer의 produce를 사용하는 코드에서 발생했습니다.

에러 로그를 보면 Mobx의 Observable arrays는 frozen 될 수 없다는 것입니다.

잠깐! 여기서 frozen이 뭘까요?

Object.freeze()

Object.freeze()는 객체를 동결하는 메소드입니다. 일반적으로 immutable 한 객체를 만들 수 있도록 하는 메소드인데요.

아직까지 프로젝트에 사용해본 적이 없는 메소드입니다.

 

Object.freeze() - JavaScript | MDN

Object.freeze() 메서드는 객체를 동결합니다. 동결된 객체는 더 이상 변경될 수 없습니다. 즉, 동결된 객체는 새로운 속성을 추가하거나 존재하는 속성을 제거하는 것을 방지하며 존재하는 속성의

developer.mozilla.org

쨋든 const의 경우 변수의 재할당은 막아주지만 객체의 속성을 변경하는 것이 가능한데 이때 freeze를 함께 쓰면 immutable 한 객체를 만들 수 있게 됩니다.

freeze로 인한 변화

보시다시피 immutableObj의 객체는 변하지 않고 a : apple로 고정됨을 확인할 수 있습니다.

이제 다시 에러로 돌아가 보죠.

문제 식별 | Mobx와 immer의 상충

에러 로그에서 보다시피 Mobx의 Observable 객체는 freeze 될 수 없다라는 내용이었습니다.

근데 immer는 뭔데 freeze를 사용할까요?

immer는 freeze를 활용해서 최적화를 진행한다고 합니다. 또한 들어온 객체에 대해서 생산자 외부에서 상태 트리가 수정되는 것을 방지하는 이점도 준다고 하죠.

그래서 아래 todosObj에 있는 부분을 freeze를 하고 draft 안에서 객체를 업데이트함으로써 immer의 본래 목적인 immutable 한 값 업데이트를 진행합니다.

그래서 저 값이 freeze 된다는 건데  여기 store이 Mobx의 Obserable 객체였습니다. ( 이게 에러 발생 코드 )

문제 해결

원인을 파악했으니 이제 해결을 해보도록 하겠습니다. 방법은 크게 2가지인데 옳은 방법인지는 정확히 모르겠습니다.

#1 새로운 객체를 만들기

JSON.parse 와 stringify를 이용해서 완전히 새로운 객체를 만들어 사용하는 겁니다.

대신 draft와 하단의 meta에 밑줄이 생기죠. 바로 parse, stringify 하면서 객체의 type를 잃어버려서 그렇습니다. 

물론 여기서 any로 선언해주고 setInfo의 meta값의 타입을 가져와서 assertion 해주면 해결할 수 있을 겁니다. 근데 그러기는 약간 아쉽습니다.

이렇게 하는 건 immer를 쓰는 이유가 없어지는 거나 마찬가지니깐요. 이런 작업 안 하려고 immer를 쓴 건데... ㅜㅜ

#2 immer의 setAutoFreeze 사용하기

immer에 대한 자료를 찾아보니 immer 버전 9부터는 produce에 들어온 객체를 freeze 하여 동작한다고 합니다.

근데 여기서 한 가지 의심이 생깁니다. 다른 컴퓨터에서는 동작했는데 뭐 때문일까? 버전이 낮은 거 때문일까? 

이런 의심은 뒤에 가서 해결하고 일단 setAutoFreeze에 대해 더 봅시다.

최신 버전에는 default로 Freeze 옵션이 true입니다. 우린 이걸 적어도 Mobx 객체에 대해선 false로 해줄 필요가 있습니다.

그래서 해당 함수가 사용되는 컴포넌트가 랜더링 되면 false로 만들고 언마운트되면 true로 만들겠습니다.

그냥 모든 상황에서 false로 할 수 있지만 immer가 Mobx 객체에서만 쓰이는 것이 아니기 때문에 최적화의 이점을 버릴 수가 없어 부득이하게 Mobx객체를 건드릴 때만 해제해주는 방식으로 구현했습니다.

이제 Mobx와 Immer의 상충으로 인한 문제는 해결되었습니다.

그럼 이제 다른 컴퓨터에서는 동작했는데 제 컴퓨터에 동작 안 한 원인에 대해 파악해보도록 하죠.

대부분 문제는 버전 오류 때문이다.

대부분 환경에 따른 라이브러리의 문제는 버전 차이입니다. 그래서 냅다 immer 버전을 검색했죠.

근데 immer가 없습니다. 네 그렇습니다. 우린 깔리지도 않은 immer를 사용하고 있던 겁니다. 드디어 자바스크립트가 없는 코드를 만들어서 사용하는 경지에 이른 걸까요..?

package-lock.json 파일을 한번 검색해보겠습니다. immer가 17개나 검색이 됩니다.

검색된 내용을 더 살펴보겠습니다.

보아하니 easy-peasy 라이브러리가 뭔지 모르겠지만 requires에 immer 7이 필요하다고 되어있습니다.

근데 우리 프로젝트에서는 immer를 설치한 적이 없습니다. 하지만 사용 가능합니다.

바로 이게 유령 의존성(phantom dependency)입니다.

간단히 말하면 직접 설치하는 않는 라이브러리를 사용할 수 있게 되는 현상입니다.

여기서 문제가 생기는데 직접 설치하지 않았기 때문에 어떤 버전의 라이브러리를 가져다 쓰는지 알 수 없습니다.

그렇습니다. immer의 어떤 버전이 이번 사태의 원인이었습니다.

유령 의존성은 왜 발생했는가?

해당 코드를 작성한 팀원에게 가서 어떤 방식으로 구현을 했는지 취조를 진행했습니다.

일단 그분도 immer가 package.json에 없다는 사실에 흠칫했고, 곧이어 자책을 시작하셨는데

해당 react 프로젝트 디렉터리가 아닌 상위 디렉터리에 설치를 했던 것이었습니다.

게다가 해당 코드를 Import문을 통해 불러온 게 아니고 바로 코드에 복붙을 하게 되었습니다.

바로 코드에 복붙이 어떤 문제를 일으키는가 하면은 원래라면 보시다시피 import를 할 수 없기 때문에 function를 선언할 거냐 라는 fix가 나와야 합니다. ( 왜냐면 package.json에 원하는 라이브러리가 없거든요 )

근데 바로 복붙을 하게 되면 node_module에 해당 라이브러리가 있는 경우 그냥 동작해버립니다.

여기서 바로 문제가 일어나게 되었습니다. 게다가 이전 immer의 버전 7의 경우 production 모드에서는 freeze 기능이 제거된 채로 빌드되었기 때문에 더욱 문제 확인이 어려웠습니다. ㅜㅜ

유령 의존성 해결 방법

유령 의존성은 보통 개발자의 실수로 일어납니다. 이번 제 경우만 해도 그렇고요. 

하지만 좋은 도구는 이런 개발자의 실수도 잡아줄 수 있어야 한다고 생각하고 또 이미 그런 도구가 나와있습니다.

기존의 yarn v1과 npm이 유령 의존성과 node_module에 모든 라이브러리를 설치하기 때문에 용량 자체가 커진다는 이슈가 있습니다.

1.05GB 실화냐..?

그래서 yarn berry로 하게 되면 의존성 라이브러리들을 zip 파일로 관리하고 각각의 의존성을 별도의 json 형식으로 저장하여 이런 단점을 개선했다고 합니다.

저 또한 개인 프로젝트에서 yarn berry를 적용해서 사용하고 있는데요. 회사 프로젝트에서 갑자기 환경설정을 바꾸는 건 어렵다고 해서 그냥 yarn를 사용하고 있습니다. ( 그러면 도입기가 아닌거 아닌가..? )

토스에서도 yarn berry를 적용하여 이런 문제를 해결했다고 하니 아래 문서를 보시면 좀 더 수준 높은 글을 확인할 수 있으실 겁니다.

 

node_modules로부터 우리를 구원해 줄 Yarn Berry

토스 프론트엔드 레포지토리 대부분에서 사용하고 있는 패키지 매니저 Yarn Berry. 채택하게 된 배경과 사용하면서 좋았던 점을 공유합니다.

toss.tech

p.s 저는 yarn berry로 바꿨는데 빌드 시간이 줄어들지 않았습니다. 다만 적어진 용량, gitlab에 라이브러리들도 올려 더 이상 yarn install 없이 바로 프로젝트를 빌드할 수 있다는 장점이 생겼습니다.


마무리

지금 보면 간단한 이슈였는데 어처구니없이 2가지 이슈가 합쳐져 저를 하루 종일 괴롭힌... 이슈였습니다.

이렇게 유령 의존성 그리고 mobx, immer에 대한 깊은 공부를 한 것으로 합리화했던 하루였습니다.