Как типизировать данные из сторонних NPM пакетов

Ответ

Для типизации данных из сторонних пакетов без TypeScript-типов можно использовать декларационные файлы (.d.ts).

  1. Если пакет не имеет типов, создай файл types.d.ts в проекте:
declare module 'package-name' {
  export function someFunction(arg: string): number;
  export interface SomeInterface { /* ... */ }
}
  1. Для популярных пакетов часто есть готовые типы в @types/package-name:
npm install --save-dev @types/package-name
  1. Можно расширить типы существующего модуля через declare module:
declare module 'package-name' {
  interface ExistingType {
    newProperty: string;
  }
}
  1. Для импорта JSON:
declare module '*.json' {
  const value: Record<string, unknown>;
  export default value;
}

Ответ 18+ 🔞

Вот, смотри, как это работает на практике, а то некоторые думают, что TypeScript — это такая магия, которая сама всё про твой node_modules узнает. Не, блядь, не узнает.

Если ты взял какой-нибудь левый пакет с npm, который писал какой-то хипстер на коленке, и у него там типов нет — пиши пропало. Твой редактор начнёт орать красным, а компилятор материться, что не знает, что за хуйню ты импортируешь.

Вариант первый, для рукастых. Берёшь и сам описываешь, что этот пакет вообще из себя представляет. Создаёшь где-нибудь в проекте файлик, например, types.d.ts, и пишешь туда вот такую молитву:

// Объявляем модуль 'cool-but-typeless-package'
declare module 'cool-but-typeless-package' {
  // Говорим TypeScript: "Поверь мне, там есть такая функция"
  export function doAwesomeStuff(input: string): Promise<number>;

  // А ещё там есть такой интерфейс, я видел
  export interface AwesomeResult {
    id: number;
    status: 'ok' | 'fail';
    data?: unknown;
  }
}

Всё, теперь ты сам себе хозяин. TypeScript успокоится и будет думать, что так и было. Главное — не наврать в описании, а то в рантайме получишь undefined is not a function, и будешь потом ебаться с отладкой.

Вариант второй, для ленивых (и он часто срабатывает). Идешь и проверяешь, а нет ли для этого пакета готовых типов в репозитории @types. Это как библиотекарь, который уже за тебя всё прочитал и описал.

npm install --save-dev @types/cool-but-typeless-package

Если такой пакет есть — считай, тебе повезло, и можно не париться. Установил и забыл.

Вариант третий, для продвинутых: расширение существующих типов. Бывает, что типы в @types есть, но они кривые или неполные. Или ты сам добавил какую-то хуйню через Object.assign. Тогда можно аккуратно их дополнить, не ломая оригинал.

// В своём .d.ts файле
declare module 'already-typed-package' {
  // Дополняем интерфейс, который уже объявлен в оригинальных типах
  export interface ExistingConfig {
    newOptionalField?: string; // Добавили своё поле
  }
}

Ну и классика — импорт JSON-файлов. TypeScript по умолчанию их нихуя не понимает. Надо ему объяснить.

declare module '*.json' {
  const value: Record<string, unknown>; // Или любой другой более конкретный тип
  export default value;
}

Объяснил — и теперь можешь спокойно делать import data from './config.json' без всяких ошибок.

Короче, суть в чём: TypeScript — не телепат. Если типов нет, их надо либо найти, либо написать. Иначе вся эта затея с типами превращается в ебаное профанацию, и можно было сразу на чистом JS писать.