NestJS ESM 전환 시 경로 별칭 문제 해결
Posted: Updated:
#NodeJS#ESM#Typescript
1. 문제 상황
NestJS 프로젝트를 ESM(ES Module) 모드로 전환한 후, 기존에 편리하게 사용하던 경로 별칭(@/shared/...)이 정상적으로 동작하지 않는 문제를 확인했습니다. 이로 인해 빌드 결과물 실행 시 런타임 에러가 발생하고, Jest 테스트 환경에서도 모듈을 찾지 못하는 현상이 나타났습니다.
- Jest:
Configuration error: Could not locate module @/shared/... - Node.js 런타임:
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/shared' imported from ... - 특이사항:
tsconfig-paths라이브러리를 사용 중임에도 ESM 환경에서는 별칭 해석이 실패했습니다.
2. 원인 분석
문제를 파헤쳐 보니 TypeScript의 설계 원칙과 ESM의 엄격한 로더 시스템이 충돌하고 있었습니다.
- TypeScript의 한계:
tsc는paths설정을 오직 타입 체크용으로만 사용합니다. 빌드 후 결과물(dist)을 확인해 보면@/별칭이 상대 경로로 치환되지 않고 그대로 남아 있어, Node.js가 이를 실제 경로로 인식하지 못했습니다. - ESM 로더의 엄격함:
- CJS 환경에서는
tsconfig-paths/register가require()를 가로채서 경로를 해결해 주었지만, ESM은 로더 시스템이 비동기적이고 훨씬 엄격하여 기존 방식의 가로채기가 통하지 않았습니다. - ESM은 명시적인 확장자(
.js)를 요구하는데, 별칭과 확장자 처리가 동시에 이루어지는 과정에서 표준 로더와 충돌이 발생했습니다.
- CJS 환경에서는
3. 해결 방안
3.1. 빌드 타임 경로 치환 (tsc-alias)
빌드가 완료된 후 dist 내의 별칭을 물리적인 상대 경로로 강제 치환하는 방식입니다.
- 장점: 코드에서 계속 별칭을 쓸 수 있습니다.
- 단점: 빌드 파이프라인에
tsc-alias단계를 추가해야 하고, Jest 설정(moduleNameMapper)도 별칭과 확장자를 동시에 처리하도록 복잡하게 수정해야 합니다.
// jest.config.js
moduleNameMapper: {
// 1. .js로 끝나는 별칭을 먼저 잡아서 확장자 제거 후 매핑
'^@/(.*)\\.js$': '<rootDir>/src/$1',
// 2. 나머지 별칭 매핑
'^@/(.*)$': '<rootDir>/src/$1',
// 3. 상대 경로 뒤의 .js 제거
'^(\\.{1,2}/.*)\\.js$': '$1',
}3.2. Node.js 표준 기능 활용 (imports 필드)
package.json의 imports 필드를 사용하여 Node.js 자체 별칭 기능을 쓰는 방식입니다.
- 장점: 외부 라이브러리 없이 표준 기능으로 별칭 해석이 가능합니다.
- 단점: 별칭 형식이
#/*와 같이 표준 규격을 따라야 하며, 여전히tsconfig와의 이중 설정 관리가 필요합니다.
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"#/*": ["src/*"]
}
}
}// package.json
{
"name": "backend",
"type": "module",
"imports": {
"#/*": "./dist/*"
}
}3.3. ESM 표준 방식 준수
별칭에 의존하지 않고, ESM 명세대로 상대 경로와 확장자를 명시하는 방법입니다.
- 장점: 추가적인 빌드 도구나 런타임 로더가 전혀 필요 없습니다. 브라우저와 Node.js 표준에 완벽히 부합하여 구조가 가장 단순하고 견고합니다.
- 단점: 폴더 깊이가 깊어질 경우
../../..와 같은 경로가 길어져 가독성이 떨어질 수 있습니다.
최종 해결방법
여러 대안을 검토한 끝에, 3번 방식인 ESM 표준 방식을 준수하기로 결정했습니다.
무분별한 별칭 사용은 빌드 도구에 대한 의존성을 높이고 환경 설정의 복잡도를 증가시키기 때문입니다. 대신, 상대 경로가 너무 길어지는 단점을 보완하기 위해 전체적인 프로젝트 폴더 구조를 재설계했습니다.
- 결정 사항: 폴더 깊이를 최대 2단계(
../../)까지만 가지도록 구조를 최적화했습니다. - 효과:
- 복잡한 빌드 스크립트(
tsc-alias등)를 제거하여 빌드 파이프라인이 단순해졌습니다. - 별도의 로더 설정 없이 런타임과 테스트 환경이 안정적으로 동작합니다.
- 표준 ESM 사양을 따름으로써 향후 다른 도구로의 전환 시에도 유연한 구조를 갖추게 되었습니다.
- 복잡한 빌드 스크립트(
Comments (0)
댓글을 불러오는 중...