프로그램이 커지면 코드를 여러 파일로 나누어야 합니다. TypeScript의 모듈 시스템은 JavaScript ES 모듈을 기반으로 하며, 타입을 내보내고 가져오는 기능이 추가되어 있습니다.
import 또는 export 문이 있는 파일은 모듈입니다. 없는 파일은 스크립트로 취급되어 전역 스코프를 공유합니다.
// 이 파일은 모듈 (export가 있으므로)
export function greet(name: string) {
return `안녕하세요, ${name}`;
}
모듈은 자신만의 스코프를 가집니다. 다른 파일에서 이 파일의 greet를 쓰려면 명시적으로 import해야 합니다.
// utils.ts
export function add(a: number, b: number) {
return a + b;
}
export const PI = 3.14159;
export interface Point {
x: number;
y: number;
}
파일당 하나만 가능합니다.
// user.ts
export default class User {
constructor(public name: string) {}
}
// 선언 후 한꺼번에 내보내기
function foo() {}
function bar() {}
export { foo, bar };
export { foo as default }; // 기본 내보내기로도 가능
// 이름 가져오기
import { add, PI, Point } from "./utils";
// 기본 가져오기
import User from "./user";
// 별칭 사용
import { add as sum } from "./utils";
// 네임스페이스로 묶어서 가져오기
import * as Utils from "./utils";
Utils.add(1, 2);
런타임에 필요 없는 타입만 가져올 때 import type을 씁니다. 번들러가 타입 관련 코드를 제거할 수 있어 빌드 결과에 불필요한 코드가 남지 않습니다.
// 타입만 가져오기
import type { Point } from "./utils";
// 타입만 내보내기
export type { Point };
// 혼합 사용
import { add, type Point } from "./utils";
여러 모듈을 한 곳에서 내보내는 배럴(barrel) 패턴입니다.
// index.ts — 진입점 역할
export { add, PI } from "./utils";
export { default as User } from "./user";
export type { Point } from "./utils";
// 사용하는 쪽
import { add, User, Point } from "./index";
// 또는
import { add, User } from "."; // index.ts 자동 탐색
디렉터리 구조가 노출되지 않아 내부 구조가 바뀌어도 import 경로를 수정할 필요가 없습니다.
외부 모듈에 타입을 추가하는 기법입니다. 라이브러리 타입을 수정하지 않고 확장할 수 있습니다.
// Express의 Request 타입에 user 프로퍼티 추가
import "express";
declare module "express" {
interface Request {
user?: {
id: number;
name: string;
};
}
}
타입 정보가 없는 JavaScript 모듈을 임시로 처리할 때 씁니다.
// global.d.ts
declare module "some-untyped-lib" {
export function doSomething(value: string): void;
}
tsconfig.json의 paths로 긴 상대 경로를 짧게 줄일 수 있습니다.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// 긴 상대 경로 대신
import { add } from "../../../utils/math";
// 별칭 사용
import { add } from "@/utils/math";
TypeScript의 모듈 시스템은 JavaScript ES 모듈과 거의 동일하게 동작합니다. import type을 구분해서 사용하고 배럴 패턴으로 공개 API를 정리하면, 코드베이스가 커져도 의존 관계를 명확하게 유지할 수 있습니다.