타입 선언과 관련된 세 가지 버전 이해하기

TS를 사용하게 되면 의존성 관리가 더 복잡해집니다. 왜냐하면 다음 세 가지 사항을 추가로 고려해야 하기 때문입니다.

  • 라이브러리의 버전
  • 타입 선언(@types)의 버전
  • 타입스크립트의 버전

이 셋 중 하나라도 맞지 않으면, 의존성과 상관없어 보이는 곳에서 엉뚱한 오류가 발생할 수 있습니다.

라이브러리와 타입 정보의 버전이 별도로 관리되는 경우

일반적으로는 특정 라이브러리를 dependencies로 설치하고, 타입 정보를 devDependencies로 설치하게 됩니다.

npm install react
+ react16.8.6

npm install --save-dev @types/react
+ @types/react@16.8.19

이 때, 메이저 버전과 마이너 버전이 일치하지만 패치 버전이 일치하지 않는다는 점에 주목합시다. @types/react16.8.19는 타입 선언들이 React 16.8 버전의 API를 나타냄을 의미합니다.

React 모듈이 시맨틱 버전 규칙을 제대로 지킨다고 가정하면 패치 버전들은 공개 API의 사양을 변경하지 않습니다. 여기서 패치(patch) 버전인 .19의 경우는 타입 선언 자체의 버그나 누락에 따른 수정과 추가에 따른 것입니다. 앞서 React와 해당 타입 선언의 버전에 차이가 생긴 것은 라이브러리 자체보다 타입 선언에 더 많은 업데이트가 있었기 떄문입니다. (19 vs. 6)

다만, 별도로 버전을 관리하는 경우 다음의 네 가지 문제점이 있습니다.

  1. 라이브러리를 업데이트했지만 실수로 타입 선언은 업데이트하지 않는 경우
    • 타입 선언도 업데이트하여 버전을 맞추어줘야 합니다.
    • 단, 해당 버전이 준비되지 않은 경우, 임시적으로 보강을 활용할 수 있습니다.
  2. 라이브러리보다 타입 선언의 버전이 최신인 경우
    • 라이브러리를 업데이트하거나, 타입 버전을 낮추어야 합니다.
  3. 프로젝트에서 사용하는 TS 버전보다 라이브러리에서 필요로 하는 TS 버전이 최신인 경우
    • 프로젝트의 타입스크립트의 버전을 업데이트하거나
    • 라이브러리의 타입 선언 버전을 낮추거나
    • declare module 선언으로 라이브러리의 타입 정보를 없애 버릴 수 있습니다.
    • 라이브러리에서 typesVersions를 통해 TS 버전 별로 다른 타입 선언을 제공하는 방법도 있으나, 실제로 이 경우는 매우 드뭅니다.
    • 특정 버전에 대한 타입 정보를 설치하려면 npm install --save-dev @types/lodash@ts.31와 같이 실행하면 됩니다.
  4. 라이브러리 간 @types 의존성이 중복되는 경우
    • npm ls @types/foo와 같은 실행으로 타입 선언 중복이 어디서 발생했는지 추적합니다.
    • 해당 라이브러리들을 업데이트하여 서로 버전이 호환되게끔 합니다.
    • 단, 애초에 @types가 전이(transitive) 의존성을 갖지 않도록 설계되는 것이 좋습니다. 이에 대해서는 아이템 51에서 다룰 예정입니다.

타입 정보가 라이브러리 자체적으로 관리되는 경우

일부 라이브러리, 특히 TS로 작성된 라이브러리들은 자체적으로 타입 선언을 포함(bundling)하게 됩니다. 자체 타입 선언은 보통 package.jsontypes필드에서 .d.ts 파일을 가리키도록 되어 있습니다.

{
  // ...
  "types": "index.d.ts",
  // ...
}

타입 정보를 라이브러리 자체적으로 관리하는 경우 라이브러리와 타입 선언 간의 버전 불일치 문제를 해결하긴 합니다. 그러나 번들링 방식은 부수적인 네 가지 문제점을 갖고 있습니다.

  1. 번들된 타입 선언에 보강 기법으로 해결할 수 없는 오류가 있는 경우, 또는 공개 시점에는 잘 동작했으나 TS 버전이 올라가며 오류가 발생하는 경우
    • 번들된 타입에서는 별도로 @types의 버전 선택이 불가능하다는 문제점이 있습니다.
  2. 프로젝트 내 타입 선언이 다른 라이브러리의 타입 선언에 의존하는 경우
    • 해당 프로젝트를 다른 이용자가 설치하여 사용하게 되는 경우 별도로 devDependencies가 설치되지 않으므로 타입 에러가 발생합니다.
    • 또, JS 사용자 입장에서는 @types를 설치할 이유가 없기 때문에 dependencies에 포함하고 싶지 않을 것입니다.
    • 이러한 상황에 대한 해결책에 대해서는 아이템 51에서 다룰 예정입니다.
  3. 프로젝트의 과거 버전에 있는 타입 선언에 문제가 있는 경우
    • 라이브러리 자체의 과거 버전으로 돌아가서 패치 업데이트를 해야합니다.
  4. 타입 선언의 패치 업데이트를 자주 하기가 어렵습니다.
    • 라이브러리 자체보다 타입 선언에 대한 패치 업데이트가 훨씬 많을 수 있는데, @types의 경우 커뮤니티에서 관리되기 때문에 이러한 작업량이 감당될 수 있으나, 직접 프로젝트를 처리하려면 시간이 많이 소요되는 작업입니다.

어느 쪽을 선택해야 할까요?

공식적인 권장 사항은 라이브러리가 TS로 작성된 경우에만 타입 선언을 라이브러리에 포함하는 것입니다. 실제로 TS 컴파일러가 타입 선언을 대신 생성해 주기 때문에, TS로 작성된 라이브러리에 타입 선언을 포함하는 방식은 잘 동작합니다.

JS로 작성된 라이브러리라면 손수 작성한 타입 선언은 오류가 있을 가능성이 높고, 잦은 업데이트가 필요하게 됩니다. 이 경우에는 타입 선언을 DefinitelyTyped에 공개하여 커뮤니티에서 관리하고 유지보수하도록 맡기는 것이 좋습니다.