정의
Jest로 테스트를 돌리는데 어떤 npm 패키지가 최신 방식(ESM)으로만 배포돼서 import가 에러를 내며 안 불러와질 때, 그 패키지 코드를 내 레포 안 TS 파일로 복사해두고 테스트가 그 복사본을 대신 쓰게 만드는 우회책이다.
용어 먼저 풀면: ESM(ECMAScript Modules)은 import/export를 쓰는 최신 자바스크립트 모듈 방식, CJS(CommonJS)는 require를 쓰는 예전 방식이고 Jest 기본 환경은 CJS다. vendoring(벤더링)은 외부 패키지 코드를 내 프로젝트 안으로 복사해 넣는 것, shim(심)은 원본 대신 끼워 넣는 얇은 대체 코드를 뜻한다. moduleNameMapper는 "이 import 이름이 나오면 실제로는 이 파일을 불러라"라고 Jest에 알려주는 경로 치환 설정이다.
정리하면 Jest ESM vendor shim은 Jest(ts-jest — TypeScript를 Jest에서 돌려주는 도구, CommonJS preset) 테스트에서 ESM 전용 npm 패키지를 직접 import하지 않고, 패키지 소스(또는 얇은 wrapper)를 레포 내부 TypeScript 파일로 vendoring한 뒤 moduleNameMapper로 import 경로를 그 파일로 우회하는 기법이다.
왜 필요한가
최신 npm 패키지들은 점점 ESM("type": "module") 단일 빌드로만 배포된다. 그런데 Jest 기본 파이프라인은 node_modules 안 코드를 CJS로 변환(transform)하지 않고 그대로 실행하려 해서, ESM 문법을 만나면 SyntaxError: Cannot use import statement outside a module(모듈 밖에서 import 못 씀)이 난다.
transformIgnorePatterns(어떤 패키지는 변환해서 읽으라고 지정하는 Jest 설정)에 해당 패키지를 추가하는 방법도 있지만, pnpm/yarn이 패키지를 설치하는 중첩된 경로·버전별 경로가 환경마다 달라서 CI와 로컬 사이에서 됐다 안 됐다(flaky) 하기 쉽다. vendor shim은 그 경로 문제를 아예 없앤다.
동작 원리
- 실패하는 import
from 'esm-only-pkg'확인. __tests__/vendor/esm-only-pkg.ts또는src/ordering/vendor/에 TS shim 작성(필요 API만 export).jest.config:
- ts-jest가 shim을 transform → CJS 테스트 런타임에서 require 가능.
- vendored upstream 코드는 lint/tsc 이슈가 있으면 파일 단위 suppress + 교체 경로 주석.
| 접근 | 장점 | 단점 |
|---|---|---|
| transformIgnorePatterns | upstream 그대로 | nested path fragile |
| Jest experimental ESM | idiomatic import | 설정·속도 부담 |
| vendor shim | 안정·예측 가능 | sync·라이선스 책임 |
실무 적용
- shim은 테스트/번들이 실제 쓰는 surface만 export — 전체 패키지 fork 최소화.
- pre-commit turbo lint가 vendored 파일까지 검사하면, 무관 upstream lint가 커밋을 막을 수 있다 → 파일 헤더 억제 또는 lint ignore path 정책.
isolatedModules: true(ts-jest)는 타입체크를 생략 —pnpm testgreen 후tsc --noEmit별도 확인.
트레이드오프
- vendor shim은 안정적이지만 upstream 업데이트 시 수동 sync 필요.
@ts-nocheck남용은 타입 안전 환상 — 신규 코드는 typed wrapper로 감싸기.
사용하면 안 되는 경우
- 패키지가 CJS dual build를 제공하고 Jest가 이미 안정 import하는 경우.
- shim 대신 전체 프로젝트를 Vitest ESM으로 이전하는 migration이 진행 중일 때(중복 투자).
흔한 실수
- transformIgnorePatterns regex만 바꿔도 pnpm
.pnpm/경로에서 계속 실패. - test green = ship — vendor
@ts-nocheck아래 타입 hole 방치. - shim 없이 dynamic
import()in test — Jest mock 타이밍 이슈.
관련 개념
- fractional-index-ordering — ESM-only ordering library를 Jest에서 쓸 때 흔한 동기