Skip to main content

Guide

dts-bundle-generator vs rollup-plugin-dts vs tsup 2026

Compare dts-bundle-generator, rollup-plugin-dts, and tsup's built-in dts for bundling TypeScript declaration files. .d.ts output, type-only exports, package.

·PkgPulse Team·
0

TL;DR

dts-bundle-generator is a standalone CLI tool that generates a single .d.ts bundle from your TypeScript library — no Rollup required, fast, handles re-exports and barrel files. rollup-plugin-dts is a Rollup plugin that bundles .d.ts files — integrates into your existing Rollup/Vite build pipeline, processes tsc output into a single declaration file. tsup dts is tsup's built-in declaration generation — tsup --dts generates declarations alongside your JavaScript bundle automatically. In 2026: tsup --dts for the simplest workflow, dts-bundle-generator for standalone CLI usage, rollup-plugin-dts for complex Rollup pipelines.

Key Takeaways

  • dts-bundle-generator: ~500K weekly downloads — standalone CLI, no Rollup dependency
  • rollup-plugin-dts: ~1M weekly downloads — Rollup plugin, processes tsc output
  • tsup --dts: built into tsup (~5M weekly downloads) — zero config, esbuild + tsc
  • The problem: tsc generates one .d.ts per source file — consumers want ONE file
  • A single .d.ts bundle: smaller package, cleaner API surface, faster IDE autocomplete
  • All three produce the same result — a single declaration file for your package entry

The Problem

tsc output (individual files):
  dist/
    index.d.ts           → re-exports from multiple files
    utils/helpers.d.ts   → internal types leaked
    types/index.d.ts     → intermediate types
    core/parser.d.ts     → implementation detail types

What npm consumers want:
  dist/
    index.d.ts           → ONE file with all public types
    index.js             → bundled JavaScript

Benefits of bundled .d.ts:
  ✅ Smaller npm package (fewer files)
  ✅ Faster IDE autocomplete (one file to parse)
  ✅ Cleaner public API (internal types not exposed)
  ✅ No deep import accidents (users can't import internal types)

tsup --dts (Simplest)

tsup — built-in dts generation:

Zero config

# Generate JS bundle + .d.ts in one command:
tsup src/index.ts --dts --format esm,cjs

# Output:
# dist/index.js     (ESM)
# dist/index.cjs    (CJS)
# dist/index.d.ts   (declarations)
# dist/index.d.cts  (CJS declarations)

tsup.config.ts

import { defineConfig } from "tsup"

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  dts: true,            // Generate .d.ts
  clean: true,
  sourcemap: true,
  splitting: false,
  minify: false,        // Don't minify library code
})

package.json exports

{
  "name": "my-library",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup"
  }
}

DTS-only mode

// tsup.config.ts — separate dts build for complex cases:
export default defineConfig([
  {
    entry: ["src/index.ts"],
    format: ["esm", "cjs"],
    // JS only:
  },
  {
    entry: ["src/index.ts"],
    dts: { only: true },  // DTS only — no JS output
    // Use when dts generation needs different config
  },
])

How tsup --dts works

1. tsup uses esbuild to bundle JavaScript (fast)
2. tsup runs tsc to generate individual .d.ts files
3. tsup uses rollup-plugin-dts internally to bundle into one .d.ts
4. Both outputs land in dist/

Note: tsup --dts is slower than JS-only builds because tsc runs
For faster dev builds: tsup (no --dts) in dev, tsup --dts for release

dts-bundle-generator

dts-bundle-generator — standalone CLI:

CLI usage

npx dts-bundle-generator -o dist/index.d.ts src/index.ts

# Options:
npx dts-bundle-generator \
  --project tsconfig.json \
  --out-file dist/index.d.ts \
  --no-check \
  src/index.ts

Programmatic API

import { generateDtsBundle } from "dts-bundle-generator"

const result = generateDtsBundle([
  {
    filePath: "./src/index.ts",
    output: {
      noBanner: true,
      exportReferencedTypes: false,  // Don't export internal types
    },
  },
])

// result[0] is the bundled .d.ts content as a string

Build script

{
  "scripts": {
    "build:js": "esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm",
    "build:dts": "dts-bundle-generator -o dist/index.d.ts src/index.ts",
    "build": "npm run build:js && npm run build:dts"
  }
}

Advantages

dts-bundle-generator vs tsc + rollup-plugin-dts:
  ✅ Single step — no intermediate .d.ts files
  ✅ No Rollup dependency
  ✅ Faster for simple libraries
  ✅ Standalone CLI — works with any bundler

Limitations:
  ❌ Slower than rollup-plugin-dts for complex projects
  ❌ Less ecosystem integration (not a Rollup/Vite plugin)
  ❌ Some edge cases with complex re-exports

rollup-plugin-dts

rollup-plugin-dts — Rollup integration:

Setup

npm install -D rollup-plugin-dts typescript

rollup.config.ts

import { defineConfig } from "rollup"
import dts from "rollup-plugin-dts"

export default defineConfig([
  // JavaScript bundle:
  {
    input: "src/index.ts",
    output: [
      { file: "dist/index.js", format: "esm" },
      { file: "dist/index.cjs", format: "cjs" },
    ],
    plugins: [/* esbuild, typescript, etc. */],
  },
  // Declaration bundle:
  {
    input: "dist/types/index.d.ts",  // tsc output
    output: { file: "dist/index.d.ts", format: "es" },
    plugins: [dts()],
  },
])

Two-step workflow

{
  "scripts": {
    "prebuild": "tsc --emitDeclarationOnly --outDir dist/types",
    "build": "rollup -c",
    "postbuild": "rm -rf dist/types"
  }
}
Step 1: tsc → generates dist/types/*.d.ts (individual files)
Step 2: rollup + rollup-plugin-dts → bundles into dist/index.d.ts
Step 3: cleanup → remove intermediate dist/types/

With Vite

// vite.config.ts — using rollup-plugin-dts in Vite library mode:
import { defineConfig } from "vite"
import dts from "rollup-plugin-dts"

export default defineConfig({
  build: {
    lib: {
      entry: "src/index.ts",
      formats: ["es", "cjs"],
    },
    rollupOptions: {
      plugins: [dts()],
    },
  },
})

Feature Comparison

Featuretsup --dtsdts-bundle-generatorrollup-plugin-dts
Bundlertsup (esbuild)StandaloneRollup/Vite
JS + DTS in one step❌ (DTS only)❌ (DTS only)
Zero configCLI argsRollup config
Requires tsc firstNo (runs internally)NoYes
Complex re-exports⚠️
SpeedMediumFast (simple)Fast
ESM + CJS .d.ts✅ (.d.ts + .d.cts)❌ (single)❌ (single)
Weekly downloads~5M (tsup)~500K~1M

When to Use Each

Use tsup --dts if:

  • Want the simplest possible library build setup
  • One command: JS + declarations + source maps
  • ESM + CJS dual output with matching declaration files
  • Don't want to manage Rollup config

Use dts-bundle-generator if:

  • Using esbuild, swc, or a non-Rollup bundler for JavaScript
  • Need standalone CLI without Rollup dependency
  • Simple library with straightforward exports
  • CI scripts where you want explicit control

Use rollup-plugin-dts if:

  • Already have a Rollup/Vite build pipeline
  • Complex library with multiple entry points
  • Need fine-grained control over declaration bundling
  • Integrating into an existing rollup.config.ts

Declaration Maps and Source Navigation for Library Consumers

A feature that significantly improves the consumer experience of published TypeScript libraries is declaration maps — .d.ts.map files that map from the bundled declaration file back to original TypeScript source files. When a consumer of your library command-clicks a function in VS Code, declaration maps allow the IDE to navigate to the actual TypeScript source file rather than stopping at the bundled .d.ts. This requires distributing your source files alongside the declaration bundle, which is feasible when your source is published on npm (either because the package includes src/ or because you publish source maps pointing to a public repository).

tsup supports declaration maps via dts: { resolve: true } in the config, and the sourcemap: true option generates source maps for the JavaScript bundle simultaneously. The combination of declaration maps and source maps gives consumers both type-safe navigation and meaningful stack traces that point into your original TypeScript source. This is particularly valuable for libraries with complex generic types where consumers frequently need to inspect the source to understand type constraints.

rollup-plugin-dts generates declaration maps when sourcemap: true is passed to the Rollup output config for the declarations build configuration. dts-bundle-generator also supports declaration map generation via the --out-file-declaration-map flag. For all three tools, enabling declaration maps adds minimal build time but meaningfully improves the debugging experience for library consumers working with complex types.

Managing Public API Surface with TypeScript Declaration Files

The .d.ts bundle is your library's public contract with TypeScript consumers. Beyond generating the file, the content of your declaration file determines what types consumers have access to and shapes their experience with your library in IDEs. Several TypeScript configuration decisions affect what ends up in the bundled declaration file.

The stripInternal compiler option in tsconfig.json removes any declaration annotated with /** @internal */ from the emitted .d.ts files. Combined with API Extractor's @internal tag (which serves the same purpose for publishing workflows), this gives you a way to expose types within your package's own source for cross-module use while keeping them out of the public API. This is the recommended pattern for large libraries with multiple entry points that need to share types internally without making those types part of the public contract.

Conditional exports in package.json interact with declaration files in ways that require careful configuration. A library with "exports": { ".": "./dist/index.js", "./utils": "./dist/utils.js" } needs matching declaration files: "./utils": { "types": "./dist/utils.d.ts" }. tsup handles dual-entry builds via the entry array (entry: ["src/index.ts", "src/utils.ts"]) and generates matching declaration files automatically. dts-bundle-generator processes one entry point at a time, so multi-entry libraries require running it once per entry and combining the scripts. rollup-plugin-dts supports multiple entry points through Rollup's multi-input configuration.

Community Adoption in 2026

Weekly npm downloads (early 2026 estimates):

PackageWeekly DownloadsGitHub StarsMaintained By
tsup~5M9,000+egoist
rollup-plugin-dts~1M1,100+Community
dts-bundle-generator~500K1,700+nicolo-ribaudo

tsup's download numbers are high because it is a complete build solution — many projects that just want tsup --dts do not care about the underlying dts mechanism. The choice is often tsup vs "roll your own Rollup config" rather than tsup vs dts-bundle-generator.

rollup-plugin-dts was the default dts bundling approach before tsup made it the default. Many projects still use it explicitly in their rollup.config.ts because they were set up before tsup popularized the all-in-one approach. It has very good stability — the API hasn't changed meaningfully in years.

dts-bundle-generator has fewer downloads but higher stars relative to its download count — it is a niche tool that people who know about it tend to love. The approach of "one CLI command, one output file" appeals to developers who want minimal tooling and explicit control.

Build Speed Comparison

DTS generation is the slowest part of TypeScript library builds because it requires the TypeScript compiler to run full type checking:

ToolBuild time (small library, ~20 files)Notes
tsup --dts8-15 secondsesbuild for JS, tsc + rollup-plugin-dts for DTS
dts-bundle-generator6-12 secondsUses TypeScript compiler API directly
rollup-plugin-dts5-10 secondsProcesses tsc output (tsc runs separately)
tsup (no --dts)0.5-2 secondsesbuild only — very fast

The times are similar because all three eventually invoke the TypeScript compiler. tsup without --dts is dramatically faster — this is worth knowing for development builds where you only need --dts on release builds.

For CI where build time matters, a common pattern is:

{
  "scripts": {
    "dev": "tsup --watch",
    "build": "tsup --dts",
    "build:js-only": "tsup"
  }
}

Use build:js-only in CI if TypeScript types are pre-validated by a separate tsc --noEmit step.

Real-World Adoption: Who Uses What

tsup --dts is the standard choice for new TypeScript libraries in 2026. The UnJS ecosystem (Nitro, h3, ofetch, defu) uses tsup or unbuild (which uses rollup-plugin-dts internally). Vitest, Radix UI, and most modern TypeScript packages on npm use tsup or a similar zero-config build tool with built-in dts generation.

rollup-plugin-dts is used by projects that predate tsup's dominance or that have complex multi-entry builds that need fine-grained control. Vue's own packages use Rollup directly with rollup-plugin-dts. Projects like vite-plugin-* that ship alongside Vite use rollup-plugin-dts in their build pipelines.

dts-bundle-generator is the choice when you want the absolute minimum toolchain. If your JavaScript is bundled by esbuild directly (without Rollup), dts-bundle-generator lets you add declaration generation without pulling in the entire Rollup ecosystem. It is popular in CLI tools and packages with simple public APIs.

Migration Guide

Adding dts to an existing tsup build:

// tsup.config.ts — before (no types):
import { defineConfig } from "tsup"
export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
})

// After (add dts: true):
import { defineConfig } from "tsup"
export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  dts: true,  // That's it
})

Migrating from rollup-plugin-dts to tsup --dts:

# Remove rollup dependencies:
npm uninstall rollup rollup-plugin-dts

# Install tsup:
npm install -D tsup

# Update package.json scripts:
# Before: "build": "tsc --emitDeclarationOnly && rollup -c rollup.dts.config.ts"
# After:  "build": "tsup --dts"

From dts-bundle-generator to tsup --dts:

# Before:
# package.json: "build:dts": "dts-bundle-generator -o dist/index.d.ts src/index.ts"

# After:
# package.json: "build": "tsup --dts"
# tsup handles both JS and dts in one command

The migration from any standalone dts tool to tsup is straightforward — tsup's dts: true config option replaces whatever dts pipeline you had with a single setting.

Common Pitfalls and Troubleshooting

"Could not find a declaration file for module"

This error means consumers of your library cannot find your type declarations. The most common cause is a misconfigured types field in package.json or a missing --dts flag in your build command.

// package.json — correct configuration:
{
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",  // Must point to real file
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",  // Required for Node 16+ resolution
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  }
}

Always run ls dist/ after building to verify the .d.ts file exists before publishing.

Re-export issues with dts-bundle-generator

If your library re-exports from node_modules (e.g., export type { Options } from "some-dep"), dts-bundle-generator may fail to resolve those types. The fix:

// tsup handles this automatically — use tsup for libraries with re-exported third-party types
// For dts-bundle-generator, use the inlinedLibraries option:
const result = generateDtsBundle([{
  filePath: "./src/index.ts",
  libraries: {
    inlinedLibraries: ["some-dep"],  // Inline types from this dep
  },
}])

tsconfig paths not resolving

All three tools respect tsconfig.json paths aliases, but require the paths to be resolvable at build time. If you use @/utils path aliases, ensure your tsconfig paths are configured and the tool can find them.

// tsconfig.json — path aliases:
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

For tsup, path aliases are resolved automatically via esbuild. For rollup-plugin-dts, add @rollup/plugin-alias. For dts-bundle-generator, path aliases are resolved via the TypeScript compiler — just ensure the correct tsconfig is passed via --project.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on tsup v8.x, dts-bundle-generator v9.x, and rollup-plugin-dts v6.x.

Compare TypeScript build tools and bundler packages on PkgPulse →

See also: Rollup vs webpack and Bun vs Vite, archiver vs adm-zip vs JSZip (2026).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.