TL;DR

vite와 yarn berry(pnp)를 사용하는 프로젝트에서 dev server 실행 시 node_modules가 생성되는 것을 원치 않는다면:

1) vite.config.ts의 config 필드 중 cacheDir 경로 따로 지정해주기

2) yarn.lock 파일에서 vite의 dependencies 목록 중 esbuild의 버전이 0.15.15보다 작다면 0.15.15로 수정 후 yarn install하기

3) node_modules 삭제 후 dev server 실행하기

 


일전에 패키지 매니저 npm에서 yarn berry(zero install)로 바꾸기 포스팅에서 node_modules가 좀비처럼 되살아나는 현상을 막기 위해 이것저것 시도해 본 상황에 대해 기록했었다. 당시에 명확한 원인을 파악하지 못했지만 적어도 pnp 방식을 사용할 땐 node_modules가 없는 게 정상이라고 판단해서 어떻게든 안 보이게 하는 걸 목표로 미봉책이라도 찾아보려고 했으나 결과는 실패.. 사실 그냥 거슬릴 뿐이지 프로젝트 돌아가는 데 지장을 준 것은 아니기 때문에 찝찝해도 넘어갔다. 더 붙잡고 있기에는 이미 너무 많은 시간을 써버려서..ㅠ

 

"아무튼 cra 때문"이라고 단정지었었는데, 오늘 cra가 아닌 vite 템플릿으로 프로젝트를 생성한 이후에도 동일한 현상이 발생했다! 그러니까, dev server를 실행할 때 다음과 같이 node_module가 짠~ 하고 생겨버린 것이다. (참고로 vite 커뮤니티 템플릿 중 하나인 varsarr를 사용해서 프로젝트를 생성했다. 개발자분 한국 대학생 분이신 것 같은데 깃헙이랑 블로그 보면서 감탄했다..👍)

 

node_modules 내부

 

.vite/deps 먼저 살펴보면, package.json의 dependencies에 기재된 패키지들이 트랜스파일링 되어있다. 패키지들은 우리가 작성하는 소스코드와 달리 설치 후에 내용이 바뀌지 않으므로 vite는 패키지들을 이렇게 미리 트랜스파일링 해놓고 캐싱해서 쓴다고 한다.

그리고 .cache/esbuild/pnpapi-어쩌고의 경우 esbuild 실행파일이다. 정확히 어떤 일을 하는지는 모르겠으나 esbuild 쪽에서 만들어진 것으로 보인다. (vite는 내부적으로 esbuild를 써서 패키지들을 사전에 번들링한다.)

 

.vite 안에 있는 deps의 경우 vite.config.ts에서 cacheDir 필드에 원하는 경로를 지정함으로써 node_modules가 아닌 다른 곳에 저장할 수 있다.

.cache/esbuild/pnpapi-어쩌고는.. 이쪽도 esbuild 관련 설정을 건드려서 다른 곳에 저장되도록 하려는 속셈이었는데, 일단 공식문서 쪽에선 못찾겠어서 esbuild 소스코드를 좀 살펴보았다. 일단 찾고 싶었던 파일은 타입스크립트로 작성되어 있어서 원인으로 짐작되는 지점은 금방 찾을 수 있었다. lib/npm/node-platform.ts 파일의 217번째 줄부터 보면.. 에반 월러스쿤(28조원짜리 그 소프트웨어의 공동 창업자..)이 아주 친절하고도 자세하게 이 코드가 필요한 이유에 대해 설명해주고 있다.

 

ㄷㄷ

 

파파고의 힘을 빌려 번역해보면, :

 

 

(217번째 줄) 아래 이 코드는 사용자가 Yarn 2+를 PnP 모드에서 사용하고 있으며 해당 버전이 "preferUnplugged" 설정을 지원하지 않을 정도로 오래된 경우를 방지합니다. 그렇다면 위에서 설명한 이진 실행 파일의 경로는 실제 경로가 아닙니다. 대신 추가적인 내용이 추가된 zip 파일의 경로입니다.

Yarn의 PnP 모드는 Node의 파일 시스템 API를 패치하여 이러한 가짜 경로가 실제인 것처럼 가장하려고 노력합니다. 따라서 Node의 파일 시스템 API(예: "fs.existsSync")를 사용하여 실제 파일인지 여부를 확인할 수 없습니다. 가짜로 패치가 적용되었기 때문입니다. 그러나 Yarn이 "child_process.execFileSync"가 그러한 가짜 경로를 통해 동작할 수 있도록 패치하지 않았기 때문에 이 가짜 경로를 반환할 수도 없습니다. 따라서 바이너리를 실행하려고 하면 실패합니다.

이에 대한 속임수로, 우리(esbuild)는 Node의 파일 시스템 API를 사용하여 가짜 경로에서 실제 경로로 파일을 복사합니다. 이에 따라 Yarn의 파일 시스템이 zip 파일에서 바이너리 실행 파일을 추출하여 우리가 실행할 수 있는 실제 파일로 만들 것입니다.

이 작업은 경로에 ".zip/"가 있으면서 동시에 "pnpapi" 패키지가 있는 경우에만 수행되는데, 이러한 경우는 Yarn PnP가 사용되고 있음을 나타냅니다. 내가 아는 한 어떤 것이 실제 파일인지 아닌지를 알려주는 API가 전혀 없습니다. Yarn 자신의 코드조차도 경로에 ".zip/"가 있는지 여부만 확인합니다.

참고 사항: 어떤 경로가 Yarn의 파일 시스템 속임수의 영향을 받고 있는지 여부를 확인하는 임시방편 중 하나는 파일을 stat(?)하고 결과에서 "crc" 속성을 확인하는 것입니다. 해당 속성이 존재한다면 파일이 zip 파일 안에 있는 것입니다. 하지만, 그것은 그렇게 사용될 의도로 만들어진 건 아니기 때문에 나는 여기서 그렇게 하지는 않았습니다. 어떤 Yarn 버전에서 작동하는지 또는 작동하지 않는지 누가 알 수 있습니까(향후 버전 포함).

 

 

(254번째 줄) 실행 파일을 ".cache/esbuild"에 복사합니다. Yarn 팀의 공식적인 권장 사항은 "node_moduels/.cache/esbuild" 디렉토리를 사용하는 것입니다. Yarn을 PnP 모드로 사용하는 사람들은 "node_modules" 디렉토리를 보는 것을 좋아하지 않기 때문에 이것을 정말 짜증나게 생각합니다. (ㅇㅈ) 새로운 버전의 Yarn은 "preferUnplugged" 설정으로 인해 빌드의 이진 실행 파일을 zip 파일에 붙여서는 안 되기 때문에 Yarn과 협력하여 권장 사항을 변경하거나 Yarn 버전을 업그레이드해야 합니다.

 

읽어보니 대략 yarn 때문에 킹쩔 수 없다는 느낌인데 .. 그래도 node_modules 보이는 거 불편해하는 사람이 지구 어딘가에 더 있다는 사실에 안도감을 느꼈다. 우선은 애초에 내가 원했던 건 node_modules를 아예 사라지게 하는 것이었기 때문에 여기서 제안한 방법을 시도해보려고 했다. 그런데 버전 업그레이드를 하기에는 이미 latest 버전 사용 중이고(2023년 1월 31일 기준 3.3.1).. Yarn이 권장 사항을 바꿀지 말지는 Yarn 마음이고.. 그래서 일단은 좀 더 자세히 살펴봤다. 우선 node_modules 아래에 esbuild의 실행파일이 저장되는 건 esbuild 쪽에서 다음과 같이 경로를 그렇게 지정해뒀기 때문인데, 그 이유는 위 주석과 같이 Yarn이 그렇게 하라고 공식적으로 권장하고 있기 때문이다!

 

// lib/npm/node-platform.ts

const binTargetPath = path.join(
    root,
    'node_modules',
    '.cache',
    'esbuild',
    `pnpapi-${pkg.replace('/', '-')}-${ESBUILD_VERSION}-${path.basename(subpath)}`,
)

 

왜 이렇게 권장하는 것일까? (pnp 쓰면 node_modules 없을 거라고 말해놓구..ㅠ) 월러스쿤이 주석에 첨부한 링크로 들어가보면 yarn은 그 이유에 대해 다음과 같이 이야기한다. :

패키지들은 read-only 데이터 저장소에 위치해야 한다. 만약 어떤 패키지를 시스템 전역 저장소 같은 데에 뒀다간, 그 패키지의 소스를 수정하는 경우가 발생할 때, 같은 기기에 있는 다른 프로젝트들 중 그 패키지에 의존하는 모든 프로젝트들이 그에 대한 영향을 받게 될 위험이 있다. 이런 이유로 패키지들은 쓰기 접근이 제한된 저장소에 둬야 한다. 그래서 주로 쓰이는 방법이 바로 node_modules/.cache 폴더 안에 캐시 데이터를 저장하는 방법이라고 한다.

 

이와 같이 yarn이 pnp 쓸 때도 node_modules/.cache 안에 캐시 데이터 저장하라고 권장했다는 사실은 조금 충격적이었다. 그치만 한편으론 이대로 node_modules를 냅둬도 괜찮겠구나하는 안도감이 들기도.. 그래서 esbuild 실행파일은 우선은 node_modules 밑에 생성되도록 냅두기로 했고 vite cacheDir 경로만 수정하는 방식으로 타협했다. 방법을 찾으면 업데이트 하는 걸로..

 

...........

글 올리고 삽질 좀 더 하려고 보다가 바로 해결..! 분명 esbuild v0.15.15에서 월러스쿤이 yarn pnp 사용자들의 불편함을 해소해주고자 esbuild 쪽 manifest 파일들에서 preferUnplugged 필드를 전부 true로 바꿔줬기 때문에 해당 버전 이후의 esbuild를 쓰고 있다면 node_modules가 안 생기는 게 맞다..! 그 말인즉슨 이렇게 node_modules가 생긴다는 건 내부에서 돌아가고 있는 esbuild 버전이 0.15.15 밑일 가능성이 크다는 것... 우선 프로젝트에서 직접적으로 esbuild를 쓰는 건 아니고 vite의 dependencies 중 하나로 포함되어 있는 것이기 때문에 yarn.lock 파일에서 vite가 버전 몇 짜리의 esbuild를 쓰고 있는지 찾아봤다.

 

 

오 목표 버전보다 낮은 것은 확인했으니 목표 버전으로 올려주면 되는데, 직접 의존하고 있는 dependency가 아닌, dependency의 dependency의 버전은 어떻게 올려준담..? 검색해보니 lock 파일 직접 수정하면 된다는 의견이 많아서, 안전한 방법은 아닐 수 있지만 문제 생기면 되돌리자는 마인드로 일단 yarn.lock 파일의 vite > dependencies > esbuild 필드에 목표 버전(^0.15.15)을 지정하고 yarn install을 다시 실행했다. 그리고 node_modules를 삭제한 뒤 yarn dev를 실행한 결과..!! node_modules 생성 없이 dev server가 정상적으로 실행되는 것을 확인할 수 있었다ㄷㄷ

다만 월러스쿤 피셜, 경우에 따라서 esbuild가 파일 시스템에서 차지하는 공간이 더 많아질 수 있다고는 했으나 0.15.15에 이러한 변경사항을 포함한 것을 보면 그렇게 큰 지장은 없지 않..을ㄲ..ㅏ?

 


<참고>

yarn 쪽에도 esbuild 변경사항 알려준 월러스쿤

 

[Bug?]: prefersUnplugged should take precedence over pkg.conditions · Issue #4749 · yarnpkg/berry

Self-service I'd be willing to implement a fix Describe the bug In this comment, esbuild was recommended to add prefersUnplugged: false to its optional platform-specific deps so they don't ...

github.com

changelog

 

Release v0.15.15 · evanw/esbuild

Remove duplicate CSS rules across files (#2688) When two or more CSS rules are exactly the same (even if they are not adjacent), all but the last one can safely be removed: /* Before */ a { color: ...

github.com

esbuild creates node_modules/.cache directory in Yarn PnP repo

 

esbuild creates `node_modules/.cache` directory in Yarn PnP repo · Issue #2685 · evanw/esbuild

When I create a Yarn PnP repo and install or run esbuild, it creates a node_modules/.cache/esbuild directory containing pnpapi-esbuild-linux-64-0.15.14-esbuild, even after deleting the directory. S...

github.com

 

복사했습니다!