모듈 해석 전략
타입스크립트의 모듈 해석(Module Resolution)은 컴파일러가 임포트 문에서 참조하는 모듈의 정의를 찾는 과정입니다.
이는 프로젝트의 구조와 성능에 중요한 영향을 미칩니다.
Classic vs Node 해석 전략
1. Classic 전략
- 단순하고 오래된 방식
- 상대 경로 : 현재 파일 기준으로 해석
- 비상대 경로 : 현재 파일부터 루트까지 각 상위 디렉토리에서 검색
2. Node 전략
- Node.js의 모듈 해석 알고리즘을 모방
- 상대 경로 :
./module
,../module
등 - 비상대 경로 :
node_modules
폴더에서 검색
예시
// Classic
import { foo } from "./foo"; // ./foo.ts 또는 ./foo.d.ts 검색
import { bar } from "bar"; // /root/src/bar.ts, /root/bar.ts 등 검색
// Node
import { foo } from "./foo"; // ./foo.js, ./foo.ts, ./foo/index.js 등 검색
import { bar } from "bar"; // ./node_modules/bar, ../node_modules/bar 등 검색
tsconfig.json 설정 옵션
- baseUrl : 비상대 경로 임포트의 기준 디렉토리 설정
{
"compilerOptions": {
"baseUrl": "./src"
}
}
- paths : 임포트 경로 별칭 설정
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["src/utils/*"],
"@models/*": ["src/models/*"]
}
}
}
- rootDirs : 가상 디렉토리 설정
{
"compilerOptions": {
"rootDirs": ["src", "test"]
}
}
상대 경로 vs 절대 경로 임포트
상대 경로
- 장점 : 파일 위치 변경 시 임포트 문 수정 불필요
- 단점 : 깊은 중첩 구조에서 가독성 저하
절대 경로
- 장점 : 간결하고 일관된 임포트 문
- 단점 : 파일 위치 변경 시 임포트 문 수정 필요
// 상대 경로
import { foo } from "../../../utils/foo";
// 절대 경로 (paths 설정 필요)
import { foo } from "@utils/foo";
타입 정의 파일 해석
.d.ts
파일 : 자바스크립트 라이브러리의 타입 정보 제공@types
패키지 : DefinitelyTyped 저장소의 타입 정의
사용자 정의 타입 정의
// global.d.ts
declare module "my-module" {
export function myFunction(): void;
}
일반적인 모듈 해석 문제
1. 순환 의존성
- 해결 : 인터페이스를 통한 의존성 분리, 의존성 방향 재설계
2. 모듈 not found
- 해결 : 경로 확인, tsconfig.json 설정 검토, 패키지 설치 확인
모노레포 환경에서의 모듈 해석
모노레포에서는 여러 프로젝트 간 의존성 관리가 중요합니다.
- 워크스페이스 설정 (예 : Yarn Workspaces)
- 프로젝트 간 참조 설정 (
tsconfig.json
의references
필드 사용)
{
"references": [
{ "path": "../common" },
{ "path": "../feature" }
]
}
Dynamic Import
동적 임포트는 런타임에 모듈을 로드합니다.
async function loadModule() {
const module = await import("./dynamicModule");
module.doSomething();
}
이 방식은 코드 스플리팅에 유용하며, 초기 로딩 시간을 줄일 수 있습니다.
번들러와 타입스크립트 컴파일러
번들러(예 : Webpack)와 타입스크립트 컴파일러의 모듈 해석 방식이 다를 수 있습니다.
이를 조화시키기 위해 다음을 고려해야 합니다.
- 번들러 설정과 tsconfig.json 설정 일치시키기
- 절대 경로 별칭 설정 동기화
Webpack 예시
module.exports = {
resolve: {
alias: {
"@utils": path.resolve(__dirname, "src/utils")
}
}
}
Best Practices와 모듈 구조 설계 지침
- 일관된 임포트 스타일 사용 : 상대 경로 또는 절대 경로 중 하나로 통일
- 경로 별칭 활용 : 긴 상대 경로 대신 별칭 사용
- 순환 의존성 방지 : 모듈 간 의존성 그래프 주기적 검토
- 모듈 경계 명확히 정의 : 각 모듈의 책임과 공개 API 명확화
- 폴더 구조 최적화 : 관련 기능 그룹화, 과도한 중첩 피하기
- 타입 정의 파일 적절히 사용 : 써드파티 라이브러리 타입 정의 관리
- 동적 임포트 전략적 사용 : 대규모 모듈 지연 로딩
- 번들러 설정 최적화 : 타입스크립트 컴파일러 설정과 동기화
- 모노레포 고려 : 대규모 프로젝트에서 모듈 공유 전략 수립
- 성능 모니터링 : 빌드 시간과 번들 크기 주기적 검토
대규모 프로젝트 모듈 구조 예시
src/
├── core/ # 핵심 유틸리티 및 공통 기능
├── features/ # 주요 기능별 모듈
│ ├── auth/
│ ├── user/
│ └── product/
├── api/ # API 관련 모듈
├── ui/ # UI 컴포넌트
└── types/ # 전역 타입 정의