모듈 시스템 — import, export, 타입 전용 import, 배럴 패턴
TypeScript의 모듈 시스템은 ES Modules 문법을 기반으로 하며, 타입 전용 import 와 배럴 패턴 등 TypeScript만의 기능이 추가되어 있습니다.
기본 import/export
// user.ts — named export
export interface User {
id: number;
name: string;
}
export function createUser(name: string): User {
return { id: Date.now(), name };
}
// 별도의 변수/함수를 묶어서 export
const DEFAULT_ROLE = 'user';
export { DEFAULT_ROLE };
// app.ts — named import
import { User, createUser, DEFAULT_ROLE } from './user';
const user: User = createUser('홍길동');
default export
// logger.ts
export default class Logger {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
// app.ts — 이름을 자유롭게 지정
import MyLogger from './logger';
const logger = new MyLogger();
TypeScript 커뮤니티에서는 named export를 권장 합니다. default export는 import할 때 이름이 자유로워서 일관성이 떨어지고, IDE의 자동 import가 덜 정확합니다.
타입 전용 import (import type)
TypeScript 3.8부터 타입만 import할 때 명시적으로 import type을 사용할 수 있습니다.
// types.ts
export interface User {
id: number;
name: string;
}
export type Status = 'active' | 'inactive';
export const DEFAULT_STATUS: Status = 'active';
// app.ts — 타입만 import
import type { User, Status } from './types';
// ✅ 타입으로만 사용 가능
const user: User = { id: 1, name: '홍길동' };
// ❌ 런타임에서 값으로 사용 불가
// console.log(User); // Error — import type은 런타임에 존재하지 않음
// 값과 타입을 동시에 import할 때
import { DEFAULT_STATUS, type User as UserType } from './types';
왜 import type을 쓰나요?
- **번들 크기 최적화 **:
import type은 컴파일 시 완전히 제거됨 - ** 순환 참조 방지 **: 타입만 참조하면 런타임 순환 의존성이 생기지 않음
- ** 의도 명확화 **: "이건 타입으로만 쓸 거야"라는 의도를 명시
// tsconfig.json에서 강제할 수 있음
{
"compilerOptions": {
"verbatimModuleSyntax": true // import type 사용을 강제
}
}
배럴 패턴(Barrel Pattern)
여러 모듈의 export를 하나의 index.ts로 모아서 re-export하는 패턴입니다.
// models/user.ts
export interface User { id: number; name: string; }
// models/product.ts
export interface Product { id: number; title: string; }
// models/index.ts — 배럴 파일
export { User } from './user';
export { Product } from './product';
export type { User as UserModel } from './user'; // 별칭도 가능
// app.ts — 깔끔한 import
import { User, Product } from './models'; // index.ts에서 가져옴
// 배럴 없이는
// import { User } from './models/user';
// import { Product } from './models/product';
배럴 패턴의 주의점
// ⚠️ 큰 프로젝트에서 배럴은 트리쉐이킹을 방해할 수 있음
// 하나만 import해도 배럴의 모든 모듈이 로딩될 수 있음
// 특히 이런 와일드카드 re-export는 피하는 것이 좋음
// export * from './user';
// export * from './product';
// ✅ 명시적 re-export가 더 안전
export { User } from './user';
export { Product } from './product';
namespace (권장하지 않음)
TypeScript 초기의 모듈 시스템이지만, ES Modules이 표준이 된 지금은 거의 사용하지 않습니다.
// ❌ namespace — 레거시
namespace Validation {
export function isEmail(value: string): boolean {
return value.includes('@');
}
}
// ✅ ES Module — 권장
export function isEmail(value: string): boolean {
return value.includes('@');
}
단, .d.ts 파일에서 전역 타입을 선언할 때는 namespace가 여전히 사용됩니다.
모듈 해석(Module Resolution)
// tsconfig.json
{
"compilerOptions": {
// 경로 별칭 설정
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@models/*": ["src/models/*"],
"@utils/*": ["src/utils/*"]
},
// 모듈 해석 방식
"moduleResolution": "bundler" // Node16, NodeNext, bundler 중 선택
}
}
// 경로 별칭 사용
import { User } from '@models/user';
import { formatDate } from '@utils/date';
면접 포인트:
moduleResolution의node(Node.js 전통 방식),node16(package.json exports 지원),bundler(번들러 친화적)의 차이를 알면 좋습니다.
동적 import
// 동적 import — 코드 스플리팅에 활용
async function loadModule() {
const { heavyFunction } = await import('./heavy-module');
heavyFunction();
}
// 타입만 가져올 때도 유용
type HeavyModule = typeof import('./heavy-module');
정리
- named export를 권장하고, default export는 가능한 피한다
import type으로 타입만 import하면 번들 크기 최적화와 순환 참조 방지에 도움이 된다- 배럴 패턴(
index.ts)은 import를 깔끔하게 만들지만 트리쉐이킹에 영향을 줄 수 있다 namespace는 레거시이므로 ES Modules을 사용한다paths별칭으로 긴 상대 경로를 줄일 수 있다
댓글 로딩 중...