icon
8장 : 타입 선언과 앰비언트 선언

앰비언트 모듈 선언


앞선 8.1절과 8.2절에서 .d.ts 파일이 무엇이고 어떻게 작성하는지 기본적인 내용을 살펴보았습니다. 그중에서도 declare 키워드를 사용하여 타입 정보만을 선언하는 것을 앰비언트 선언(Ambient Declarations) 이라고 부릅니다. 그리고 이 앰비언트 선언 중에서도 특정 모듈의 타입을 정의하는 것이 바로 앰비언트 모듈 선언(Ambient Module Declarations) 입니다.

앰비언트 모듈 선언은 주로 두 가지 경우에 사용됩니다.

  1. 타입스크립트 프로젝트에서 타입 정의가 없는 자바스크립트 모듈을 사용할 때.
  2. 웹팩(Webpack)이나 Vite 같은 번들러를 사용할 때 자바스크립트가 아닌 파일(예: 이미지, CSS 파일) 을 모듈처럼 import할 때.

이 절에서는 앰비언트 모듈 선언의 구체적인 사용법과 중요성에 대해 더 깊이 알아보겠습니다.


declare module 'module-name' 구문

앰비언트 모듈 선언의 핵심은 declare module 'module-name' 구문입니다. 여기서 'module-name'import 문에서 사용될 모듈의 이름 또는 경로를 나타냅니다.

// Example: some-legacy-library.d.ts
declare module 'some-legacy-library' {
  // 이 블록 안에 'some-legacy-library' 모듈의 타입을 정의합니다.

  export interface Options {
    debugMode: boolean;
    logLevel: 'info' | 'warn' | 'error';
  }

  export function initialize(options: Options): void;
  export function fetchData<T>(url: string): Promise<T>;
  export const version: string;

  // 기본(default) 내보내기가 있다면 이렇게 선언합니다.
  export default class SomeClient {
    constructor(apiKey: string);
    sendRequest(data: any): Promise<any>;
  }
}

some-legacy-library.d.ts 파일을 프로젝트에 포함시키면, 이제 다른 타입스크립트 파일에서 이 라이브러리를 타입 안전하게 가져와 사용할 수 있습니다.

// app.ts
import { initialize, fetchData, version } from 'some-legacy-library';
import SomeClient from 'some-legacy-library'; // default export 가져오기

initialize({ debugMode: true, logLevel: 'info' });
console.log(`Library Version: ${version}`);

async function loadData() {
  const data = await fetchData<{ name: string; value: number }>('/api/data');
  console.log(data.name, data.value);
}

const client = new SomeClient('my-api-key');
client.sendRequest({ type: 'report' });

// initialize({}); // Error: 'Options' 형식에 'debugMode' 및 'logLevel' 속성이 없습니다.

컴파일러는 import 'some-legacy-library' 구문을 만나면, node_modules에서 실제 some-legacy-library 모듈을 찾는 것 외에도, 해당 모듈 이름에 해당하는 .d.ts 파일을 찾아 타입 정보를 얻습니다. @types 패키지를 설치하는 것이 바로 이 앰비언트 모듈 선언 파일을 프로젝트에 추가하는 과정입니다.


와일드카드 모듈 선언

특정 파일 확장자를 가진 모든 파일에 대해 공통된 타입을 선언하고 싶을 때 와일드카드 문자 (*) 를 사용할 수 있습니다. 이는 이미지, CSS, JSON 등 웹팩과 같은 번들러가 모듈처럼 처리하는 비-자바스크립트/타입스크립트 파일을 임포트할 때 특히 유용합니다.

// types/custom-file-types.d.ts 또는 src/declarations.d.ts
declare module '*.png' {
  const value: string; // 일반적으로 이미지 파일은 번들링 후 URL 문자열로 처리됩니다.
  export default value;
}

declare module '*.svg' {
  // SVG 파일을 React 컴포넌트로 가져오는 경우 (예: @svgr/webpack)
  import * as React from 'react';
  export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;
  const src: string;
  export default src;
}

declare module '*.css' {
  const content: { [className: string]: string }; // CSS Module을 사용하는 경우
  export default content;
}

declare module '*.json' {
  const value: any; // JSON 파일은 객체로 직접 로드될 수 있습니다.
  export default value;
}

이제 이 선언 파일이 프로젝트에 포함되면, 다음과 같이 해당 확장자의 파일을 타입 오류 없이 가져올 수 있습니다.

// app.ts
import logo from './assets/logo.png'; // logo의 타입은 string
import Icon from './assets/icon.svg'; // Icon은 ReactComponent, 기본 export는 src (string)
import styles from './styles/main.css'; // styles의 타입은 { [className: string]: string }

console.log(logo); // 예: "/static/media/logo.9259f60f.png"
if (Icon.ReactComponent) {
  // React 컴포넌트로 사용 가능
}
console.log(styles.container); // 예: "main_container__abc123"

와일드카드 모듈 선언은 번들러 환경에서 파일 기반의 에셋을 모듈처럼 다룰 때 발생하는 타입 오류를 해결하는 표준적인 방법입니다.


전역 모듈과 스크립트 파일의 앰비언트 선언

간혹 모듈 시스템을 사용하지 않고 전역 스코프에 변수나 함수를 노출하는 자바스크립트 파일(스크립트 파일)의 타입을 선언해야 할 때가 있습니다. 이 경우 declare module 대신 declare namespace 또는 단순히 declare var/function/class를 사용합니다.

전역 스크립트 타입 선언 (global-script-types.d.ts)

// global-script-types.d.ts
// 이 파일은 HTML <script> 태그로 로드되는 자바스크립트 파일에 대한 타입 정의입니다.

// 전역 변수 선언
declare var MY_APP: {
  version: string;
  debugMode: boolean;
};

// 전역 함수 선언
declare function initializeApp(config: { theme: string }): void;

// 전역 객체 확장 (예: window 객체에 추가되는 속성)
interface Window {
  _paq?: Array<any>; // Matomo (Piwik) 같은 분석 스크립트가 추가하는 전역 변수
}

// 네임스페이스를 사용하여 관련된 전역 객체를 그룹화
declare namespace MyGlobalUtils {
  function formatCurrency(amount: number): string;
  class DataLoader {
    load(url: string): Promise<any>;
  }
}

이러한 파일들은 declare module 구문으로 감싸져 있지 않기 때문에, 해당 .d.ts 파일이 프로젝트에 포함되면 선언된 모든 타입이 전역 스코프에 추가됩니다.


앰비언트 모듈 선언의 중요성

  • 타입 안전성 확보: 자바스크립트 라이브러리나 외부 리소스에 대한 타입 정보를 제공하여, 타입스크립트 컴파일러가 잠재적인 오류를 잡아내고 코드 자동 완성을 제공할 수 있도록 합니다.
  • 개발 생산성 향상: IDE의 자동 완성, 매개변수 힌트, 타입 오류 검출 등의 기능을 활용하여 개발자가 더 빠르고 정확하게 코드를 작성할 수 있게 합니다.
  • 유지보수성 증대: 타입 정보를 통해 코드의 의도와 기대되는 데이터 형태를 명확히 하여, 다른 개발자가 코드를 이해하고 변경하기 쉽게 만듭니다.
  • 레거시 코드 통합: 기존 자바스크립트 코드베이스를 타입스크립트로 점진적으로 전환할 때, 핵심적인 역할을 합니다.

앰비언트 모듈 선언은 타입스크립트가 방대한 자바스크립트 생태계와 조화롭게 공존할 수 있게 하는 핵심 메커니즘입니다. declare module 'module-name'과 와일드카드 선언(*.ext)을 통해 외부 모듈의 타입을 명확히 정의함으로써, 타입스크립트의 강력한 타입 검사 기능을 자바스크립트 프로젝트 전반에 걸쳐 확장할 수 있습니다.