Генерация .d.ts файлов

Генерация .d.ts файлов в экосистеме Rollup представляет собой отдельный слой сборочного процесса, который отвечает за формирование TypeScript-типов для потребителей библиотеки. В отличие от основного бандлинга JavaScript-кода, здесь задача заключается не в объединении модулей, а в точной и консистентной передаче контрактов типов, которые используются в исходном коде TypeScript или JSDoc-аннотациях.

Файлы .d.ts являются описанием интерфейсов, функций, классов и модулей без реализации. Они используются TypeScript-компилятором для проверки типов в проектах, которые подключают библиотеку как зависимость.

Ключевая особенность заключается в том, что .d.ts не исполняются в рантайме, но напрямую влияют на качество интеграции библиотеки в IDE и системы статической проверки типов.

Основные цели генерации деклараций:

  • предоставление публичного API библиотеки в виде типов;
  • обеспечение автодополнения в редакторах;
  • предотвращение неправильного использования API;
  • поддержка TypeScript-проектов без необходимости просмотра исходников.

Базовый механизм генерации через TypeScript Compiler

Наиболее прямой способ получения .d.ts файлов — использование TypeScript Compiler (tsc) в режиме деклараций.

В tsconfig.json включается:

{
  "compilerOptions": {
    "declaration": true,
    "emitDeclarationOnly": true,
    "declarationMap": true,
    "outDir": "dist/types"
  }
}

Параметры:

  • declaration: true — включает генерацию .d.ts;
  • emitDeclarationOnly: true — отключает генерацию .js;
  • declarationMap: true — создаёт карты соответствий исходного кода;
  • outDir — отдельная директория для типов.

Этот подход прост, но плохо сочетается с Rollup, поскольку Rollup выполняет собственную сборку модулей, и возникает проблема согласования структуры выходных файлов.

Конфликт Rollup и tsc при генерации типов

Rollup выполняет трансформацию модулей и может:

  • объединять файлы в один бандл;
  • переименовывать и перемещать модули;
  • генерировать несколько entry points;
  • использовать плагины, изменяющие структуру импорта.

TypeScript же генерирует .d.ts исходя из файловой структуры исходного проекта.

Это приводит к рассинхронизации:

  • JavaScript-бандл становится плоским;
  • декларации остаются модульными;
  • пути импортов в .d.ts не совпадают с итоговым пакетом.

Для устранения этой проблемы применяются специализированные плагины Rollup.

@rollup/plugin-typescript

Один из базовых способов интеграции TypeScript в Rollup — использование плагина @rollup/plugin-typescript.

Он позволяет:

  • компилировать TypeScript в процессе сборки Rollup;
  • генерировать .d.ts файлы параллельно с JS;
  • синхронизировать структуру выходных модулей.

Пример конфигурации:

import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.ts',
  output: {
    dir: 'dist',
    format: 'esm'
  },
  plugins: [
    typescript({
      declaration: true,
      declarationDir: 'dist/types'
    })
  ]
};

Особенности:

  • декларации генерируются на уровне плагина;
  • структура типов ближе к итоговой сборке;
  • ограниченная гибкость при сложных конфигурациях.

В крупных библиотеках этот вариант часто заменяется более специализированными решениями.

rollup-plugin-dts и агрегация деклараций

Для сложных проектов используется rollup-plugin-dts, который выполняет отдельную сборку .d.ts файлов.

Его задача — не просто сгенерировать декларации, а объединить их в единый согласованный модульный граф, соответствующий итоговому бандлу JavaScript.

Принцип работы:

  1. TypeScript генерирует .d.ts для каждого модуля;
  2. Rollup обрабатывает декларации как входные файлы;
  3. выполняется бандлинг типов;
  4. формируется единый .d.ts файл или набор согласованных файлов.

Пример конфигурации:

import dts from 'rollup-plugin-dts';

export default {
  input: 'dist/types/index.d.ts',
  output: {
    file: 'dist/index.d.ts',
    format: 'esm'
  },
  plugins: [dts()]
};

Преимущество такого подхода заключается в том, что итоговый .d.ts соответствует структуре JavaScript-бандла.

Разделение сборки JS и типов

В зрелых проектах сборка типов отделяется от сборки JavaScript.

Типичная схема:

  1. Rollup собирает Jav * aScript:

    • src/index.ts → dist/index.js
  2. TypeScript генерирует декларации:

    • src/index.ts → dist/types/*.d.ts
  3. Rollup или отдельный шаг объединяет декларации:

    • dist/types → dist/index.d.ts

Такое разделение обеспечивает:

  • независимость процессов;
  • предсказуемость результатов;
  • возможность оптимизации каждого этапа отдельно.

Работа с multiple entry points

Rollup часто используется для библиотек с несколькими точками входа:

export default {
  input: {
    main: 'src/index.ts',
    utils: 'src/utils.ts'
  },
  output: {
    dir: 'dist',
    format: 'esm'
  }
};

В этом случае генерация .d.ts усложняется, поскольку:

  • каждый entry point имеет собственный граф зависимостей;
  • типы пересекаются между модулями;
  • требуется единая консолидация деклараций.

rollup-plugin-dts в таких случаях строит общий граф типов и объединяет их в согласованную структуру, избегая дублирования экспортов.

Влияние tree-shaking на декларации

Tree-shaking в Rollup удаляет неиспользуемый код на этапе JavaScript-сборки. Однако декларации типов не могут быть оптимизированы столь же агрессивно без потери корректности API.

Типичная проблема:

  • JS-экспорт удалён как неиспользуемый;
  • соответствующий тип остаётся в .d.ts.

Это приводит к рассинхронизации публичного API.

Для решения применяются:

  • явное управление публичными экспортами через index.ts;
  • использование preserveModules;
  • фильтрация экспортов на уровне типов через плагин dts.

preserveModules и влияние на типы

Опция preserveModules сохраняет структуру исходных файлов в выходной сборке:

output: {
  dir: 'dist',
  format: 'esm',
  preserveModules: true
}

В этом режиме:

  • структура JS сохраняется;
  • генерация .d.ts становится тривиальнее;
  • уменьшается необходимость в агрегации типов.

Однако увеличивается количество файлов, что может быть нежелательно для публичных библиотек.

Поддержка declarationMap

declarationMap создаёт сопоставление между .d.ts и исходными .ts файлами:

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true
  }
}

Это важно для:

  • перехода к исходному коду при навигации в IDE;
  • отладки типов;
  • улучшения developer experience.

Rollup напрямую не использует declaration maps, но корректная их генерация важна при публикации пакета.

Типичные проблемы генерации деклараций

В процессе интеграции Rollup и TypeScript часто возникают системные проблемы.

Дублирование экспортов

Причина:

  • несколько entry points экспортируют один и тот же модуль;
  • отсутствует дедупликация в dts-bundler.

Потеря внутренних типов

Причина:

  • неправильная настройка export в TypeScript;
  • использование type-only импортов без поддержки в сборке.

Несоответствие структуры пакета

Причина:

  • JS бандл и .d.ts собираются разными инструментами без общей конфигурации.

Оптимальная архитектура генерации типов

На практике используется трёхэтапная схема:

  1. TypeScript компилирует исходники в .d.ts;
  2. Rollup собирает JavaScript;
  3. отдельный bundler деклараций объединяет .d.ts.

Такой подход обеспечивает:

  • синхронность API;
  • контроль над структурой пакета;
  • масштабируемость для крупных библиотек;
  • предсказуемость при изменении входного графа.

Использование isolated declarations

В некоторых конфигурациях применяется isolatedDeclarations:

{
  "compilerOptions": {
    "isolatedDeclarations": true
  }
}

Это требует, чтобы каждый файл был самодостаточным с точки зрения типов.

В контексте Rollup это полезно для:

  • ускорения сборки;
  • предотвращения скрытых зависимостей;
  • улучшения совместимости с incremental builds.

Однако увеличивает строгость требований к архитектуре кода.

Роль package.json в публикации типов

Для корректной интеграции типов в npm-пакет используется:

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

или:

{
  "typings": "dist/index.d.ts"
}

Это связывает entry point JavaScript с декларациями и обеспечивает автоматическое использование типов при установке пакета.

Итоговая модель взаимодействия Rollup и TypeScript

Генерация .d.ts в Rollup-проектах представляет собой слой синхронизации между двумя системами:

  • Rollup управляет структурой и оптимизацией JavaScript;
  • TypeScript отвечает за семантику типов;
  • dts bundlers обеспечивают согласование итогового API.

Ключевая инженерная задача заключается не в самой генерации деклараций, а в поддержании идентичности публичного интерфейса между JS- и TS-слоем при изменяющейся структуре модулей и применяемых оптимизациях.