본문으로 건너뛰기
개발 머꼬
개발 노트Node.js
hohyeon.dev62

워크스페이스에 분명히 설치된 sharp를 바깥 스크립트가 Cannot find module로 못 찾은 이유

  • #Common Pitfall
  • #Engineering Note
  • #Node.js

문제 발생

이미지를 한 번 만들어 커밋하려고 임시 디렉터리에 스크립트를 두고 import sharp from "sharp"를 적었습니다. 모노레포의 packages/uploadssharp를 쓰고 있어 설치돼 있는 것은 확실했습니다.

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'sharp' imported from /tmp/gen.mjs

같은 스크립트를 packages/uploads 안으로 옮기니 바로 돌았습니다.

원인 분석

Node는 파일 위치에서 위로 올라가며 node_modules를 찾습니다. /tmp/gen.mjs에서 시작하면 /tmp/node_modules, /node_modules뿐입니다. 저장소 안의 어느 node_modules도 그 경로 위에 없습니다. 이건 pnpm과 무관한 Node의 해석 규칙입니다.

그런데 저장소 루트에서 실행해도 실패했습니다. 여기서부터가 pnpm의 구조입니다. pnpm은 node_modules를 평평하게 펴지 않습니다. 문서에 따르면 pnpm의 배치에서는 실제로 의존성에 적힌 패키지만 접근할 수 있고, 평평한 구조에서는 끌어올려진 모든 패키지에 접근할 수 있습니다. 문서는 이것을 "어리석은 버그를 막아 주는 엄격함"이라고 부릅니다 — package.json에 적지 않은 패키지를 쓰는 코드는 언젠가 설치 순서가 바뀌면 깨지니까요.

sharppackages/uploads/package.json에만 있습니다. 그래서 packages/uploads/node_modules/sharp 심링크는 있지만 루트 node_modules/sharp는 없습니다. 문서가 말하듯 pnpm이 모든 의존성을 기본으로 끌어올리는 곳은 루트가 아니라 node_modules/.pnpm/node_modules이고, 그 디렉터리는 의존성 사이의 해석을 위한 것이지 루트에 놓인 스크립트를 위한 것이 아닙니다.

해결 방안

  1. 그 의존성을 선언한 패키지 안에서 실행합니다. 가장 정직한 방법입니다. 일회성 스크립트라면 그 패키지 디렉터리에 잠시 두고 돌린 뒤 지웁니다.
pnpm --filter @meokko/uploads exec node ./scripts/generate-og.mjs
  1. 계속 쓸 스크립트라면 그 패키지의 scripts에 넣습니다. 파일 위치와 의존성 선언이 같은 패키지에 있으니 다음 사람도 헷갈리지 않습니다.

  2. 루트에서 써야 한다면 루트 package.json에 의존성을 추가합니다. 그 대신 "루트가 왜 sharp가 필요한가"에 답이 있어야 합니다. 없다면 1번입니다.

  3. shamefully-hoistpublic-hoist-pattern으로 전부 끌어올리지 않습니다. 한 스크립트 때문에 저장소 전체가 선언하지 않은 의존성에 기대게 됩니다. pnpm이 막아 주던 버그가 그때부터 다시 가능해집니다.

  4. pnpm dlx는 별개입니다. 설치되지 않은 도구를 한 번 실행하는 명령이라, 이미 워크스페이스에 있는 버전과 다른 것을 받을 수 있습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.