본문으로 건너뛰기

안동민 개발노트

본문 시작

타입 선언 파일 작성 및 사용

타입이 없는 JavaScript 유틸리티의 함수·옵션·반환값을 d.ts로 선언해 프로젝트와 패키지 배포에서 함께 사용합니다.

앞서 8장 1절에서 .d.ts 파일이 무엇이고 왜 필요한지, 그리고 declare 키워드의 기본적인 사용법을 알아보았습니다.

이번 절에서는 실제 시나리오를 바탕으로 타입 선언 파일(.d.ts)을 어떻게 작성하고 프로젝트에서 사용하는지 구체적인 예시와 함께 살펴보겠습니다.

JavaScript 구현과 .d.ts 는 하나의 공개 API를 공유한다

선언 파일은 실행 코드를 복제하지 않고, 실제 export의 이름·입력·반환 타입을 소비 코드에 전달한다.

  1. 런타임 API 확인

    StringUtils.js 가 내보내는 함수 이름과 실제 동작 범위를 먼저 고정한다.

  2. 시그니처 선언

    .d.ts 에는 구현 없이 매개변수, 반환값, null 허용 범위만 적는다.

  3. 경로 일치

    선언의 모듈 이름과 소비 코드의 import 문자열이 같은 대상을 가리켜야 한다.

  4. 호출부 검사

    컴파일러는 선언을 읽어 잘못된 인자와 반환값 오해를 실행 전에 드러낸다.


시나리오: 자바스크립트 유틸리티 라이브러리에 타입 추가하기

우리는 이미 존재하는 간단한 자바스크립트 유틸리티 파일이 있다고 가정하고, 이 파일에 타입스크립트의 타입 정보를 추가하는 .d.ts 파일을 작성해볼 것입니다.

자바스크립트 파일 (src/js/StringUtils.js)
src/js/StringUtils.js
// 이 파일은 CommonJS 모듈로 작성되었다고 가정합니다.

/**
 * 주어진 문자열의 첫 글자를 대문자로 변환합니다.
 * @param {string} str - 원본 문자열
 * @returns {string} 첫 글자가 대문자로 변환된 문자열
 */
function capitalize(str) {
  if (typeof str !== 'string' || str.length === 0) {
    return '';
  }
  return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * 주어진 문자열이 비어있는지 (null, undefined, 빈 문자열) 확인합니다.
 * @param {string | null | undefined} str - 확인할 문자열
 * @returns {boolean} 비어있으면 true, 아니면 false
 */
function isEmpty(str) {
  return str === null || str === undefined || str.length === 0;
}

module.exports = {
  capitalize,
  isEmpty
};

이 자바스크립트 파일은 capitalizeisEmpty라는 두 함수를 CommonJS 방식으로 내보냅니다.

타입스크립트 프로젝트에서 이 함수들을 import하여 사용할 때, 타입 정보가 없으면 any로 추론되어 타입 검사의 이점을 누릴 수 없습니다.


타입 선언 파일 작성하기 (.d.ts)

이제 위 StringUtils.js 파일에 대한 타입 정보를 담은 StringUtils.d.ts 파일을 작성해봅시다.

이 파일은 src/js 폴더와 동일한 레벨에 생성하거나, 별도의 types 또는 @types 폴더에 생성할 수 있습니다.

여기서는 src/js/StringUtils.d.ts로 생성하겠습니다.

타입 선언 파일 (src/js/StringUtils.d.ts)
src/js/StringUtils.d.ts
// 이 파일은 'StringUtils'라는 CommonJS 모듈에 대한 타입 선언 파일입니다.
// declare module '모듈 이름' { ... } 구문을 사용합니다.
declare module './StringUtils' {
  /**
   * 주어진 문자열의 첫 글자를 대문자로 변환합니다.
   * @param str 원본 문자열
   * @returns 첫 글자가 대문자로 변환된 문자열
   */
  export function capitalize(str: string): string;

  /**
   * 주어진 문자열이 비어있는지 (null, undefined, 빈 문자열) 확인합니다.
   * @param str 확인할 문자열
   * @returns 비어있으면 true, 아니면 false
   */
  export function isEmpty(str: string | null | undefined): boolean;

  // 만약 기본 내보내기가 있다면 아래와 같이 선언할 수 있습니다.
  // export default class StringUtil { ... }
}

// 참고: 만약 이 js 파일이 ES 모듈처럼 동작한다면, 다음과 같이 직접 export를 선언합니다.
// export function capitalize(str: string): string;
// export function isEmpty(str: string | null | undefined): boolean;
코드 분석

declare module './StringUtils': 이 구문은 특정 모듈에 대한 타입 선언을 시작함을 의미합니다.

'./StringUtils'는 우리가 타입 정의를 제공하고자 하는 자바스크립트 모듈의 경로(또는 이름)입니다.

TypeScript는 이 경로를 사용하여 해당 .js 파일과 이 .d.ts 파일을 연결합니다.

  • 중요: 여기서 모듈 경로는 .js 파일에서 import 또는 require할 때 사용하는 경로와 일치해야 합니다. .js 확장자는 생략하는 것이 일반적입니다.

export function capitalize(str: string): string;: 자바스크립트 파일에서 module.exports.capitalize로 내보냈던 capitalize 함수의 타입 시그니처를 선언합니다.

실제 구현 없이 오직 함수 시그니처만 작성합니다.

export 키워드를 사용하여 이 함수가 모듈 외부로 내보내진다는 것을 명시합니다.

export function isEmpty(str: string | null | undefined): boolean;: isEmpty 함수에도 동일하게 타입 시그니처를 선언합니다.

여기서는 str 매개변수가 string, null, undefined 중 하나일 수 있음을 유니온 타입으로 정확히 명시했습니다.

JSDoc 주석: JSDoc 주석(/** ... */)은 타입스크립트의 타입 정의 파일에서도 유용합니다.

IDE나 에디터에서 함수 사용 시 툴팁으로 표시되어 개발자에게 자세한 정보를 제공합니다.


타입 선언 파일 사용하기

이제 app.ts 파일에서 StringUtils 모듈을 가져와 타입 안전하게 사용해봅시다.

타입스크립트 파일 (src/app.ts)
src/app.ts
import { capitalize, isEmpty } from './js/StringUtils'; // .d.ts 파일에 의해 타입 정보가 제공됨

const myString = "hello world";
const capitalizedString = capitalize(myString);
console.log(capitalizedString); // "Hello world"

const emptyString = "";
const isStringEmpty = isEmpty(emptyString);
console.log(`"${emptyString}" is empty: ${isStringEmpty}`); // "is empty: true"

const nullString: string | null = null;
const isNullEmpty = isEmpty(nullString);
console.log(`null is empty: ${isNullEmpty}`); // "is empty: true"

// 잘못된 타입의 인자 전달 시 컴파일 오류 발생
// const num = 123;
// const capitalizedNum = capitalize(num); // Error: 'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
컴파일러 동작

타입스크립트 컴파일러는 import { capitalize, isEmpty } from './js/StringUtils'; 구문을 만나면, 먼저 StringUtils.ts 파일을 찾습니다.

StringUtils.ts 파일이 없으면, StringUtils.d.ts 파일을 찾습니다.

StringUtils.d.ts 파일이 있으면, 해당 파일의 declare module './StringUtils' 블록 안에 정의된 capitalizeisEmpty의 타입 시그니처를 읽어와 app.ts에서 사용되는 이 함수들에 대한 타입 정보를 제공합니다.

이 덕분에 capitalize(myString)와 같이 올바른 타입으로 함수를 호출하면 문제가 없지만, capitalize(num)처럼 잘못된 타입으로 호출하면 컴파일 타임에 오류를 잡아낼 수 있게 됩니다.

import 한 줄은 구현 파일과 타입 시그니처를 따로 찾아 만난다

컴파일러는 module resolution으로 실제 파일을 찾고, 타입 정보는 .ts 또는 .d.ts 선언에서 확보한다.

  1. import specifier 가져올 경

    문자열 읽기

  2. Resolve file tsconfig

    package 규칙으로 후보 탐색

  3. Find types .ts, .d.ts, types

    필드 확인

  4. Check usage 호출부

    선언 계약을 만족하는지 검사

점검기준
JS only package@types 또는 직접 declaration 필요
path aliascompiler와 bundler 설정 일치 필요
판단실행 경로와 타입 경로가 동시에 맞아야 한다

전역 타입 선언 (.d.ts 파일의 또 다른 용도)

특정 자바스크립트 코드가 모듈 시스템을 사용하지 않고 전역 스코프에 변수나 함수를 선언하는 경우(예: <script> 태그로 로드되는 레거시 코드나 브라우저 API), declare 키워드를 최상위 레벨에서 사용하여 전역 타입을 선언할 수 있습니다.

전역 타입 선언 파일 (src/types/global.d.ts)
src/types/global.d.ts
// 전역 변수 선언
declare var MY_APP_NAME: string;
declare const VERSION_NUMBER: number;

// 전역 함수 선언
declare function logActivity(message: string, level: 'info' | 'warn' | 'error'): void;

// 전역 인터페이스 확장 (예: Window 객체에 새로운 속성 추가)
interface Window {
  myGlobalData?: {
    userId: number;
    sessionId: string;
  };
}

// 또는 declare namespace를 사용한 전역 객체 선언
declare namespace Analytics {
  function trackEvent(eventName: string, data?: object): void;
  function setUserId(id: string): void;
}
사용 예시 (src/main.ts)
src/main.ts
// 타입스크립트 컴파일러는 global.d.ts를 자동으로 인식합니다.
console.log(`Application Name: ${MY_APP_NAME}`);
console.log(`Version: ${VERSION_NUMBER}`);

logActivity("User logged in", "info");

if (window.myGlobalData) {
  console.log(`User ID: ${window.myGlobalData.userId}`);
}

Analytics.trackEvent("page_view", { path: "/dashboard" });

// logActivity("Invalid level", "debug"); // Error: 'debug' 형식은 '"info" | "warn" | "error"' 형식에 할당될 수 없습니다.

이러한 전역 선언 파일은 tsconfig.jsoninclude 설정에 의해 컴파일러가 인식할 수 있는 경로에 두기만 하면 자동으로 전역 스코프의 타입 정보를 제공합니다.

아래 다이어그램은 로컬 선언, 전역 선언, 배포용 선언이 각각 어디에 놓이고 어떤 방식으로 TypeScript에 연결되는지 한 번에 비교합니다.

TypeScript는 import 문자열에서 선언 파일 위치를 추적한다

같은 .d.ts 라도 모듈 옆, 전역 선언, 패키지 산출물 중 어디에 있느냐에 따라 해석 경로가 달라진다.

  1. 구현 파일과 선언 파일을 나란히 둔다

    local module JS 구현을 유지하면서 타입만 보강할 때 import 경로와 가장 강하게 연결된다.

  2. 전역 이름은 프로젝트 포함 범위에 묶인다

    global scope 브라우저 전역, 레거시 스크립트, Window 확장은 파일 위치보다 tsconfig 포함 여부가 중요하다.

  3. 소비자는 types 필드를 따라간다

    published package 빌드 결과의 선언 파일이 패키지 루트에서 명확히 노출되어야 자동 완성이 살아난다.

점검확인할 위치깨졌을 때 증상
모듈 옆 선언StringUtils.js / StringUtils.d.tsimport는 되지만 함수 인자와 반환 타입이 any로 보인다.
전역 선언global.d.ts, tsconfig include브라우저 전역 값이나 Window 확장을 찾지 못한다.
패키지 선언dist/index.d.ts, package.json types배포 후 라이브러리 소비자 쪽에서 타입이 사라진다.

.d.ts 파일의 배포

직접 작성한 타입스크립트 라이브러리를 npm으로 배포할 때는, tsconfig.jsondeclaration: true 옵션을 사용하여 컴파일 시 .js 파일과 함께 .d.ts 파일을 자동으로 생성하도록 설정합니다.

tsconfig.json
{
  "compilerOptions": {
    "declaration": true,      // .d.ts 파일 자동 생성
    "outDir": "./dist",       // .js 및 .d.ts 파일 출력 디렉토리
    // ...
  }
}

그리고 package.json 파일에 types (또는 typings) 필드를 추가하여, 라이브러리를 사용하는 다른 타입스크립트 프로젝트가 타입 정의 파일을 쉽게 찾을 수 있도록 경로를 명시합니다.

package.json
{
  "name": "my-awesome-library",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ]
}

main은 JavaScript 구현 파일, types는 타입 정의 파일을 가리키며, files는 배포할 dist 디렉터리를 지정합니다.

이렇게 설정하면, npm install my-awesome-library를 통해 라이브러리를 설치한 사용자들은 자동으로 타입 정보를 받아 타입 안전하게 라이브러리를 사용할 수 있습니다.


.d.ts 파일의 작성과 사용은 타입스크립트가 자바스크립트 생태계와 효과적으로 상호작용하는 핵심 방법입니다.

기존 자바스크립트 코드에 타입을 부여하거나 타입스크립트 라이브러리를 배포할 때, .d.ts 작성법을 능숙하게 다루는 것이 중요합니다.

declare module, export, 정확한 타입 시그니처 작성을 통해 코드 타입 안정성과 개발 경험을 크게 향상시킬 수 있습니다.

실무에서는 선언 파일을 작성한 뒤 실제 import 경로, 컴파일러 포함 범위, 패키지 메타데이터까지 이어서 확인해야 합니다.

아래 다이어그램은 로컬 작성부터 배포까지 .d.ts가 끊기지 않게 연결되는 흐름을 보여줍니다.

작성한 .d.ts가 실제 코드까지 이어지는 흐름

선언 파일은 작성만으로 끝나지 않습니다. JavaScript 구현, import 경로, 컴파일러 포함 범위, 배포 메타데이터가 같은 API를 가리키는지 확인해야 합니다.

  1. 1
    구현 확인

    StringUtils.js 가 실제로 내보내는 함수와 값 이름을 먼저 적습니다.

  2. 2
    선언 작성

    각 함수의 매개변수, 반환 타입, null 가능성을 구현 동작에 맞춰 선언합니다.

  3. 3
    경로 맞춤

    declare module 문자열과 실제 import 문자열이 같은지 비교합니다.

  4. 4
    포함 확인

    include 또는 패키지 구조 안에 선언 파일이 들어와야 타입스크립트가 읽습니다.

  5. 5
    배포 연결

    types 필드가 npm 소비자가 읽을 대표 선언 파일을 가리키게 둡니다.

선언 파일은 한 번 작성하고 끝나는 파일이 아니라 런타임 API가 바뀔 때마다 사용 지점, 빌드 출력, 패키지 메타데이터와 함께 갱신해야 하는 계약입니다.

선언 파일은 작성, 사용, 배포, 갱신까지 이어진다

타입 정의는 코드 옆에 놓는 문서가 아니라 소비자가 import하는 API 계약입니다.

  1. author
    타입 선언 작성

    author 실제 JS가 노출하는 값만 선언하고 내부 구현 세부사항은 타입 표면에서 제외합니다.

  2. use
    사용

    use 소비 코드의 import 경로와 선언 파일의 모듈 이름이 정확히 일치해야 합니다.

  3. ship
    배포

    ship declaration 출력과 types 필드를 함께 확인합니다.

  4. renew
    갱신

    renew 런타임 API가 바뀌면 타입 계약도 같이 갱신합니다.


다음 다이어그램은 타입 선언 파일 작성과 사용 과정을 API 표면 확인 기준으로 정리한 표입니다.

타입 선언 파일 역할

자바스크립트 유틸리티의 실제 export를 확인하고 .d.ts에서 함수, 객체, 전역 타입을 맞춰 선언해야 합니다.

  1. 실제 export 확인

    API 관찰 라이브러리가 함수 하나를 내보내는지 객체 여러 개를 내보내는지 먼저 봅니다. module exports

  2. 시그니처 번역

    선언 작성 함수 인자, 반환값, 객체 속성을 .d.ts에 구현 없이 적습니다. index.d.ts

  3. import 방식 일치

    사용 연결 선언한 모듈 이름과 실제 import 문자열이 같아야 타입이 연결됩니다. import util

  4. 타입 전달

    배포 포함 패키지의 types 필드나 파일 배치를 통해 사용자 프로젝트가 선언을 찾게 합니다. types

타입 선언 파일 작성 및 사용을 코드에 적용하기 전, 컴파일 오류가 막아 줄 지점과 사람이 약속해야 할 지점을 나눕니다.

타입 선언 파일은 자바스크립트 유틸리티를 안전한 API로 감쌉니다

기존 JS 파일의 함수와 객체에 .d.ts를 붙이면 구현을 바꾸지 않고도 TypeScript 프로젝트에서 타입 검사를 받을 수 있습니다.

  1. JS 유틸 분석

    시나리오 이미 있는 함수가 어떤 인자를 받고 어떤 값을 반환하는지 실행 코드와 호출부에서 확인합니다. utils.js

  2. API 모양 기록

    선언 작성 같은 이름의 .d.ts 파일에 함수, 타입, 모듈 내보내기 형태를 선언합니다. utils.d.ts

  3. 호출부 타입 검사

    사용 확인 TypeScript 파일에서 import 후 자동완성과 잘못된 인자 오류가 나타나는지 확인합니다. import

  4. 패키지 타입 노출

    배포 라이브러리라면 package.json의 types 필드로 선언 파일 진입점을 가리킵니다. types

아래 다이어그램은 JavaScript 유틸리티 라이브러리에 타입 선언을 붙이고 사용하는 과정을 정리합니다.

타입 선언 파일 작성 실전

타입 선언 파일 작성은 실제 JavaScript 모듈의 사용법을 TypeScript 시그니처로 옮겨 호출부 검사를 가능하게 만드는 작업입니다.

  1. 시나리오 파악
    JS API 사용법 확인

    시나리오 파악 유틸리티 함수가 어떤 인자를 받고 무엇을 반환하는지 실제 코드에서 먼저 확인합니다. utils.js

  2. 모듈 선언
    import 경로와 맞추기

    모듈 선언 declare module 안의 이름은 사용자가 가져오는 경로 문자열과 같아야 합니다. declare module "./utils"

  3. 사용 확인
    호출부 자동완성

    사용 확인 선언 파일을 추가한 뒤 TypeScript 파일에서 타입 오류와 자동완성이 동작하는지 봅니다. import { sum }

  4. 배포 기준
    types 필드 연결

    배포 기준 패키지로 배포할 때 package.json이 주 선언 파일을 가리키게 합니다. "types": "index.d.ts"