외부 API를 사용하다보면 타입 선언이 없는 js 기반의 API를 쓸 일이 있다. 자체적으로 타입을 선언해보기도 하고 typescript 커뮤니티에서 제공하는 @types 라이브러리를 사용해보기도 하면서 삽질도 많이 하고 구글링도 많이 한 결과, 타입스크립트의 근본을 더 공부할 필요성을 느껴 책을 뒤져보았다.
이번 포스팅에서는 타입스크립트에서 의존성이 어떻게 동작하는지 알아보고 의존성 관리를 하다가 맞닦뜨릴 수도 있는 문제의 해결방법을 찾아보도록 한다.
devDependencies에 typescript와 @types 추가하기
npm은 3가지 종류의 의존성을 구분해서 관리하며 각각의 의존성은 package.json
파일 내에서 확인할 수 있다.
- dependencies 현재 프로젝트를 실행하는 데 필수적인 라이브러리를 포함한다. 프로젝트를 npm에 공개해 다른 사용자가 해당 프로젝트를 설치한다면 dependencies에 포함된 라이브러리가 설치된다.
- devDependencies 현재 프로젝트를 개발하고 테스트하는 데 사용되지만 런타임에는 필요없는 라이브러리를 포함한다. 프로젝트를 npm에 공개해 다른 사용자가 해당 프로젝트를 설치한다면 devDependencies에 포함된 라이브러리들은 제외된다.
- peerDependencies 런타임에 필요하지만 의존성을 직접 관리하지 않는 라이브러리를 포함한다.
타입정보는 런타임에 존재하지 않기 때문에 타입스크립트와 관련된 라이브러리는 일반적으로 devDependencies
에 속한다.
타입스크립트 프로젝트에서 공통적으로 고려해야 하는 의존성으로 두가지가 있다.
- 타입스크립트 자체 의존성
- 타입 의존성
타입스크립트 자체 의존성
프로젝트 팀원 모두가 항상 동일한 버전의 타입스크립트를 설치하리라는 보장이 없고 프로젝트 셋업시 별도의 단계가 필요해 타입스크립트를 시스템 레벨로 설치하는 것 보다 devDependencies
에 넣는 것이 좋다. 커멘드 라인에서 npx
을 사용해 devDependencies
에 설치된 타입스크립트 컴파일러를 실행할 수 있다.
타입 의존성
사용하려는 라이브버리에 타입 선언이 포함되어 있지 않아도 DefinitelyTyped에서 타입 정보를 얻을 수 있다. DefinitelyTyped의 타입 정의들은 npm 레지스트리의 @types
스코프에 공개된다. @types 라이브러리는 타입 정보만 포함하므로 원본 라이브러리 자체가 dependencies에 있더라도 @types 의존성은 devDependencies에 있어야 한다.
1
2
$ npm install react
$ npm install --save-dev @types/react
리액트의 타입 선언과 리액트를 의존성에 추가하려면 위와 같이 실행해야 한다.
1
2
3
4
5
6
7
8
9
{
"devDependencies": {
"@types/react": "^16.8.19",
"typescript": "^3.5.3"
},
"dependencies": {
"react": "^16.8.6"
}
}
타입스크립트에서 추가로 고려해야 하는 세가지 버전
타입스크립트를 사용하면 아래의 세 가지 사항을 추가로 고려해야 한다.
- 라이브러리의 버전
- 타입 선언(@types)의 버전
- 타입스크립트의 버전
세 가지 버전 중 하나라도 맞지 않으면 의존성과 상관없어 보이는 곳에서 오류가 발생할 수 있다. 이러한 오류의 원인을 파악하기 위해서는 타입스크립트 라이브러리 관리의 메커니즘을 이해해야 한다.
타입스크립트에서 의존성을 사용할 때 일반적으로 특정 라이브러리를 dependencies
로 설치하고 타입 정보는 devDependencies
로 설치한다.
1
2
3
4
5
$ npm install react
+ react@16.8.6
$ npm install --save-dev @types/react
+ @types/react@16.8.19
메이저 버전과 마이너 버전(16.8)이 일치하지만 패치 버전은 일치하지 않는다. @types/react의 16.8.19는 타입 선언들이 리액트 16.8 버전의 API를 나타낸다는 것을 의미한다. @types 모듈의 패치 ㅓ전은 버그나 누락으로 인한 수정과 추가에 따른 것으로 라이브러리 자체보다 타입 선언에 더 많은 업데이트가 있었음을 알 수 있다.
실제 라이브러리와 타입 정보의 버전이 별도로 관리되는 방식은 몇 가지 문제점이 있다.
-
라이브러리를 업데이트 했지만 실수로 타입 선언은 업데이트하지 않는 경우 (라이브러리 > 타입 선언) 라이브러리 업데이트와 관련된 새로운 기능을 사용하려 할 때마다 타입 오류가 발생한다. 일반적으로 타입 선언을 업데이트하여 라이브러리와 버전을 맞춰 해결할 수 있다. 그러나, 업데이트할 타입 선언의 버전이 없는 경우 사용하려는 새 함수와 메서드의 타입 정보를 프로젝트 자체에 추가하거나 타입 선언의 업데이트를 직접 작성하고 공개하여 커뮤니티에 기여할 수 있다.
-
라이브러리보다 타입 선언의 버전이 최신인 경우 (라이브러리 < 타입 선언) 타입 정보없이 라이브러리를 사용하다가 뒤늦게 타입 선언을 설치하려고 할 때 발생한다. 타입 체커는 최신 API를 기준으로 코드를 검사하지만 런타임에 실제로 쓰이는 것은 과거 버전이 된다. 라이브러리와 타입 선언의 버전이 맞도록 라이브러리의 버전을 올리거나 타입 선언의 버전을 내린다.
-
프로젝트에서 사용하는 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신인 경우 현재 프로젝트보다 라이브러리에게 필요한 타입스크립트의 버전이 높으면 @types 선언 자체에서 타입 오류가 발생한다. 프로젝트의 타입스크립트 버전을 올리거나 라이브러리 타입 선언의 버전을 내리거나
declare module
선언으로 라이브러리의 타입 저오를 없앤다.
1
$ npm install --save-dev @types/lodash@ts3.1
이와 같이 타입스크립트의 특정 버전에 대한 타입 정보를 설치할 수도 있다.
- @types 의존성이 중복되는 경우 만약 @types/bar가 현재 프로젝트와 호환되지 않는 버전의 @types/foo에 의존한다면 npm은 중첩된 폴더에 별도로 해당 버전을 설치하여 문제를 해결하려고 한다. 전역 네임스페이스에 타입 선언이 존재하면 중복된 선언, 또는 선언이 병합될 수 없다는 오류가 발생한다.