노션 블로그 만들기 - Yarn Berry 토핑하기
노션 api를 이용한 블로그 제작에 패키지 관리 토핑을 뿌려보자~
2023-02-19 | 프로젝트 | 10min
초기 세팅 - intro
우선 새롭게 NextJS
를 이용하는 만큼 전부 새로운 것들을 경험해보자는 의미에서 기존에 즐겨쓰던 yarn
의 버전도 berry
로 올렸다. 그 이유는 그 유명한 블랙홀보다 깊은 node_modules
짤에서 시작됐다..😱 여기까지의 과정을 소개해보겠다…ㅎㅎ
이전 프로젝트 초기 세팅할때는 아무 생각 없이 단순하게 node_modules
를 사용해왔었다. 그런데 궁금증에 한번 해당 디렉토리를 하나하나 눌러서 들어가보았다. 결과는 뭐 알다시피 위 사진 형태였다. Blackhole 보다 깊은..
사실 node_modules
이 어떤 특정 라이브러리를 다운받을 경우 해당 라이브러리의 dependencies까지 모두 다운 받는다는 정도만 알고 있었다. 그리고 내 기억으로는 gitignore
에 node_modules
경로를 추가하지 않고 깃허브에 업로드 했던 때 “이게 정말 크구나..” 라는 것을 경험했던 것으로 기억한다. 그렇다면 왜 이게 문제가 되는지에 대해서 살펴봐야했다.
심연의 node_modules
이와 관련된 매우 좋은 퀄리티의 글은 여기에서 확인할 수 있다. (스포를 하자면 역시나 토스 블로그이다..ㅋㅋ)
해당 글에서 좋은 내용들을 정리하자면,
유령의존성
NPM 및 Yarn v1에서는 중복해서 설치되는 node_modules를 아끼기 위해 끌어올리기(Hoisting) 기법을 사용합니다.
즉, npm이나 yarn을 사용하여 패키지를 설치하는 경우에 설치되는 패키지가 중복되는 것을(왼쪽 그림) 막기 위해 호이스팅 기법을 이용하여 오른쪽과 같이 중복되는 버전들을 디스크 공간의 낭비를 줄이도록 트리구조를 바꾸어 설치하게 된다는 것이다.
이렇게 되면, 정작 사용 의도가 없었던 B패키지에 접근할 수 있게 되었다. 다시말해, package.json
에 없는 패키지에 조용히 접근할 수 있게 되었다…?
비효율적 설치 및 탐색
NPM은 패키지를 찾기 위해서 계속 상위 디렉토리의 node_modules 폴더를 탐색합니다. 따라서 패키지를 바로 찾지 못할수록 readdir, stat과 같은 느린 I/O 호출이 반복됩니다. 경우에 따라서는 I/O 호출이 중간에 실패하기도 합니다.
NPM은 패키지를 찾지 못하면 상위 디렉토리의 node_modules 폴더를 계속 검색합니다. 이 특성 때문에 어떤 의존성을 찾을 수 있는지는 해당 패키지의 상위 디렉토리 환경에 따라 달라집니다. … 상위 디렉토리가 어떤 node_modules를 포함하고 있는지에 따라 의존성을 불러올 수 있기도 하고, 없기도 합니다. 다른 버전의 의존성을 잘못 불러올 수 있는 여지도 존재합니다.
게다가, NPM의 검색 방식은 파일시스템을 이용하는데, node_modules
폴더 내에서 패키지를 찾지 못한다면, 상위 디렉토리의 node_modules
를 순회하고 이에 반복이라고 볼 수 있다. 설상가상으로, 상위 디렉토리의 node_modules
환경이 의존성을 불러오지 못하는 상황을 일으킬 수 있다는 것이다.
또한, 의존성 설치 검증 방식에서는 I/O 호출이 많아져 느려지는 것을 피하기 위해 기존의 의존성 트리의 유효성만을 검증하며 패키지 내용은 확인하지 않는다고 한다.
이제보니 생각보다 npm과 yarn은 엄청 비효율적이었다는 것을 새삼 깨닫게 되었다..
엘리트 Plug’n’Play
그럼 yarn v1(npm i yarn
을 통해 설치한 패키지 관리자이다. 내가 사용하려는 yarn berry와는 다르다. 앞서 설명했듯 node_modules
을 활용하는 yarn이라고 보면 된다)은 어떤 방식으로 작동할까?
Yarn v1은 package.json 파일을 기반으로 의존성 트리를 생성하고, 디스크에 node_modules 디렉토리 구조를 만듭니다. 이미 패키지의 의존성 구조를 완전히 알고 있는 것입니다.
그렇지만 앞서 나왔듯 Node의 내장 파일 시스템을 이용한 의존성 관리는 비효율적이다.
그렇다면 yarn berry는 어떤 식으로 작동하는 것일까?
Yarn Berry 세팅
기존의 쓰던 yarn v1을 berry로 버전 업을 시켜주면 된다.
// 최신버전 berry로 적용됨
yarn set version stable
// 또는
yarn set version berry
위 코드를 커맨드 창에 입력하면 yarn berry를 사용할 수 있게 된다.
이때, node_modules
를 삭제하고 다시 커맨드 창에 yarn 을 입력해서 기존의 패키지들을 다운받으면 된다.
추가로 .yarnrc.yml
파일 내 nodeLinker: pnp
(기존의 yarn은 node_modules
로 되어있다)를 입력해주고, 아래의 코드를 .gitignore
에 추가하면 된다.
/* Zero-Install을 사용하겠다면? */
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
/* Zero-Install을 사용하지 않겠다면? */
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
참고로 Zero-Install 이란 패키지들을 설치하지 않고 이용하는 것을 의미한다. 사용 여부에 따른 위 아래의 차이에 !.yarn/cache
가 있고 없고를 보면, 해당 디렉토리가 바로 yarn berry를 통해 패키지들이 저장되는 장소임을 짐작할 수 있다. 다시 말해, .yarn/*
을 통해 yarn 디렉토리의 모든 정보들을 .gitignore
에 추가하지만, 그 아래 나열된 항목들을 제외하는 것에서 .yarn/cache
를 포함할지 말지가 달라지는 것이다.
만약 원격 저장소에 .yarn/cache
를 업로드 했다면 이후 다른 로컬 환경에서 사용할 때, 굳이 yarn install 할 필요 없이 cache가 저장된 채로 다운받아지기에 추가로 다운 받을 필요가 없게 되는 것이다. 반대로, Zero Install을 사용하지 않을 것이라면, .yarn/cache
를 제외항목에 두지 않으면 된다. 이럼 나중에 풀 이후 yarn을 통해 패키지를 설치해야 할 것이다. 이렇게 되면, 해당 패키지들에 대한 의존성 정보를 기록하는 PnP
관련 파일들은 install과 같이 생성되도록 원격 저장소에 저장되지 않게끔 제외항목에 추가하면 된다.
Yarn Berry
우선 기존의 node_modules
를 이용하지 않는다. 대신 아래와 같이 .yarn/cache 디렉토리 내에 의존성 정보를 zip 파일로 저장된다. 여기까지만 보면, node_modules
과는 크게 다른 점이 없어 보인다.
Yarn berry의 특별한 점은 의존성 관리를 위한 정보가 기록되는 .pnp.cjs
파일이다. 현재 나는 creat-next-app을 typescript template으로 이용하고 있다. 해당 파일 내에 들어있는 react 패키지를 찾아보자.
/* 일반적인 구조 */
["react", [
["npm:18.2.0", {
"packageLocation": "./.yarn/cache/react-npm-18.2.0-1eae08fee2-88e38092da.zip/node_modules/react/",
"packageDependencies": [
["react", "npm:18.2.0"],
["loose-envify", "npm:1.4.0"]
],
"linkType": "HARD"
}]
]],
추가로 일반적이지 않은 패키지의 형태이다.
/* 레어하지만 특이한 구조 */
["virtual:966b21e19f20b6ab6a17e9767e2339d9ca7724f91424603b5cf252ca77b98fa0f1e32ab71214f633a544919118f8d1f5eb4521fe0ca35c125ccdf7bef850544e#npm:3.21.0", {
"packageLocation": "./.yarn/__virtual__/tsutils-virtual-6fa13d6f1b/0/cache/tsutils-npm-3.21.0-347e6636c5-1843f4c1b2.zip/node_modules/tsutils/",
"packageDependencies": [
["tsutils", "virtual:966b21e19f20b6ab6a17e9767e2339d9ca7724f91424603b5cf252ca77b98fa0f1e32ab71214f633a544919118f8d1f5eb4521fe0ca35c125ccdf7bef850544e#npm:3.21.0"],
["@types/typescript", null],
["tslib", "npm:1.14.1"],
["typescript", "patch:typescript@npm%3A4.9.5#~builtin<compat/typescript>::version=4.9.5&hash=23ec76"]
],
"packagePeers": [
"@types/typescript",
"typescript"
],
"linkType": "HARD"
}]
]],
위 두 코드들의 구조는 아래와 같이 해석된다.
["패키지 명", [
["npm 버전", {
packageLocation: "해당 패키지가 있는 위치"
packageDependencies: "해당 패키지의 의존성 정보"
packagePeers: "같은 인스턴스를 참고하는 다른 패키지에 대한 정보"
linkType: "링커의 영역 밖의 위치에 있는 패키지라면 'Soft', 링커의 영역 내 있는 패키지라면 'Hard'"
}]
]]
사실 packageLocation
과 packageDependencies
까지는 이해가 가더라도 packagePeers
와 linkType
에 대해서는 아직 이해가 잘 가지 않는다. 추후 pnp 관련 집중 탐구 뒤에 포스팅을 해보겠다. 더 자세한 내용을 알고 싶다면 아래의 PnP API 페이지를 참고하길 바란다.
결론
이렇게 yarn berry를 적용하게 된 과정을 전반적으로 알아보았다. 해당 기술의 의의를 탐구해볼 수 있던 중요한 경험이었다. 물론 아직 더욱 집중탐구를 해봐야 할 부분들이 많이 남았기에 지식 부족으로 글의 퀄리티는 많이 부족하다고 느껴진다. 그래도 이런 식으로 정리하고 기록하는 것이 기억에 잘 남는 것 같다. 어쩌면 개발일기 같기도 하다..😂😂
하여튼 이렇게 yarn berry를 적용하고 간단하게 내부를 들춰보았다. 다음 포스트는 Emotion에 관한 글을 올려볼까 한다.