본문 바로가기

DefinitelyTyped와 @types

DefinitelyTyped에서 제공하는 @types 패키지를 설치·선택하고 자체 타입 내장 여부와 types·typeRoots 설정을 구분합니다.

타입스크립트로 자바스크립트 라이브러리를 개발하거나, 기존 라이브러리를 타입스크립트 프로젝트에서 사용할 때 핵심은 타입 정의 파일(.d.ts)입니다.

문제는 라이브러리 수가 너무 많아 모든 .d.ts를 개발자가 직접 작성하는 것이 비효율적이고 사실상 불가능에 가깝다는 점입니다.

이러한 문제를 해결하기 위해 등장한 것이 바로 DefinitelyTyped 프로젝트와 npm의 @types 스코프 패키지입니다.


DefinitelyTyped 프로젝트란?

DefinitelyTyped는 수많은 자바스크립트 라이브러리에 대한 고품질의 타입 정의 파일을 제공하는 오픈 소스 프로젝트입니다.

전 세계의 개발자들이 자발적으로 참여하여 각 라이브러리의 .d.ts 파일을 작성하고 유지보수하며, 이를 통해 타입스크립트 사용자들이 자바스크립트 생태계를 타입 안전하게 활용할 수 있도록 돕습니다.

DefinitelyTyped는 GitHub 저장소(https://github.com/DefinitelyTyped/DefinitelyTyped)에서 관리되며, 각 라이브러리별 타입 정의 파일이 별도 디렉터리에 존재합니다.

예를 들어 Lodash 라이브러리 타입 정의는 DefinitelyTyped/types/lodash 경로에 있습니다.


@types 스코프 패키지

DefinitelyTyped에 기여된 모든 타입 정의 파일은 자동으로 npm의 @types 스코프로 패키징되어 배포됩니다.

즉, lodash 라이브러리에 대한 타입 정의는 npm install @types/lodash 명령어로 설치할 수 있게 됩니다.

이 @types 패키지들은 실제 자바스크립트 코드를 포함하지 않고, 오직 해당 라이브러리의 타입 정의(.d.ts 파일)만을 포함합니다.

타입스크립트 컴파일러는 명시적으로 import한 모듈의 선언을 해석할 때 설치된 @types 패키지를 사용할 수 있습니다. 전역 선언의 자동 포함은 별도 설정이며, TypeScript 6.0 이상에서는 types의 기본값이 []이므로 필요한 전역 패키지를 명시합니다.

사용 예시

lodash 라이브러리를 타입스크립트 프로젝트에서 사용하려고 할 때:

lodash 라이브러리 설치
npm install lodash
lodash에 대한 타입 정의 설치
npm install --save-dev @types/lodash

--save-dev는 개발 의존성으로 기록합니다. 실제 설치 여부는 배포·설치 옵션에 달려 있습니다. 애플리케이션의 빌드용 타입은 개발 의존성으로 둘 수 있지만, 배포할 .d.ts가 다른 타입 패키지를 참조하면 소비자가 받도록 의존성 배치를 검토해야 합니다.

이제 타입스크립트 코드에서 lodash를 가져와 사용하면, 타입스크립트 컴파일러는 node_modules/@types/lodash 경로의 .d.ts 파일을 참조하여 _ 객체의 모든 메서드와 속성에 대한 정확한 타입 정보를 제공합니다.

import _ from 'lodash'; // 'lodash' 모듈을 가져옴 (CommonJS 모듈이므로 esModuleInterop: true 권장)

const numbers = [1, 2, 3, 4, 5];
const sum = _.sum(numbers); // sum은 number 타입으로 추론됩니다. (정확한 매개변수 힌트 제공)
console.log(sum); // 15

const shuffled = _.shuffle(numbers); // shuffled는 number[] 타입으로 추론됩니다.
console.log(shuffled);

// _.sortBy(numbers, 'invalidKey'); // string shorthand가 허용되므로 이 호출을 타입 오류 예시로 볼 수 없습니다.
// 타입 선언이 허용하는 shorthand까지 실제 속성 존재를 증명하는 것은 아닙니다.

@types 패키지의 장점

  • 자동 완성 및 타입 검사: IDE에서 라이브러리 사용 시 정확한 자동 완성(_.)과 함수 시그니처 힌트를 제공하여 개발 생산성을 크게 높입니다. 잘못된 인자 전달이나 존재하지 않는 메서드 호출 등의 오류를 컴파일 시점에 즉시 잡아줍니다.
  • 쉬운 통합: npm install 한 줄로 수많은 자바스크립트 라이브러리를 타입스크립트 프로젝트에 타입 안전하게 통합할 수 있습니다.
  • 커뮤니티 주도: 전 세계 개발자들의 기여로 최신 라이브러리 버전에도 빠르게 대응하고, 버그 수정 및 개선이 활발하게 이루어집니다.
  • 명확한 버전 관리: @types 패키지는 해당 라이브러리의 특정 버전(또는 범위)에 맞는 타입 정의를 제공하므로, 라이브러리 버전과 타입 정의 버전 간의 호환성을 관리하기 용이합니다.

@types 패키지가 없을 때

만약 특정 자바스크립트 라이브러리에 대한 @types 패키지가 존재하지 않는다면, 다음 두 가지 방법 중 하나를 선택할 수 있습니다.

직접 .d.ts 파일 작성: 해당 라이브러리의 외부 API를 파악하여 직접 앰비언트 모듈 선언(.d.ts 파일)을 작성합니다 (8장 3절 참조).

이 방법은 라이브러리의 규모가 작거나, 사용하려는 부분이 제한적일 때 유용합니다.

my-lib-declaration.d.ts
declare module 'my-custom-js-lib' {
  export function doSomething(param: string): number;
}
app.ts
import { doSomething } from 'my-custom-js-lib';
doSomething('hello');

any 타입으로 사용 (최후의 수단): 정의된 타입이 없어 타입 안정성을 포기하고 any 타입으로 라이브러리를 사용하는 방법입니다.

app.ts
import SomeUntypedLib from 'some-untyped-lib'; // 선언 누락을 허용하는 설정에서는 any이며, strict에서는 오류가 날 수 있음
const lib = new SomeUntypedLib();
lib.doSomethingElse(123); // 타입 검사 없이 허용

이 방법은 타입스크립트 사용의 이점을 상실하므로 가능한 한 피해야 합니다.

아래 다이어그램은 새 라이브러리를 도입할 때 타입 정의를 찾는 우선순위와, 없을 때 선택지를 비교합니다.

자체 타입, 호환되는 @types, 좁은 직접 선언과 any의 차이 및 소비자 의존성을 설명합니다.

TYPE / RUNTIME / CONTRACT

자체 선언부터 확인하고 없는 타입만 보완한다

새 라이브러리는 현재 패키지의 공개 경로와 타입 내장을 확인합니다. 없을 때 호환되는 @types를 찾고, 필요한 API를 직접 선언하거나 타입 검사를 포기하는 선택의 차이를 읽습니다.

자체 선언부터 확인하고 없는 타입만 보완한다예는 오른쪽의 선언을 사용하고, 아니요는 다음 선택으로 진행합니다. 호환 타입을 직접 기술하는 선택과 any로 검사를 포기하는 선택은 같은 안전성 수준이 아닙니다. 어느 선언도 구현을 설치하지 않습니다.자체 타입 있음?현재 패키지 확인내장 선언 사용공개 경로 확인호환 @types?구현 버전 대조호환 선언 사용구현도 별도 필요필요 API 직접 선언any는 검사 포기예아니요예아니요
자체 선언부터 확인하고 없는 타입만 보완한다예는 오른쪽의 선언을 사용하고, 아니요는 다음 선택으로 진행합니다. 호환 타입을 직접 기술하는 선택과 any로 검사를 포기하는 선택은 같은 안전성 수준이 아닙니다. 어느 선언도 구현을 설치하지 않습니다.예아니요예아니요자체 타입 있음?현재 패키지 확인내장 선언 사용공개 경로 확인호환 @types?구현 버전 대조호환 선언 사용구현도 별도 필요필요 API 직접 선언any는 검사 포기

예는 오른쪽의 선언을 사용하고, 아니요는 다음 선택으로 진행합니다. 호환 타입을 직접 기술하는 선택과 any로 검사를 포기하는 선택은 같은 안전성 수준이 아닙니다. 어느 선언도 구현을 설치하지 않습니다.

1 · BUNDLED

자체 선언이 있으면 그것을 사용한다

my-typescript-lib는 main:dist/index.js와 types:dist/index.d.ts를 제공합니다. exports의 타입 조건도 확인하며 중복되거나 불필요해진 @types 설치를 피합니다.

2 · @TYPES

구현 버전과 호환되는 선언을 찾는다

본문은 lodash 구현과 @types/lodash를 설치합니다. _.sum([1,2,3,4,5])은15이며 shuffle 결과의 타입은 number[]입니다. 선언은 실제 lodash 구현을 설치하지 않습니다.

3 · LOCAL

사용할 API를 좁게 직접 설명한다

my-custom-js-lib의 doSomething(param:string):number를 선언합니다. 이 선언으로 문자열 호출을 검사하지만 실제 함수 구현과 반환값은 별도로 확인해야 합니다.

LAST OPTION

선언 누락과 any를 구분한다

some-untyped-lib의 선언이 없으면 strict 설정에서 오류가 날 수 있습니다. 누락을 허용해 any로 사용하면 new와 메서드 호출의 타입 검사를 포기합니다.

CONSUMER

타입이 허용하는 범위를 읽는다

타입 선언이 허용하는 string shorthand까지 실제 속성 존재를 증명하지는 않습니다. default/named export와 선택한 모듈 실행 방식도 맞춰서 사용합니다.

DEPENDENCY

배포되는 선언의 의존성도 전달한다

앱 빌드용 @types는 devDependencies로 둘 수 있습니다. 배포할 .d.ts가 다른 타입 패키지를 참조하면 소비자가 받도록 dependencies 등 의존성 배치를 검토합니다.

@types는 DefinitelyTyped에서 배포하는 선언 패키지입니다. 타입 패키지 선택과 실제 JavaScript의 설치·실행은 각각 확인합니다.


라이브러리가 자체적으로 타입 정의를 제공하는 경우

일부 최신 자바스크립트 라이브러리(특히 타입스크립트로 작성된 라이브러리)는 package.json 파일의 types (또는 typings) 필드를 통해 자체적으로 타입 정의 파일을 포함하여 배포합니다.

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

기존 package.json에 적용할 때는 main과 types 키를 병합하고 다른 스크립트와 메타데이터는 유지합니다.

이러한 라이브러리들은 @types 패키지를 별도로 설치할 필요가 없습니다.

npm으로 라이브러리를 설치하면 타입스크립트 컴파일러가 package.json의 types 필드를 참조하여 자동으로 타입 정의를 찾습니다.

정리
  • 라이브러리가 자체 선언을 제공함: types·exports의 타입 조건 등을 확인하고 중복 @types 설치를 피합니다.
  • 자체 선언이 없음: 구현 버전과 호환되는 @types/<라이브러리명>을 확인합니다.
  • @types에도 없음: 직접 .d.ts 파일 작성 또는 any 사용.

tsconfig.json의 types 및 typeRoots 옵션

tsconfig.json 파일의 compilerOptions에서 types와 typeRoots 옵션을 사용하여 타입 정의 파일의 검색 방식을 더 세밀하게 제어할 수 있습니다.

  • typeRoots: 자동 전역 포함에 사용할 타입 패키지 루트를 지정한 목록으로 제한합니다. 기본 루트에 단순히 더하는 옵션이 아니므로, 아래 예제는 기본 @types 경로도 명시합니다. 커스텀 루트에는 패키지별 선언 디렉터리를 둡니다.
    "compilerOptions": {
      "typeRoots": ["./node_modules/@types", "./custom-types"] // custom-types 디렉토리도 검색
    }
  • types: 컴파일러가 전역 타입 선언으로 포함할 @types 패키지 목록을 지정합니다. TypeScript 6.0 이상에서 이 옵션의 기본값은 []입니다. 이전 버전은 보이는 타입 패키지를 기본적으로 포함했지만, 명시한 types 목록은 전역에 포함할 패키지를 제한합니다.
    "compilerOptions": {
      "types": ["node", "jest"] // 오직 @types/node와 @types/jest만 포함하고 싶을 때
    }
    버전별 기본값과 명시적 import에 미치는 영향은 공식 types 문서를 함께 확인합니다. 이는 자동 전역 타입의 충돌을 줄이는 설정입니다. JavaScript 번들 크기를 직접 줄이지 않으며 명시적으로 import한 모듈의 타입 해석도 막지 않습니다.

아래 다이어그램은 types와 typeRoots가 전역 타입 선언의 포함 범위를 어떻게 좁히는지 정리합니다.

모듈 타입과 전역 타입을 독립적으로 판단하기

구성을 읽을 때는 먼저 이름을 어떻게 사용했는지 봅니다. import _ from 'lodash'처럼 가져온 값은 모듈 해석 경로로 타입을 찾습니다. process·expect를 가져오기 없이 쓰면 프로그램의 전역 타입 환경에 그 이름이 있어야 합니다. types: []는 자동으로 포함할 타입 패키지 목록을 비우지만 명시적 모듈 import나 files로 직접 포함한 선언을 전부 제거하는 스위치는 아닙니다.

다음 경우를 각각 판단해 보세요. 호환되는 lodash와 @types/lodash는 이미 설치돼 있고, 프로젝트는 types: []입니다. process에 대한 다른 직접 선언은 없다고 가정합니다.

  1. import _ from 'lodash' 뒤 _.sum([2, 4, 6])의 타입과 결과는 무엇일까요?
  2. process.version이 자동으로 알려질까요? @types/node만 설치돼 있고 아직 전역 목록에는 넣지 않았습니다.
  3. files로 포함한 custom-globals.d.ts에 declare const COURSE_NAME: string;이 있으면 이 이름의 정적 타입까지 사라질까요? 이 선언만으로 실행 값도 생길까요?
  4. @types/lodash만 남기고 실제 lodash 구현을 설치하지 않은 새 실행 환경에서 타입 검사 성공만으로 프로그램이 실행될까요?
두 타입 경로와 실제 구현 확인

합은 12이며 반환 타입은 number입니다. 명시적 import는 types의 자동 전역 목록과 별도로 선언을 해석합니다. 반면 가정한 process의 전역 선언은 자동 포함되지 않아 사용할 수 없으므로 필요한 전역 패키지를 명시합니다. 직접 포함한 COURSE_NAME의 정적 선언은 유지되지만 실제 값은 별도 구현이 제공해야 합니다. 타입 패키지는 JavaScript 구현을 대신 설치하지 않으므로 실행 환경에는 lodash도 필요합니다.

버전 기본값은 앞의 공식 링크와 설치한 tsc --version을 함께 확인합니다. 이 문제는 표시한 설치·포함 가정 안에서 두 경로를 구분하는 연습이며 실제 패키지를 새로 설치하는 명령을 요구하지 않습니다.

명시적 import와 types/typeRoots의 자동 전역 포함이 타입 환경을 구성하는 두 경로임을 설명합니다.

TYPE / RUNTIME / CONTRACT

명시적 import와 자동 전역 포함은 별도로 설정한다

types 목록은 자동으로 전역에 포함할 타입 패키지를 선택합니다. 목록에 없는 패키지를 명시적으로 import했을 때의 모듈 타입 해석까지 차단하지는 않습니다.

명시적 import와 자동 전역 포함은 별도로 설정한다명시적 import와 types/typeRoots의 자동 전역 포함이 타입 환경을 구성하는 두 경로임을 설명합니다.명시적 import예: lodash자동 전역 포함예: node · jest모듈 선언 해석package / localtypes · typeRoots전역 패키지 선택프로그램 타입 환경
명시적 import와 자동 전역 포함은 별도로 설정한다명시적 import와 자동 전역 포함은 독립적인 두 입력 경로이고 마지막 타입 환경에 합쳐집니다. 세로로 재배치해도 첫 경로의 출력이 두 번째 경로의 입력이 되는 간선은 없습니다. 직접 files/include로 포함한 선언은 별도로 유지합니다.명시적 import예: lodash모듈 선언 해석package / local자동 전역 포함예: node · jesttypes · typeRoots전역 패키지 선택프로그램타입 환경

명시적 import와 자동 전역 포함은 독립적인 두 입력 경로이고 마지막 타입 환경에 합쳐집니다. 세로로 재배치해도 첫 경로의 출력이 두 번째 경로의 입력이 되는 간선은 없습니다. 직접 files/include로 포함한 선언은 별도로 유지합니다.

IMPORT

목록 밖의 모듈도 타입을 해석한다

types:[]이어도 import한 lodash 등의 선언은 설정에 따라 해석합니다. 전역 목록에서 빠졌다는 이유만으로 모듈 API가 any가 되지는 않습니다.

TYPES

자동 전역 패키지를 고른다

본문 types:["node","jest"]는 해당 전역 타입을 포함합니다. process·expect 같은 전역 이름과 모듈 import를 구분합니다. types:[]는 자동 전역 패키지 포함을 비웁니다.

TYPE ROOTS

루트를 추가가 아닌 목록으로 지정한다

본문은 ./node_modules/@types와 ./custom-types를 함께 지정합니다. 기본 루트에 자동으로 더하는 옵션이 아니며 커스텀 루트는 패키지별 선언 디렉터리로 구성합니다.

VERSION / LOCAL

버전 기본값과 직접 포함을 구분한다

TypeScript6.0 이상은 types의 기본값이[]입니다. 이전 기본값과 구분해 필요한 전역 패키지를 명시합니다. files/include로 직접 포함한 global.d.ts를 types:[]가 제거하는 것은 아닙니다.

types와 typeRoots는 JavaScript 번들 크기를 직접 줄이는 설정이 아닙니다. 모듈 해석 실패, 전역 이름 누락, 실제 실행 경로 오류를 각각 확인합니다.

@types를 설치할지, 자체 타입을 믿을지, 직접 선언을 둘지 결정할 때는 패키지의 현재 배포 형태와 프로젝트의 전역 타입 범위를 같이 확인해야 합니다.


DefinitelyTyped와 @types는 타입스크립트 생태계의 핵심이며, 자바스크립트와 타입스크립트 간 호환성을 보장하는 중요한 메커니즘입니다.

이를 통해 수많은 기존 자바스크립트 라이브러리를 타입 안전하게 활용할 수 있어, 타입스크립트 도입 장벽을 낮추고 개발 생산성을 높일 수 있습니다.

새 라이브러리는 자체 선언과 export 형식을 먼저 확인하고, 없을 때 호환되는 @types를 찾습니다.