안녕하세요. 모바일엔트로피에서 웹 프론트엔드를 담당했던 개발자 Octoping입니다.
해당 글은 리멤버 웹 서비스 좌충우돌 Yarn Berry 도입기
를 아주 감명 깊게 읽었던 한 개발자가 Yarn Berry를 도입하고, 그 과정에서 겪었던 여러 이슈들을 정리한 글입니다.
리멤버의 킹갓종택님과는 다르게 이 글은 프론트엔드 초보들끼리 삽질한 내용들이 주가 될 것 같아, 어찌보면 저 원본 글과는 달리 조금 절망편의 느낌이 묻어나는 글이 될 것 같네요 ㅎㅎㅎ
Yarn Berry를 써야 할 결심
리멤버 웹 서비스 좌충우돌 Yarn Berry 도입기 아티클을 살펴보니 yarn berry
를 사용하면 빌드 시간을 평균적으로 1분 정도 단축할 수 있는 것으로 보였습니다. 뿐만 아니라 종속성들을 보다 안전하게 관리하면서도 기존 node_modules에 딸려오는 여러가지 골치 아픈 문제들을 근본적으로 해결할 수도 있을 것으로 봤습니다.
기존에 저희 프로젝트는 운영 환경에 문제 있는 코드가 머지 되는 것을 방지하기 위해, PR 전에 Github Action을 통해 test, lint, build, sonarqube를 돌려서 코드의 문제 여부를 조기 탐지하고자 했습니다.
하지만 완전 초기부터 개발되고 있는 프로덕트에 매번 PR을 올릴 때마다 3분씩의 대기 시간이 필요한 것은 큰 병목으로 느껴졌습니다. 이를 최소화하기 위해 node_modules
를 캐싱하는 방법도 사용했지만, 이 대기 시간은 너무나도 길게 느껴졌습니다.
하지만 당시 2명만으로 운영되던 터라 개발 인프라 단에 리소스를 투자하기 어려운 상황이었기 때문에 우선 순위에서 밀렸던 것 같았습니다. 하지만 프로젝트 컨셉을 위해 CRA → Next.js로의 대격변이 이루어져야 하는 상황이 와서 명분도 충분했습니다.
마침 기존 저희 회사의 서비스는 yarn 1.x 버전의 패키지 매니저를 사용해왔는데, 현재 yarn 1.x 버전은 유지보수만 이루어지는 레거시 프로젝트가 되었습니다. 즉, 굳이 모던 레거시를 만들 필요도 없다 생각하여 berry로 마이그레이션 하는 것이 바람직하다고 생각했습니다.
Why 'Yarn Berry'
도입을 결심한 뒤, 어차피 프로젝트도 갈아엎겠다. yarn classic에서의 버전 업도 yarn set version berry
만 치면 되겠다, 큰 걱정 없이 마음 편하게 적용을 시작해나갔습니다.
yarn berry를 적용함으로써 얻을 수 있는 이점은 다음과 같습니다.
- 1) 모듈 탐색 과정의 비효율
- 2) 유령 의존성
- 3) Plug'n'Play (PnP)
- 4) Zero-Installs
이 네 가지에 대해서는 리멤버의 아티클 뿐만 아니라, 토스 slash 컨퍼런스와 토스 기술 블로그에서도 여러 차례 언급해주신 적이 있습니다. 아주 소중한 자료들이었습니다.
하지만!!!!
저희의 경우는 나머지 3개에는 솔직히 전혀 관심 없었습니다.
오로지 Zero-Install 하나만을 바라보며 적용한 Yarn Berry 였습니다.
나머지는 부수적으로 따라오는 기대효과 123 이었을 뿐이었는데요.
Yarn Berry의 도입을 통해 저희가 진정으로 바랐던 개선 방향은 다음과 같습니다.
CI 시간 감소
앞에서 언급했듯, 저희 프로젝트는 PR시 테스트, 린트, 빌드, 소나큐브 분석 자동화를 돌립니다.
코드 리뷰에서 approve를 받더라도, 이 일련의 시퀀스가 전부 끝나야만 머지를 할 수 있도록 구성해놓았는데요.
이 시퀀스에는 대략 2~3분이 소요되었습니다.
Zero-Install을 활용한다면 여기에서 yarn install
을 통해 의존성을 설치하는 시간이 줄어들테니 어느정도의 CI 시간의 감소를 볼 수 있을 것이라 생각했습니다.
끔찍한 node_modules
아시다시피 node_modules 폴더의 크기는 굉장히 거대합니다.
간단한 CRA 프로젝트라도 200mb는 넘어가며, 엔터프라이즈 급 되는 프로젝트의 경우는 수 GB까지도 가는 경우가 있습니다.
그런데 저희 회사는 기존에 SVN을 사용하고 있다가 최근에 들어서 Git 기반의 레포지토리로 프로젝트를 이관하고 있는 상태입니다.
따라서 직원들의 git의 숙련도가 높지가 않은데요.
따라서 여럿이 동시에 작업하다가 Conflict가 나는 경우, conflict를 해결하기보다는 그냥 프로젝트 자체를 전체 삭제한 후 다시 clone 받아서 본인 작업물을 덮어씌우는 쪽으로 작업을 하고 있었습니다. 😅
하지만 여기서 위에 적은 node_modules의 문제가 발생하는데요.
사실 프로젝트 폴더를 통으로 삭제할 때 이 용량?은 문제가 별로 없습니다.
진짜 문제는 안에 들어있는 파일 개수입니다.
node_modules에는 어마어마한 라이브러리의 폴더들이 있고, 이 라이브러리 폴더 안에는 또 다수의 js 파일들이 존재합니다.
이런 많은 양의 파일을 삭제하는 데에는 시간이 오래 걸립니다.
하루에 많으면 세 네번씩도 프로젝트 폴더를 통으로 날리는 팀원들에게 node_modules란 악몽이었습니다. 💀
Yarn Berry의 적용
그리하여 이런 이유들로 인해 yarn berry의 적용을 완료했습니다.
하지만 생각과는 다르게 우리의 앞에 탄탄대로만 놓여있는 것은 아니었는데요.
수많은 트러블 슈팅이 있었습니다.
트러블 슈팅 (with Github issues)
yarn berry
가 처음 발표되었을 때에 비해 관련 자료도 많아지고, 여러 대규모 프로젝트에서 지원하기 시작하는 등 꾸준한 개선이 있어왔지만, 기존 패키지 매니저와의 구조적인 차이 때문에 맞닥뜨리게 되는 낯선 이슈들이 여전히 존재합니다. 이번 섹션에서는 그러한 문제들을 해결했던 경험을 이야기해보겠습니다.
커밋에 포함되지 않는 종속성 문제
yarn install
시 커밋에 포함되지 않는 파일들이 있습니다.
Next.js 등에 포함되는 `swc`의 경우 운영체제에 종속되는 부분이 있다보니 커밋에 포함시킬 경우 실행환경에 따라 문제를 일으킬 수 있어 커밋에서 제외되고 있습니다.
또한, @sentry/nextjs
도 비슷한 경우인지, 커밋에서 제외되고 있는 것으로 보입니다.
결국에 정상적인 빌드를 위해 최초 1회 설치 명령어가 필요합니다.
굉장히 아이러니한 상황인데요..
오로지 CI 할 때의 yarn install을 없애기 위해 yarn berry를 도입했지만, 결국 최초 1회 설치 명령어를 실행해야 하는 상황이 왔습니다.
커밋에 포함되는 종속성 문제
살짝의 말장난인데요, yarn berry는 zero-install을 구현하기 위해 이 라이브러리 의존성을 github에 소스코드와 같이 올리는 전략을 취하고 있습니다.
다시 말해.. 깃허브에 라이브러리 파일까지 같이 커밋을 해서, 클론 받을 때 라이브러리까지 같이 다운을 받아서 굳이 install할 필요가 없게 만들겠다는 이야기이죠.
근데 이건 다시 생각하면.. 커밋할 때 의존성이 바뀌었다면 PR을 올릴 때 같이 올라오게 된다는 말입니다.
코드 리뷰할 때 files changed의 개수가 많으면 딱 리뷰하기가 겁나는 거 다들 아실 것 같은데요.
이는 코드 리뷰할 때 생각보다 피로감을 주는 경향이 있었습니다.
실제 리뷰할 코드와 라이브러리 코드가 뒤섞여 있으니 어지럽더라구요.
yarn berry와 emotion을 사용할 때 storybook이 실행되지 않는 문제
Storybook은 Next.js와 연동할 때 마법사를 이용해서 간단하게 구성하는 것을 도와주고 있습니다.
npx storybook@latest init
하지만 Yarn Berry와 함께하니 분명 공식 문서에서 하라는대로 마법사를 통해 구성을 완료했지만 아주 오류가 빠바방 뜨게 되었습니다.
storybook의 누락된 devDependencies (styled-jsx 등등)를 전부 설치하더라도 정상적으로 실행되지는 않았는데, 다음과 같이 바벨 설정을 .yarnyc.yml에 잡아주어 해결했습니다.
packageExtensions:
"@storybook/addon-docs@*":
dependencies:
"@babel/core": "*"
yarnPath: .yarn/releases/yarn-3.6.0.cjs
typescript에서 import 한 패키지를 인식 못하는 문제
yarn berry 설치 이후에, 다음 사진처럼 typescript에서 import한 패키지를 아무것도 인식 못하는 문제가 발생했습니다.
기묘한 점은 yarn dev 등의 명령어로 실행하는 것은 가능했지만 ide만 이렇게 말썽을 일으키는 문제가 있었습니다.
VsCode, IntelliJ 모두 이 문제가 발생했으며, 최초 1회 다음 명령어를 실행해주면 해결이 되었습니다.
yarn dlx @yarnpkg/sdks vscode
이 프로젝트 온보딩 시에 거쳐야 할 절차가 하나 더 늘은 셈이었습니다.
@toss/use-query-param이 작동하지 않는 문제
토스에서 만든 라이브러리인 use-query-param이 정상적으로 작동하지 않는 문제를 맞이했습다.
실제로는 이 세션을 보고 감명받아 @toss/use-funnel을 사용하려다 맞이한 문제긴합니다만... ㅎ 뭐
No router instance found. you should only use "next/router" inside the client side of your app.
그저 import 하는 것만으로 이런 문제를 일으켰습니다.
결국 해결은 하지 못했고, 이 @toss/use-funnel이란 라이브러리의 사용을 포기하고 직접 use-funnel을 만드는 선택을 할 수 밖에 없었습니다. 🥲
외부 라이브러리의 내부를 확인하기 위해 추가 확장 프로그램 필요
기존에는 외부 라이브러리의 코드 안을 확인해보기 위해서는 그냥 ctrl + 클릭을 통해 코드를 확인할 수 있었습니다. node_modules 안에 얌전히 js 파일로 존재하니까!
하지만 yarn berry는 의존성 하나 하나를 zip 파일로 관리한다. 이 덕분에 그냥 ctrl + 클릭으로는 내부를 확인할 수 없었습니다.
대신 이를 해결하기 위해서 확장 프로그램이 따로 존재합니다.
바로 ZipFS.
이 녀석을 활용하면 곧바로 VSCode에서 zip 파일 내부를 확인할 수 있게 됩니다.
분명 좋은 일이지만.. 결국 yarn을 사용하기 위해서 깔아야 하는 추가 의존성이 생긴 것이니 좀 껄끄러운 면이 있었습니다.
개선 결과
결과적으로 개선된 빌드 시간은 프로젝트마다 차이가 있으나 지금까지 적용해본 케이스들에서는.. 의외로 거의 차이가 나지 않았습니다.
앞에서 언급했듯, yarn berry를 사용해서 zero-install 기능을 사용했지만 결국 최초 1회 install이 필요했습니다.
이 install은 로컬에서 실행 시 3초도 걸리지 않았지만, 깃허브 액션 환경에서 실행 시 무려 1분이나 되는 시간을 잡아먹었습니다.
기존에 node_modules 환경에서 작동할 때에는 어차피 캐싱을 사용했기 때문에 충격적이게도 가장 기대했던 CI 시간 단축은 효과를 보지 못했습니다.
대신 다른 점 하나.
프로젝트 파일 폴더 째로 삭제할 때의 속도가 굉장히 빨라졌다는 점은 팀에서 아주 만족하고 있습니다.
라이브러리를 zip 파일들로 관리하다보니, 의존성들이 용량도 줄었을 뿐더러 파일 개수 자체도 많이 줄었습니다.
프로젝트 파일 삭제하는 데에 30초 가까이 들던 것이 이제는 3초 내외로 삭제가 됩니다. 어마어마한 속도 개선(?)이다.
마치며
한 명의 아이를 기르기 위해 온 마을이 필요하다는 말 처럼, 하나의 웹 서비스가 만들어지기 위해서는 정말 다양한 기술이 필요합니다.
그 중에서도 이 yarn berry와 pnp는 기존의 npm 환경의 단점을 멋진 방법으로 해결해낸 좋은 솔루션이라고 생각합니다. 하지만 위에 말씀드린 이슈처럼 사용자들이 직접 부딪혀야 하는 문제들이 산재해있고, 대부분은 자료 또한 많지 않은 상황이라 디버깅의 리스크가 아직 크다 생각합니다.
node_modules의 근간부터 뜯어고치며 기존의 npm 환경을 벗어나는 것 그 자체인 기술인 pnp를 활용하는 것이기 때문에, 기술적 성숙도가 높지 않은 팀에서 섣불리 yarn berry를 도입하는 것은 프로젝트의 복잡도를 어마어마하게 올리는 일이라 생각합니다.
리멤버 웹 서비스 좌충우돌 Yarn Berry 도입기와는 다르게 결국 부정적인 견해로 글을 마무리하게 되는 점 유감스럽지만, yarn berry는 여전히 발전 가능성이 높은 라이브러리라 생각합니다.
CommonJS에서 ESModule로 트렌드가 바뀌어가듯, node_modules에서 pnp로 트렌드가 바뀌는 시절을 기원하며, 일단은 저는 이후의 발전을 더 기다려보도록 하겠습니다.
지금까지 긴 글 읽어주셔서 감사합니다.
'언어 > Javascript & Typescript' 카테고리의 다른 글
함수의 파라미터를 리터럴로 받는 방법과 응용 (0) | 2023.08.22 |
---|---|
레거시 프론트엔드 프로젝트 점진적으로 개선하기 (0) | 2023.04.13 |
CDN에서 받아온 js에 타입 넣기 (0) | 2023.04.13 |
배열 안의 원소들을 파라미터로 하는 Promise 체이닝하기 (0) | 2023.04.13 |
자바스크립트로 난수 만들기 (0) | 2022.09.06 |