본문으로 건너뛰기
ff1451 logo ff1451

Prettier VS Code: Yarn PnP SDK 모듈 로딩 버그를 고치고 머지하기까지

파일 경로(.cjs)를 디렉토리로 잘못 해석해 발생한 Prettier 로딩 실패 원인을 분석하고, 경로 보정 로직과 통합 테스트 추가로 해결한 과정을 정리했습니다.

4 min read
#vscode #prettier #yarn #opensource

최근 Yarn Berry(PnP) 환경에서 Prettier VS Code 확장 프로그램의 formatting이 갑자기 멈추는 문제를 겪었습니다.
처음에는 로컬 설정 이슈라고 생각했지만, 에러 로그를 따라가 보니 확장 내부 모듈 로딩 경로 처리에서 문제가 발생하고 있었습니다. 이 글에서는 문제를 어떻게 재현했고 어떤 지점을 고쳤는지, 그리고 재발 방지를 위해 어떤 테스트를 추가했는지 순서대로 정리합니다.

1. Yarn PnP 환경에서 발생한 Prettier 로딩 실패

Yarn Berry(PnP) 환경에서 프로젝트를 진행하던 중 VS Code의 Prettier 확장이 동작하지 않는 현상을 확인했습니다. 당시 환경은 다음과 같습니다.

  • Yarn Berry(PnP): 4.10.3
  • VS Code: 1.108.0
  • Prettier Extension: 12.1.0
  • Prettier: 3.6.2

VS Code 설정 (.vscode/settings.json)

{
  "prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}

저희 프로젝트는 yarn dlx @yarnpkg/sdks vscode 를 통해 SDK 모듈을 로딩하는 방식을 사용하고 있습니다. 이 상태에서 파일 저장 또는 포맷팅을 시도하면 아래 에러가 출력되었습니다.

["ERROR"] Failed to load module.
ensure you have run npm install: /Users/.../.yarn/sdks/prettier/index.cjs/package.json

2. prettierPath가 파일인 경우를 고려하지 않은 설계

에러 로그를 보면 이상한 점이 있습니다. 설정된 경로는 .yarn/sdks/prettier/index.cjs라는 “파일”인데, 확장은 뒤에 /package.json을 붙여 경로를 찾고 있었습니다.

즉, 확장 프로그램이 prettierPath를 디렉토리로 가정하고 동작한 것입니다.

아무 설정 변경 없이 갑자기 에러가 발생했고, 동일 설정의 다른 프로젝트에서도 같은 문제가 재현됐습니다. 이 정황을 바탕으로 당일 배포된 Prettier Extension 업데이트를 원인 후보로 잡았습니다.

3. 소스 코드 추적과 문제 재현

문제를 해결하기 위해 prettier-vscode 저장소를 포크하여 소스 코드를 확인했습니다.

3.1 package.json 경로 결합 로직 추적

먼저 package.json 문자열을 검색해 modulePath 결합 지점을 추적했습니다. 그 결과 ModuleResolverNode.tsisValidVersion() 함수에서 문제가 발생함을 확인했습니다.

[수정 전]

// modulePath가 파일인 경우를 고려하지 않고 결합함
modulePackageJsonPath = path.join(modulePath, "package.json");

여기서 modulePath가 파일일 때를 처리하도록 보정했습니다. fs.stat()으로 파일 여부를 판별하고, 파일이면 부모 디렉토리에서 package.json을 찾도록 수정했습니다.

[수정 후]

let packageDir = modulePath;
try {
  const stat = await fs.promises.stat(modulePath);
  if (stat.isFile()) {
    // 파일(.cjs 등)이라면 부모 디렉토리에서 package.json 탐색
    packageDir = path.dirname(modulePath);
  }
} catch {
  // 에러 발생 시(경로가 없거나 등) 폴더로 가정하는 기존 로직 유지
}
modulePackageJsonPath = path.join(packageDir, "package.json");

이 변경으로 package.json 경로 오류는 해결됐지만, Prettier 로딩은 아직 실패했습니다.

3.2 import 단계에서도 반복된 파일/디렉토리 오인

다시 실행해보니 이번에는 다른 에러가 발생했습니다.

["ERROR"] Cannot find module 'index.cjs'
Require stack: /.../.yarn/sdks/prettier/index.cjs/noop.js

이번에는 noop.jsindex.cjs 아래에 결합되어 있었습니다. 경로 보정뿐 아니라 import 단계에서도 파일을 디렉토리처럼 취급하고 있다는 의미였습니다.

그래서 파일 경로가 이미 확정된 경우에는 엔트리 해석을 건너뛰고, 경로를 그대로 import 하도록 분기 처리를 추가했습니다.

[수정 전]

// 무조건 디렉토리로 가정하고 엔트리 포인트를 해석함
const entry = await resolveModuleEntry(this.modulePath);
const imported = await import(entry);

[수정 후]

const isFile = [".js", ".cjs", ".mjs"].includes(path.extname(this.modulePath));

if (isFile) {
// 파일 확장자라면 경로 그대로 import 하여 index.cjs/noop.js 같은 오류 방지
const imported = await import(this.modulePath);
this.prettierModule = (imported.default?.version ? imported.default : imported) as PrettierNodeModule;
} else {
// 디렉토리인 경우 기존처럼 엔트리 포인트를 찾아 resolve
const entry = await resolveModuleEntry(this.modulePath);
const imported = await import(entry);
// ...
}

4. 재발 방지를 위한 Yarn PnP 통합 테스트 추가

코드 수정 후 로컬에서 정상 동작을 확인했습니다. 하지만 같은 문제가 다시 생기지 않도록 자동화된 테스트가 필요했습니다. 프로젝트 테스트 인프라를 활용해 Yarn PnP 시나리오 통합 테스트를 추가했습니다.

describe("Test Yarn PnP SDK with prettierPath pointing to .cjs file", () => {
  it("it formats with prettierPath pointing to a .cjs file", async () => {
    // 실제 Yarn PnP 환경을 모사한 픽스처(yarn-pnp-sdk)에서 포맷팅 실행
    const { actual } = await format("yarn-pnp-sdk", "index.js");
    const expected = await getText("yarn-pnp-sdk", "index.result.js");

    // 실제 포맷팅 결과가 기대값과 일치하는지 검증
    assert.equal(actual, expected);
  });
});

이 테스트는 prettierPath가 디렉토리가 아닌 **엔트리 파일(.cjs)**일 때도 확장이 정상 동작하는지 검증합니다. 핵심은 “패키지 루트를 올바르게 찾고 실제 파일을 포맷팅할 수 있는가”를 CI에서 반복 확인하는 것입니다.

5. PR 머지 결과와 느낀 점

PR을 올린 뒤 얼마 지나지 않아 메인테이너 코멘트(amazing. Thank you!)와 함께 머지되었습니다.

PR이 머지된 화면

첫 오픈소스 기여를 마치며

돌아보면 이번 경험은 “도구 사용자”에서 “도구 개선 참여자”로 관점을 바꾸는 계기가 됐습니다. 특히 아래 세 가지를 분명하게 배웠습니다.

  1. 에러 로그의 중요성
    /index.cjs/package.json처럼 어색한 경로를 놓치지 않은 것이 해결의 시작점이었습니다.

  2. 테스트 코드는 신뢰의 언어
    긴 설명보다 재현 가능한 통합 테스트가 리뷰 속도를 높였습니다. 오픈소스에서는 “안전하다”는 주장보다 “검증된다”는 증거가 더 강하게 작동한다는 점을 체감했습니다.

  3. 커뮤니티의 가치
    많은 사용자가 쓰는 도구의 문제를 직접 고친 경험은 큰 동기부여가 됐습니다. 앞으로도 문제를 우회하는 데서 멈추지 않고, 도구 차원의 개선 가능성까지 함께 검토하려고 합니다.