모듈 해석 전략
node·bundler·classic 해석 전략이 import 경로를 실제 파일과 타입 선언으로 찾는 순서를 이해하고 추적 옵션으로 진단합니다.
타입스크립트 컴파일러(tsc)는 import 또는 export 문에서 참조하는 모듈의 실제 파일을 찾아내는 복잡한 과정을 거칩니다.
이 과정을 모듈 해석(Module Resolution)이라고 하며, 타입스크립트가 소스 코드의 타입을 정확하게 확인하고 올바른 자바스크립트 코드를 생성하는 데 매우 중요합니다.
타입스크립트는 Node.js 환경의 모듈 해석 방식과 유사하게 동작하며, tsconfig.json 파일의 moduleResolution 컴파일러 옵션을 통해 이 전략을 설정할 수 있습니다.
타입스크립트 컴파일러(tsc)는 import 또는 export 문에서 참조하는 모듈의 실제 파일을 찾아내는 복잡한 과정을 거칩니다. 이 과정을 모듈 해석(Module Resolution)이라고 하며, 타입스크립트가 소스 코드의 타입을 정확하게 확인하고 올바른 자바스크립트 코드를 생성하는 데 매우 중요합니다.
- 1모듈 해석의 기본 원리
타입스크립트 컴파일러는 다음 두 가지 유형의 모듈 참조를 처리해야 합니다. 모듈 해석(Module Resolution)
- 2moduleResolution 컴파일러 옵션
tsconfig.json의 compilerOptions.moduleResolution 옵션은 타입스크립트가 모듈을 해석하는 방식을 결정합니다. 상대 참조 (./, ../)
- 3추가적인 모듈 해석 관련 tsconfig.json 옵션
moduleResolution 외에도 모듈 해석에 영향을 미치는 중요한 compilerOptions가 있습니다. 비-상대 참조 (Non-relative imports)
- 4모듈 해석 원리와 컴파일러 옵션 기준
정확성 이 과정을 모듈 해석(Module Resolution)이라고 하며, 타입스크립트가 import 문자열을 실제 파일과 선언 파일로 연결합니다. 비용 상대 참조(./, ../)는 import { someFunc } from './myFile';처럼 현재 파일 기준으로 경로를 적습니다. 확장성 비-상대 참조(Non-relative imports)는 import * as React from 'react';처럼 패키지 이름이나 별칭을 기준으로 찾습니다. 예외 파일 찾기: import 문에 지정된 이름에 해당하는 파일을 찾아야 합니다.
- 5정확성
과정을 모듈 해석(Module Resolution)이라고 하며, 타입스크립트가 import 문자열을 실제 파일과 선언 파일로 연결합니다.
- 6비용 상대 참조(./, ../)
import { someFunc } from './myFile';처럼 현재 파일 기준으로 경로를 적습니다.
- 7확장성 비-상대 참조(Non-relative
imports)는 import * as React from 'react';처럼 패키지 이름이나 별칭을 기준으로 찾습니다.
- 8예외 파일 찾기
import 문에 지정된 이름에 해당하는 파일을 찾아야 합니다.
모듈 해석의 기본 원리
타입스크립트 컴파일러는 다음 두 가지 유형의 모듈 참조를 처리해야 합니다.
상대 참조 (./, ../):
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 컴파일러 옵션
import 문자열이 상대 경로인지, package인지, alias인지에 따라 compiler가 탐색하는 후보가 달라진다.
| 질문 | 판정 |
|---|---|
| ./ 또는 ../ 로 시작하나? | 상대 경로로 파일 후보 탐색 |
| package 이름인가? | node_modules와 package.json exports/types 확인 |
| paths alias인가? | baseUrl/paths 설정을 먼저 적용 |
| 타입만 필요한가? | .d.ts와 @types 후보까지 확인 |
| 신호 | 해석 |
|---|---|
| 실패 신호 | Cannot find module |
| 점검 순서 | specifier → tsconfig → package exports → d.ts |
| 판단 | 런타임 해석과 타입 해석을 둘 다 맞춘다 |
tsconfig.json의 compilerOptions.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등도 탐색합니다.
예시: 프로젝트 구조
src/app.ts에서
import { helperFunction } from './utils/helper'; // 상대 경로
import { someValue } from 'some-package'; // 비-상대 경로 (node_modules에서 찾음)
helperFunction();
console.log(someValue);node 전략은 Node.js 애플리케이션이나 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이 명령어는 컴파일러가 어떤 파일을 어디서 찾으려 했는지, 어떤 규칙을 적용했는지 등 상세한 로그를 출력하여 문제를 진단하는 데 큰 도움을 줍니다.
로그에는 정상적인 후보 실패도 많다. 대상 import의 최종 결과에서 거꾸로 읽는다.
- 대상 고정
Resolving module "@/utils" from src/app.ts import 하나와 출발 파일을 고른다.
- 알고리즘·조건
moduleResolution: bundler conditions: types, import 실행 환경과 같은 전략인지 본다.
- 분기와 후보
paths / package exports .ts → .tsx → .d.ts 별칭·조건 뒤 후보 순서를 본다.
- 결과에서 역추적
resolved to ... was not resolved 최종 판정 결과에서 예상 밖 분기로 돌아간다.
- Matched pattern "@/*"tsconfig ↔ 실제 경로
Matched pattern "@/*" paths 는 탔지만 substitution 경로가 틀릴 수 있다. tsconfig ↔ 실제 경로
- conditional exportspackage.json exports
conditional exports types · import · require 중 선택 조건을 본다. package.json exports
- File does not exist다음 후보와 최종 결과
File does not exist 확장자 후보 한 줄만으로 원인이라고 단정하지 않는다. 다음 후보와 최종 결과
모듈 해석 전략은 타입스크립트 프로젝트의 빌드 설정과 직접적으로 연관되어 있습니다.
프로젝트 환경(Node.js, 브라우저, 번들러 사용 여부 등)에 맞춰 moduleResolution 옵션을 올바르게 설정하는 것이 중요합니다.
특히 최신 Node.js 환경이나 모던 번들러를 사용하는 웹 프로젝트에서는 bundler 전략을 함께 고려할 필요가 있습니다.
baseUrl, paths 같은 추가 옵션을 활용하면 모듈 임포트 경로를 더 깔끔하게 관리하고 개발 경험을 개선할 수 있습니다.
모듈을 찾지 못하는 오류가 생기면 import 문자열부터 패키지의 exports와 types 필드까지 순서대로 확인하는 것이 좋습니다.
아래 점검표는 --traceResolution 로그를 읽을 때 어떤 단서를 먼저 봐야 하는지 압축해서 보여줍니다.
해석 실패는 보통 import 문자열, 해석 전략, 패키지 메타데이터, 타입 파일 위치 중 하나가 다른 방향을 가리킬 때 발생합니다.
- 1참조 종류 확인
상대 경로는 파일 위치, 패키지는 설정 기준으로 찾습니다.
- 2전략 확인
node , node16 , bundler 는 exports 와 확장자 규칙을 다르게 해석합니다.
- 3별칭 확인
baseUrl과 paths 별칭은 번들러 설정도 알아야 합니다.
- 4타입 진입점 확인
타입 진입점이 실제 JS API와 맞는지 봅니다.
해석 실패를 줄이려면 설정 변경 전후로 실제 후보 파일, 패키지 메타데이터, 번들러 규칙이 같은 방향을 가리키는지 함께 봐야 합니다.
moduleResolution 을 바꿀 때는 설정 이름보다 실제로 어떤 파일을 찾았고 건너뛰었는지 확인해야 합니다.
- import문자열
import 상대 경로, 별칭, 패키지 이름 중 어떤 규칙으로 시작하는지 먼저 나눕니다.
- tsconfig컴파일러 기준
tsconfig baseUrl , paths , types 가 후보를 줄이거나 넓히는지 봅니다.
- package패키지 메타
package 패키지 메타가 타입 파일과 실행 파일을 함께 가리켜야 합니다.
- bundler도구 체인
bundler 별칭이 빌드에서도 해석되는지 함께 맞춥니다.
다음 다이어그램은 모듈 해석 전략과 tsconfig 옵션을 경로 추적 기준으로 정리한 표입니다.
node, bundler, classic 전략과 paths, baseUrl, traceResolution을 함께 보면 타입스크립트가 모듈을 찾는 과정을 추적할 수 있습니다.
- 1패키지 규칙
node 전략 Node.js의 파일 확장자, package.json, node_modules 탐색 흐름을 따릅니다. moduleResolution: node
- 2번들러 친화
bundler 전략 현대 번들러가 처리하는 경로와 package exports 흐름에 맞춥니다. bundler
- 3별칭 연결
paths 설정 baseUrl과 paths는 import 별칭을 TypeScript 해석 규칙에 알려 줍니다. paths
- 4원인 확인
추적 명령 traceResolution으로 어떤 후보를 찾고 왜 실패했는지 로그를 봅니다. traceResolution
모듈 해석 전략을 코드에 적용하기 전, 컴파일 오류가 막아 줄 지점과 사람이 약속해야 할 지점을 나눕니다.
TypeScript 컴파일러는 moduleResolution과 경로 설정을 바탕으로 import 대상의 소스, 선언 파일, 패키지 진입점을 찾습니다.
- 1import 문자열 읽기
해석 시작 상대 경로, 절대 별칭, 패키지 이름인지에 따라 탐색 규칙이 달라집니다. import x
- 2Node 방식 선택
옵션 moduleResolution은 Node 생태계 규칙과 최신 번들러 환경 중 어떤 기준을 따를지 정합니다. node16
- 3별칭과 기준점
경로 매핑 baseUrl과 paths를 쓰면 짧은 import가 실제 디렉터리로 매핑됩니다. paths
- 4해석 로그 확인
추적 traceResolution으로 어떤 후보 파일을 확인했고 왜 실패했는지 볼 수 있습니다. traceResolution
아래 다이어그램은 TypeScript 모듈 해석이 import 경로를 실제 파일과 타입 선언으로 찾는 과정을 정리합니다.
모듈 해석은 import 문자열을 기준으로 소스 파일, package.json, 타입 선언 파일을 찾아 연결하는 규칙입니다.
- 상대 경로파일 위치에서 찾기
상대 경로 ./ 또는 ../로 시작하면 현재 파일 기준으로 후보 확장자를 탐색합니다. ./lib/math
- node 전략Node 방식 탐색
node 전략 node_modules, package.json, index 파일을 고려해 모듈을 찾습니다. moduleResolution: "node"
- bundler 전략현대 번들러와 정렬
bundler 전략 package exports와 조건부 내보내기를 번들러 동작에 맞춰 해석합니다. "bundler"
- traceResolution탐색 과정 출력
traceResolution 왜 특정 파일을 찾지 못했는지 컴파일러의 후보 경로를 확인합니다. tsc --traceResolution