icon안동민 개발노트

모듈 해석 전략


 타입스크립트의 모듈 해석(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 설정 옵션

  1. baseUrl : 비상대 경로 임포트의 기준 디렉토리 설정
{
  "compilerOptions": {
    "baseUrl": "./src"
  }
}
  1. paths : 임포트 경로 별칭 설정
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["src/utils/*"],
      "@models/*": ["src/models/*"]
    }
  }
}
  1. 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 설정 검토, 패키지 설치 확인

모노레포 환경에서의 모듈 해석

 모노레포에서는 여러 프로젝트 간 의존성 관리가 중요합니다.

  1. 워크스페이스 설정 (예 : Yarn Workspaces)
  2. 프로젝트 간 참조 설정 (tsconfig.jsonreferences 필드 사용)
{
  "references": [
    { "path": "../common" },
    { "path": "../feature" }
  ]
}

Dynamic Import

 동적 임포트는 런타임에 모듈을 로드합니다.

async function loadModule() {
  const module = await import("./dynamicModule");
  module.doSomething();
}

 이 방식은 코드 스플리팅에 유용하며, 초기 로딩 시간을 줄일 수 있습니다.

번들러와 타입스크립트 컴파일러

 번들러(예 : Webpack)와 타입스크립트 컴파일러의 모듈 해석 방식이 다를 수 있습니다.

 이를 조화시키기 위해 다음을 고려해야 합니다.

  1. 번들러 설정과 tsconfig.json 설정 일치시키기
  2. 절대 경로 별칭 설정 동기화

 Webpack 예시

module.exports = {
  resolve: {
    alias: {
      "@utils": path.resolve(__dirname, "src/utils")
    }
  }
}

Best Practices와 모듈 구조 설계 지침

  1. 일관된 임포트 스타일 사용 : 상대 경로 또는 절대 경로 중 하나로 통일
  2. 경로 별칭 활용 : 긴 상대 경로 대신 별칭 사용
  3. 순환 의존성 방지 : 모듈 간 의존성 그래프 주기적 검토
  4. 모듈 경계 명확히 정의 : 각 모듈의 책임과 공개 API 명확화
  5. 폴더 구조 최적화 : 관련 기능 그룹화, 과도한 중첩 피하기
  6. 타입 정의 파일 적절히 사용 : 써드파티 라이브러리 타입 정의 관리
  7. 동적 임포트 전략적 사용 : 대규모 모듈 지연 로딩
  8. 번들러 설정 최적화 : 타입스크립트 컴파일러 설정과 동기화
  9. 모노레포 고려 : 대규모 프로젝트에서 모듈 공유 전략 수립
  10. 성능 모니터링 : 빌드 시간과 번들 크기 주기적 검토

 대규모 프로젝트 모듈 구조 예시

src/
  ├── core/             # 핵심 유틸리티 및 공통 기능
  ├── features/         # 주요 기능별 모듈
  │   ├── auth/
  │   ├── user/
  │   └── product/
  ├── api/              # API 관련 모듈
  ├── ui/               # UI 컴포넌트
  └── types/            # 전역 타입 정의