본문으로 건너뛰기

안동민 개발노트

본문 시작

.d.ts 파일 작성법

구현이 없는 d.ts 파일에 declare로 JavaScript 변수·함수·클래스의 공개 타입을 기술하고 컴파일러가 찾도록 배치합니다.

타입스크립트는 타입 추론을 통해 많은 타입 정보를 자동으로 얻어낼 수 있지만, 자바스크립트 코드(라이브러리, 프레임워크, 레거시 코드 등)를 타입스크립트 프로젝트에서 사용할 때는 타입 정보를 알 수 없는 경우가 발생합니다.

이럴 때 필요한 것이 바로 타입 선언 파일(Declaration Files), 즉 .d.ts 파일입니다.

.d.ts 파일은 실제 구현 코드 없이, 오직 타입 정보만을 담고 있는 파일입니다.

자바스크립트 코드가 외부에 공개하는 변수, 함수, 클래스 등의 설명서 역할을 하며, 타입스크립트 컴파일러는 이 .d.ts 파일을 참조하여 해당 자바스크립트 코드의 타입을 정확하게 이해하고 타입 검사를 수행합니다.


.d.ts 파일이 필요한가?

선언과 런타임 구현의 이중 계약

DECLARATION DRIFT · DATA FLOW

선언과 런타임 구현의 이중 계약

JavaScript 구현과 declaration metadata는 typed consumer에서 만나지만 실제 export drift는 runtime smoke test까지 통과해야 발견할 수 있습니다.

선언과 런타임 구현의 이중 계약 JavaScript 구현과 d.ts 메타데이터가 typed consumer로 수렴하고 typecheck 뒤 runtime smoke test가 실제 export drift를 판정합니다. 실제 값 타입 약속 compare RUNTIME JavaScript 구현 실제 exports TYPE METADATA .d.ts 선언 설명한 exports CONSUMER TypeScript 소비자 typecheck SMOKE runtime 테스트 실제 import · 호출 OUTCOME 일치 또는 drift 배포 gate A DECLARATION DESCRIBES A VALUE · IT DOES NOT CREATE OR VALIDATE THE VALUE
  1. JavaScript 구현

    실제로 존재하는 export 이름과 동작을 제공합니다.

  2. .d.ts 선언

    동일한 공개 표면을 compiler에 설명합니다.

  3. typed consumer

    선언을 사용해 호출 계약을 검사합니다.

  4. runtime smoke

    실제 import와 호출로 declaration drift를 찾습니다.

  5. 배포 gate

    두 표면이 같을 때만 패키지를 공개합니다.

package-bundled types와 @types를 구분하고 존재하지 않는 runtime 값을 declare로 만들어 낸다고 설명하지 않습니다.

아래 다이어그램은 .d.ts 파일에서 declare var, declare function, declare module, 전역 확장 중 어떤 형태를 선택해야 하는지 판단하는 기준을 묶어 보여줍니다.

.d.ts 파일이 필요한 주요 이유는 다음과 같습니다.

자바스크립트 라이브러리 사용: jQuery, Lodash, React 등 수많은 자바스크립트 라이브러리들은 .js 파일 형태로 배포됩니다.

타입스크립트 프로젝트에서 이들을 사용할 때, .d.ts 파일이 없으면 해당 라이브러리의 함수나 객체에 접근할 때 any 타입으로 간주되어 타입 검사의 이점을 누릴 수 없습니다.

레거시 자바스크립트 코드 연동: 기존에 작성된 자바스크립트 코드를 타입스크립트 프로젝트에 통합할 때, 명시적인 타입 정보를 제공하여 타입 안전성을 확보합니다.

타입스크립트 라이브러리 배포: 타입스크립트로 작성된 라이브러리를 배포할 때, .ts 파일은 .js 파일과 .d.ts 파일로 컴파일됩니다.

사용자는 .js 파일(실제 구현)과 .d.ts 파일(타입 정보)을 함께 받아 타입스크립트 환경에서 라이브러리를 안전하게 사용할 수 있습니다.

전역 변수/객체 선언: 브라우저 환경에서 window, document와 같이 전역 스코프에 존재하는 객체나 변수의 타입을 명시할 때 사용됩니다.


.d.ts 파일의 기본 구조와 declare 키워드

.d.ts 파일은 .ts 파일과 거의 동일한 문법을 사용하지만, declare 키워드를 사용하여 이것은 실제 구현이 아니라, 단지 타입 정보임을 선언하는 것을 명시합니다.

declare 키워드는 컴파일된 자바스크립트 코드에는 포함되지 않습니다.

주요 declare 사용 예시

declare var / declare let / declare const: 전역 변수를 선언합니다.

custom-globals.d.ts
declare const MY_GLOBAL_CONFIG: {
  apiUrl: string;
  timeout: number;
};

declare let globalCounter: number;

declare function: 전역 함수를 선언합니다.

global-funcs.d.ts
declare function customLog(message: string): void;
declare function calculateSum(...numbers: number[]): number;

declare class: 클래스를 선언합니다.

custom-class.d.ts
declare class MyEventEmitter<T> {
  on(eventName: string, listener: (data: T) => void): void;
  emit(eventName: string, data: T): void;
}

declare enum: 열거형을 선언합니다. (자바스크립트 런타임에는 열거형이 없으므로 const enum처럼 사용될 때 유용)

status-codes.d.ts
declare enum StatusCode {
  Success = 200,
  NotFound = 404,
  ServerError = 500
}

declare namespace: 관련된 전역 변수, 함수, 클래스 등을 하나의 논리적인 그룹으로 묶습니다. (7장 3절 "네임스페이스" 참조)

jquery.d.ts (예시)
declare namespace jQuery {
  interface Event {
    // ...
  }
  function ajax(url: string, settings?: any): JQueryPromise<any>;
  const version: string;
}

declare var $: typeof jQuery; // $도 jQuery 네임스페이스와 동일한 타입을 가짐

이 예시처럼 .d.ts 파일에서 declare namespace는 전역 스코프에 특정 이름을 가진 객체가 존재하며 그 안에 멤버들이 있음을 선언합니다.

declare module: 특정 경로 또는 모듈 이름을 가진 자바스크립트 모듈의 타입을 선언합니다.

  • ES 모듈/CommonJS 모듈의 타입 선언
    my-custom-module.d.ts
    declare module 'my-custom-module' {
      export interface Options {
        debug: boolean;
        port: number;
      }
      export function init(options: Options): void;
      export const version: string;
      export default class Client { // default export
        constructor(apiUrl: string);
        getData(): Promise<any>;
      }
    }

    이 파일을 작성하면, 다른 .ts 파일에서 import { init } from 'my-custom-module'; 또는 import Client from 'my-custom-module';와 같이 가져올 수 있게 됩니다.

  • 파일 확장자를 위한 모듈 선언: 웹팩과 같은 번들러를 사용할 때 .css, .png 등 자바스크립트가 아닌 파일을 import하는 경우가 있습니다. 이들에 대한 타입을 선언할 때도 declare module을 사용합니다.

    image.d.ts
    declare module '*.png' {
      const content: string; // 이미지 파일은 URL 문자열로 로드된다고 가정
      export default content;
    }
    
    declare module '*.svg' {
      import * as React from 'react';
      export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;
      const src: string;
      export default src;
    }

    이제 import myImage from './logo.png';와 같이 이미지 파일을 가져와도 타입 오류가 발생하지 않습니다.


.d.ts 파일 작성 팁

필요한 타입만 선언: .d.ts 파일은 실제 구현 코드를 포함하지 않으므로, 자바스크립트 코드가 외부에 노출하는 멤버들만 선언하면 됩니다.

내부적으로만 사용되는 변수나 함수는 선언할 필요가 없습니다.

가능한 한 구체적인 타입 사용: any 타입을 남용하지 않고, 가능한 한 정확하고 구체적인 타입을 사용하여 타입 안전성을 극대화해야 합니다.

제네릭, 유니온 타입, 인터섹션 타입 등을 적극적으로 활용하세요.

불필요한 export 피하기: .d.ts 파일이 모듈이 아닌 전역 타입 선언을 목적으로 한다면, 최상위 레벨에서 export를 사용하지 않아야 합니다.

export를 사용하면 해당 .d.ts 파일은 모듈로 간주됩니다.

  • 전역 선언: declare var $, declare function jQuery()
  • 모듈 선언: declare module 'lodash' { export function ... }

자동 생성 활용: tsc --declaration 또는 tsc -d 옵션을 사용하면 타입스크립트 소스 코드로부터 .d.ts 파일을 자동으로 생성할 수 있습니다.

이를 기반으로 필요한 부분을 수동으로 수정하는 것이 효율적입니다.

DefinitelyTyped 참조: 널리 사용되는 자바스크립트 라이브러리들은 DefinitelyTyped 프로젝트에서 타입 정의를 제공하며, 이는 @types/ 접두사가 붙은 npm 패키지로 배포됩니다 (예: npm install @types/lodash).

새롭게 .d.ts 파일을 작성하기 전에, 사용할 라이브러리 타입 정의가 이미 존재하는지 먼저 확인하는 것이 좋습니다.


.d.ts 파일의 위치와 컴파일러 설정

.d.ts 파일은 특별히 임포트할 필요 없이 타입스크립트 컴파일러가 자동으로 프로젝트 내에서 찾아 인식합니다.

  • tsconfig.jsoninclude: tsconfig.json 파일의 include 배열에 .d.ts 파일이 포함된 디렉토리를 지정합니다.

  • 패키지 내 types 필드: npm 패키지의 package.json 파일에 types 또는 typings 필드를 사용하여 주된 타입 정의 파일의 경로를 명시할 수 있습니다.

    package.json
    {
      "name": "my-cool-lib",
      "version": "1.0.0",
      "main": "dist/index.js",
      "types": "dist/index.d.ts"
    }

    types 필드는 타입스크립트가 패키지의 대표 타입 정의 파일을 찾을 경로입니다.

  • typeRootstypes: tsconfig.jsontypeRoots 옵션은 타입 정의 파일을 검색할 루트 디렉토리를 지정하며, types 옵션은 전역적으로 포함할 @types 패키지를 명시합니다.


.d.ts 파일은 자바스크립트 생태계와의 상호 운용성을 확보하고 타입스크립트의 타입 안전성이라는 핵심 가치를 유지하는 데 필수적인 요소입니다.

자바스크립트 라이브러리를 사용할 때, 레거시 코드를 통합할 때, 또는 직접 타입스크립트 라이브러리를 개발할 때 .d.ts 파일을 올바르게 작성하고 활용하는 방법을 숙지하는 것이 중요합니다.

작성할 선언 파일의 형태를 고르기 전에는 “값은 이미 존재하고, 우리는 타입만 설명한다”는 기준을 먼저 세워야 합니다.

다음 표는 전역 선언, 모듈 선언, 배포용 선언을 고를 때 함께 볼 항목을 정리합니다.

선언 파일 배포 파이프라인

DECLARATION EMIT · PROCESS

선언 파일 배포 파이프라인

공개 TypeScript 소스에서 선언을 생성하고 package metadata에 연결한 뒤 깨끗한 consumer가 typecheck와 runtime을 함께 검증합니다.

선언 파일 배포 파이프라인 공개 TypeScript source가 declaration emit, generated d.ts, package types 또는 exports metadata, consumer resolution, runtime 검증으로 이어집니다. 검사 생성 연결 설치 검증 PUBLIC SOURCE TypeScript API 공개 surface EMIT declaration 생성 declarationMap ARTIFACT 생성된 .d.ts 수동 편집 금지 PACKAGE types / exports 공개 진입점 CONSUMER 소비자 해석 clean fixture VERIFY type + runtime 동일 export EDIT THE SOURCE API · REGENERATE DECLARATIONS · NEVER PATCH GENERATED OUTPUT
  1. 공개 소스

    외부에 노출할 API를 TypeScript source에서 정의합니다.

  2. 선언 생성

    오류 없는 source로 d.ts와 declaration map을 만듭니다.

  3. package metadata

    types 또는 exports 조건이 생성된 선언을 가리킵니다.

  4. 소비자 검증

    깨끗한 fixture에서 typecheck와 실제 runtime import를 모두 확인합니다.

생성 declaration을 직접 수정하지 않고 source와 package metadata를 하나의 배포 권위로 유지합니다.

선언 파일을 추가할 때는 타입이 맞는지만 보는 것이 아니라 값의 출처, 스코프, 배포 경로가 서로 맞물리는지도 확인해야 합니다.

다음 절에서는 .d.ts 파일에서 사용되는 declare 키워드의 본질인 앰비언트 선언(Ambient Declarations)에 대해 더 자세히 알아보겠습니다.


다음 다이어그램은 .d.ts 파일 작성법을 선언 대상과 포함 범위 기준으로 정리한 표입니다.

아래 다이어그램은 .d.ts 파일이 JavaScript 코드와 TypeScript 타입 세계를 연결하는 방식을 정리합니다.