TypeScript-проекты, использующие Rollup, требуют отдельного этапа
генерации деклараций типов, поскольку сам Rollup не выполняет типизацию
и не умеет создавать .d.ts файлы из исходного кода. Это
приводит к необходимости выстраивания параллельного процесса: сборка
JavaScript-бандла и формирование типовых деклараций должны происходить
синхронно и предсказуемо.
Файлы деклараций типов .d.ts являются контрактом между
библиотекой и потребителем TypeScript. Они описывают структуру API без
реализации, обеспечивая:
В контексте Rollup-сборки основной JavaScript-бандл и типовые декларации существуют как два независимых артефакта. Это разделение требует координации на уровне сборочного пайплайна.
Наиболее прямолинейный способ генерации .d.ts —
использование компилятора TypeScript с включённой опцией emit
declarations.
Ключевая настройка tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"outDir": "dist/types",
"rootDir": "src"
},
"include": ["src"]
}
Здесь важно разделение обязанностей:
tsc отвечает только за типыТакой подход исключает пересечения ответственности и предотвращает дублирование компиляции.
Типичная структура проекта с Rollup и TypeScript выглядит следующим образом:
src/
index.ts
dist/
index.js
types/
index.d.ts
Сборочный процесс разбивается на два независимых шага:
.d.tsТакой подход обеспечивает чистую архитектуру сборки, но требует синхронизации через npm scripts.
{
"scripts": {
"build:js": "rollup -c",
"build:types": "tsc -p tsconfig.json",
"build": "npm run build:js && npm run build:types"
}
}
Основная проблема такого подхода заключается в отсутствии гарантии атомарности результата: JS может собраться, а типы — нет, и наоборот.
Для устранения рассинхронизации между JavaScript и типами
используется специализированный плагин rollup-plugin-dts,
который позволяет рассматривать .d.ts как входной граф и
собирать их аналогично JS-коду.
Принцип работы отличается от tsc:
.d.ts файлыПервый этап остаётся за tsc, но с ограничением
вывода:
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist/dts-temp",
"rootDir": "src"
}
}
import dts from "rollup-plugin-dts";
export default {
input: "dist/dts-temp/index.d.ts",
output: {
file: "dist/index.d.ts",
format: "es"
},
plugins: [dts()]
};
На выходе получается единый .d.ts файл, повторяющий
структуру бандла.
Главная инженерная проблема — поддержание идентичной структуры JavaScript и типов. Rollup может:
TypeScript при этом генерирует декларации на основе исходной структуры, не учитывая трансформации Rollup.
Это приводит к необходимости соблюдать правило:
структура входных
.tsмодулей должна соответствовать публичному API, а не внутреннему устройству сборки
Именно поэтому часто вводится отдельный entry-point:
src/
index.ts // публичный API
internal/ // не экспортируется
При использовании barrel-файлов (index.ts с re-export)
возникает расхождение между:
Rollup может схлопывать re-export, тогда как TypeScript сохраняет оригинальные пути.
Это особенно критично при генерации .d.ts, так как
итоговый файл может содержать:
Решение — использовать rollup-plugin-dts как финальный
шаг нормализации графа.
При включении output.preserveModules структура JS
сохраняется 1:1 с исходниками. Это значительно упрощает согласование с
типами.
export default {
input: "src/index.ts",
output: {
dir: "dist",
format: "esm",
preserveModules: true
}
};
Преимущество в том, что:
.d.ts можно сопоставлять построчно с JS-модулямиОднако это увеличивает количество файлов в dist, что
может быть нежелательно для библиотек с публичным API, ориентированным
на единый бандл.
В монорепозиториях генерация типов усложняется из-за:
Типичная стратегия:
.d.tsRollup в таких сценариях используется только на уровне отдельных пакетов, тогда как типы часто собираются отдельным инструментом (tsc или dts bundler).
При больших кодовых базах полная пересборка .d.ts
становится узким местом. Используются подходы:
tsc --build (project references).d.tsProject References позволяют TypeScript строить граф зависимостей и пересобирать только изменённые части:
tsconfig.json (root)
packages/
core/
ui/
Каждый пакет имеет собственный tsconfig.json с
composite: true.
При использовании дополнительных Rollup-плагинов (например, Babel или SWC) возникает риск рассинхронизации типов и фактического кода:
TypeScript при этом остаётся единственным источником истины для
.d.ts, что требует строгого контроля соответствия входного
кода и выходного бандла.
На практике формируется двойной выход:
dist/index.js — результат Rollupdist/index.d.ts — результат tsc + dts bundlerКлючевое требование:
оба артефакта должны соответствовать одному и тому же публичному API-графу
Любое отклонение приводит к нарушению типовой совместимости и ошибкам в IDE у потребителей библиотеки.
Некоторые библиотеки отказываются от объединения .d.ts в
один файл и оставляют структуру как есть:
dist/
index.d.ts
utils.d.ts
helpers.d.ts
Это упрощает сборку и устраняет шаг bundling деклараций, но
увеличивает сложность публикации API и требует корректного
exports в package.json.
При использовании exports необходимо явно указать:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
}
Финальная интеграция Rollup и TypeScript происходит на уровне метаданных пакета. Важные поля:
typesexportsmainmoduleКорректная конфигурация гарантирует, что:
.d.tsРазделение сборки и типизации становится не вспомогательной задачей, а частью архитектуры библиотеки, определяющей её совместимость и долгосрочную поддерживаемость.