icon
7장 : 모듈과 네임스페이스

모듈 해석 전략


타입스크립트 컴파일러(tsc)는 import 또는 export 문에서 참조하는 모듈의 실제 파일을 찾아내는 복잡한 과정을 거칩니다. 이 과정을 모듈 해석(Module Resolution) 이라고 하며, 타입스크립트가 소스 코드의 타입을 정확하게 확인하고 올바른 자바스크립트 코드를 생성하는 데 매우 중요합니다.

타입스크립트는 Node.js 환경의 모듈 해석 방식과 유사하게 동작하며, tsconfig.json 파일의 moduleResolution 컴파일러 옵션을 통해 이 전략을 설정할 수 있습니다.


모듈 해석의 기본 원리

타입스크립트 컴파일러는 다음 두 가지 유형의 모듈 참조를 처리해야 합니다.

상대 참조 (./, ../): import { someFunc } from './myFile'; 또는 import { anotherFunc } from '../components/AnotherComponent'; 이러한 참조는 현재 파일의 위치를 기준으로 상대 경로를 사용하여 다른 파일을 찾습니다.

비-상대 참조 (Non-relative imports): import * as React from 'react'; 또는 import { format } from 'date-fns'; 이러한 참조는 절대 경로, baseUrl, paths 매핑 또는 node_modules 폴더 내에서 모듈을 찾습니다.

모듈 해석의 핵심은 다음과 같습니다.

  • 파일 찾기: import 문에 지정된 이름에 해당하는 파일을 찾아야 합니다. .ts, .tsx, .d.ts, .js, .jsx 등 다양한 확장자를 고려합니다.
  • 타입 정의 파일 찾기: 실제 .js 파일 외에도 해당 모듈의 타입 정의 파일(.d.ts)을 찾아야 정확한 타입 정보를 얻을 수 있습니다.

moduleResolution 컴파일러 옵션

tsconfig.jsoncompilerOptions.moduleResolution 옵션은 타입스크립트가 모듈을 해석하는 방식을 결정합니다. 주로 두 가지 전략이 사용됩니다.

"node": Node.js 환경에서 모듈을 해석하는 방식과 일치합니다. Node.js 프로젝트(예: 백엔드 서버, CLI 도구)에서 가장 일반적으로 사용됩니다.

"bundler" (TypeScript 5.0+): 모던 번들러(Webpack, Vite, Rollup 등)의 해석 방식을 모방합니다. 웹 개발(프론트엔드) 프로젝트에서 권장됩니다.

"classic" (Legacy): 타입스크립트 초기에 사용되던 구식 전략입니다. 현재는 거의 사용되지 않습니다.

각 전략의 주요 특징을 살펴보겠습니다.

"node" 전략

node 전략은 Node.js의 require() 함수가 모듈을 찾는 방식과 동일하게 동작합니다.

  • 상대 경로: import { x } from './moduleA'는 다음과 같은 순서로 파일을 찾습니다.

    ./moduleA.ts

    ./moduleA.tsx

    ./moduleA.d.ts

    ./moduleA.js (실제 JavaScript 파일)

    ./moduleA/index.ts

    ./moduleA/index.tsx

    ./moduleA/index.d.ts

    ./moduleA/index.js

  • 비-상대 경로: import { x } from 'moduleB'는 다음과 같은 순서로 파일을 찾습니다.

    현재 디렉토리부터 시작하여 상위 디렉토리로 이동하면서 각 node_modules 폴더를 탐색합니다.

    ./node_modules/moduleB

    ./node_modules/moduleB/package.json에서 main 또는 types 필드를 확인하여 진입점을 찾습니다.

    만약 main"./dist/index.js"라면, moduleB의 루트 폴더로 간주하고 해당 파일을 찾습니다.

    ./node_modules/moduleB.ts, ./node_modules/moduleB.d.ts 등도 탐색합니다.

예시: 프로젝트 구조

app.ts
helper.ts
index.js
index.d.ts
tsconfig.json

src/app.ts에서

app.ts
import { helperFunction } from './utils/helper'; // 상대 경로
import { someValue } from 'some-package';       // 비-상대 경로 (node_modules에서 찾음)

helperFunction();
console.log(someValue);

node 전략은 Node.js 애플리케이션이나 create-react-app과 같이 Node.js 모듈 해석 방식을 따르는 번들러 환경에서 적합합니다.

"bundler" 전략 (TypeScript 5.0+)

bundler 전략은 Webpack, Vite, Rollup 등 현대적인 자바스크립트 번들러가 모듈을 해석하는 방식과 더 가깝습니다. 이 전략은 ES 모듈의 CJS 상호 운용성을 더 잘 모델링하고, Node.js의 "exports" 필드 지원을 개선하여 Node.js 16/ESM 호환 프로젝트에 더 적합합니다.

주요 특징

  • "exports" 필드 지원: package.json"exports" 필드를 사용하여 조건부 내보내기(conditional exports)를 더 정확하게 해석합니다. 이는 Node.js의 새로운 모듈 시스템과 웹 번들러의 동작을 통일하는 데 중요합니다.
  • type: "module" 지원: package.json"type": "module" 설정에 따라 .js 파일이 ES 모듈로 간주되는 경우를 더 정확하게 처리합니다.
  • Node.js의 main 필드 대신 exports 필드를 우선적으로 고려합니다.

이 전략은 특히 Node.js 환경에서도 ES 모듈(ESM) 구문을 사용하고자 할 때 (예: package.json"type": "module"을 설정한 프로젝트), 또는 프론트엔드 프로젝트에서 최신 번들러를 사용할 때 유용합니다.

"classic" 전략 (권장되지 않음)

classic 전략은 타입스크립트 초기에 존재하던 방식입니다. 상대 경로가 아닌 모듈을 찾을 때 node_modules를 탐색하지 않고, 단순히 현재 디렉토리와 상위 디렉토리에서 .ts, .d.ts 파일을 찾는 방식으로 동작합니다. 웹팩과 같은 번들러가 없던 시절, <script> 태그를 사용하여 파일을 로드하는 환경에서 사용되었습니다. 현재는 거의 사용되지 않습니다.


추가적인 모듈 해석 관련 tsconfig.json 옵션

moduleResolution 외에도 모듈 해석에 영향을 미치는 중요한 compilerOptions가 있습니다.

  • baseUrl: 비-상대 모듈 임포트의 기준이 되는 디렉토리를 지정합니다. 이 옵션이 설정되면, 타입스크립트는 node_modules를 검색하기 전에 baseUrl을 기준으로 모듈을 찾으려고 시도합니다.

    tsconfig.json
    // tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": "./src"
      }
    }
    // src/app.ts
    import { MyComponent } from 'components/MyComponent'; // ./src/components/MyComponent를 찾음
  • paths: baseUrl과 함께 사용하여 모듈 가져오기 경로에 대한 별칭(alias) 매핑을 정의합니다. 복잡한 상대 경로를 줄이고 모듈 구조를 추상화하는 데 매우 유용합니다.

    tsconfig.json
    // tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@app/*": ["src/app/*"],
          "@utils": ["src/utils/index.ts"]
        }
      }
    }
    src/app/pages/home.ts
    import { fetchData } from '@utils'; // src/utils/index.ts
    import { User } from '@app/models/user'; // src/app/models/user.ts
  • rootDirs: 가상의 루트 디렉토리 목록을 정의하여, 런타임에 여러 디렉토리의 내용을 하나의 논리적인 디렉토리 구조처럼 처리할 수 있게 합니다. 이는 빌드 시스템(번들러)이 여러 소스 디렉토리를 하나의 가상 디렉토리로 병합하는 경우에 유용합니다.

  • typeRoots: 타입 정의 파일(.d.ts)을 검색할 추가적인 디렉토리 목록을 지정합니다. 기본적으로 node_modules/@types가 포함됩니다.

  • types: 전역 타입 선언을 포함할 특정 @types 패키지 목록을 지정합니다. 이 목록에 없는 @types 패키지는 자동으로 포함되지 않습니다.


모듈 해석 과정 디버깅

모듈 해석이 예상대로 동작하지 않을 때는 tsc 명령어에 --traceResolution 플래그를 추가하여 자세한 해석 과정을 확인할 수 있습니다.

tsc --traceResolution

이 명령어는 컴파일러가 어떤 파일을 어디서 찾으려 했는지, 어떤 규칙을 적용했는지 등 상세한 로그를 출력하여 문제를 진단하는 데 큰 도움을 줍니다.


모듈 해석 전략은 타입스크립트 프로젝트의 빌드 설정과 직접적으로 연관되어 있습니다. 프로젝트의 환경(Node.js, 브라우저, 번들러 사용 여부 등)에 맞춰 moduleResolution 옵션을 올바르게 설정하는 것이 중요합니다. 특히 최신 Node.js 환경이나 모던 번들러를 사용하는 웹 프로젝트에서는 bundler 전략의 고려가 필요할 수 있습니다. baseUrl, paths와 같은 추가 옵션들을 활용하여 모듈 임포트 경로를 깔끔하게 관리하고 개발 경험을 개선할 수 있습니다.