TypeScript 선언 파일(.d.ts) 작성법: 타입 없는 외부 라이브러리 마스터하기

타입스크립트 프로젝트를 진행하다 보면 npm install로 설치한 라이브러리가 타입을 지원하지 않아 Could not find a declaration file for module...이라는 에러를 마주할 때가 있습니다.
대부분은 @types/라이브러리명을 통해 해결할 수 있지만, 사내 라이브러리나 아주 오래된 오픈소스의 경우 직접 **선언 파일(.d.ts)**을 작성해야 합니다.
1. 선언 파일(.d.ts)이란?
.d.ts 파일은 Declaration의 약자로, 자바스크립트로 작성된 코드의 타입 정보만을 담고 있는 파일입니다. 런타임 코드(구현부)는 포함하지 않으며, 컴파일 타임에 타입스크립트가 코드의 구조를 이해하도록 돕는 역할을 합니다.
2. 기본 키워드: declare
타입스크립트에게 "이 변수나 함수는 어딘가(외부)에 이미 존재하니 타입만 알고 있어라"라고 말할 때 declare 키워드를 사용합니다.
전역 변수 선언 예시
HTML 파일의 <script> 태그를 통해 로드된 전역 변수를 사용할 때 유용합니다.
// global.d.ts
declare const APP_VERSION: string;
declare function initializeApp(config: object): void;
3. 외부 모듈 선언하기 (Ambient Modules)
타입이 없는 npm 패키지를 사용할 때는 declare module 문법을 사용합니다.
예제 1: 단순 모듈 선언
만약 my-legacy-lib이라는 라이브러리에 타입이 없다면, 가장 간단하게는 아래와 같이 선언하여 에러를 끌 수 있습니다. (다만 이 경우 모든 타입이 any가 됩니다.)
// types/my-legacy-lib.d.ts
declare module 'my-legacy-lib';
예제 2: 구체적인 타입 정의
라이브러리의 API를 알고 있다면 더 안전하게 타입을 정의할 수 있습니다.
// types/video-player.d.ts
declare module 'video-player' {
export interface PlayerConfig {
autoplay: boolean;
theme: 'dark' | 'light';
}
export class Player {
constructor(id: string, config: PlayerConfig);
play(): void;
pause(): void;
getCurrentTime(): number;
}
export const VERSION: string;
}
4. 전역 객체 확장하기 (Interface Merging)
기존의 Window 객체나 ProcessEnv에 새로운 속성을 추가해야 할 때가 있습니다. 타입스크립트의 인터페이스 병합 특성을 이용합니다.
예제 3: Window 객체 확장
// window.d.ts
declare global {
interface Window {
// 구글 분석기 등 외부 스크립트가 주입하는 객체
dataLayer: any[];
customConfig: {
apiToken: string;
};
}
}
// 이 선언이 모듈 파일(import/export가 있는 파일) 안에 있다면
// 반드시 export {}를 추가하여 모듈임을 명시해야 합니다.
export {};
5. 실전 예제: 타입 없는 로거 라이브러리 대응
cool-logger라는 가상의 자바스크립트 라이브러리가 있고, 이를 타입스크립트에서 사용한다고 가정해 봅시다.
자바스크립트 원본 (node_modules/cool-logger/index.js):
module.exports = {
log: function(msg) { console.log(msg); },
warn: function(msg) { console.warn(msg); },
level: 'info'
};
우리가 직접 작성할 선언 파일 (src/types/cool-logger.d.ts):
declare module 'cool-logger' {
export type LogLevel = 'info' | 'warn' | 'error';
export function log(msg: string): void;
export function warn(msg: string): void;
export let level: LogLevel;
}
6. 프로젝트 설정 (tsconfig.json)
작성한 .d.ts 파일을 타입스크립트가 인식하게 하려면 tsconfig.json 설정이 필요합니다.
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
},
"include": ["src/**/*", "src/types/**/*.d.ts"]
}
- typeRoots: 타입스크립트가 타입을 찾아볼 디렉토리 목록입니다.
- include: 컴파일 대상에 .d.ts 파일이 포함되도록 경로를 지정합니다.
7. 유의사항
- 구현 코드를 넣지 마세요: .d.ts 파일은 오직 타입 선언만 가능합니다. 실제 로직(console.log 등)을 넣으면 컴파일 에러가 발생합니다.
- DefinitelyTyped 확인: 직접 만들기 전에 TypeSearch에서 이미 커뮤니티가 만들어둔 타입이 있는지 먼저 확인하는 것이 좋습니다.
- 파일명 규칙: 모듈 선언 파일은 보통 라이브러리명.d.ts로 짓는 것이 관리하기 편합니다.
요약
상황해결 방법
| npm 패키지에 타입이 없을 때 | declare module '패키지명' { ... } |
| 전역 변수가 정의되어 있을 때 | declare const 변수명: 타입; |
| Window 객체에 속성을 추가할 때 | declare global { interface Window { ... } } |
| JS 파일과 함께 타입을 배포할 때 | 같은 경로에 이름만 다른 .d.ts 파일 생성 |
선언 파일을 직접 작성하는 능력은 오래된 프로젝트를 타입스크립트로 마이그레이션하거나, 사내 라이브러리를 관리할 때 매우 중요한 기술입니다.