Skip to content

Support DTS-specific dependency inlining (inline types from external packages) #199

@YevheniiKotyrlo

Description

@YevheniiKotyrlo

Problem

When a library uses tsdown with deps.neverBundle (formerly external) to externalize peer dependencies, the DTS output retains hard import type statements for those packages. Consumers who don't install all externalized packages get TS2307 errors with skipLibCheck: false.

There is currently no way to keep a package external for JS output while inlining its types in DTS output.

Real-world example: unplugin

unplugin externalizes 8 bundler packages (vite, webpack, rollup, esbuild, @farmfe/core, @rspack/core, rolldown, unloader). This is correct for JS — consumers only load the bundler they use at runtime. But the DTS output hard-imports types from all 8, causing TS2307 for any consumer that doesn't install every bundler.

I filed unjs/unplugin#589 about this — the root cause is that there's no DTS-specific dependency configuration in tsdown/rolldown-plugin-dts.

Prior discussion

This was discussed in #106, which was closed with:

This is definitely a goal worth pursuing, though I haven't figured out how to approach it yet.

Requested feature

A way to inline types from specific external packages in DTS output while keeping them external for JS. For example:

// In tsdown config
export default defineConfig({
  deps: {
    neverBundle: ['webpack', 'vite', ...],  // external for JS
  },
  dts: {
    // Hypothetical: inline these packages' types in .d.ts
    // even though they're external for JS
    resolve: true,
    // or: resolve: ['webpack', '@rspack/core', ...]
  },
})

Or alternatively, have rolldown-plugin-dts respect Rolldown's external option by default (as #106 suggested) — packages not marked external get their types inlined, packages marked external stay as import type.

The key insight is that JS externalization and DTS externalization serve different purposes:

  • JS external: "don't bundle this — it'll be resolved at runtime" (correct for peer deps)
  • DTS external: "keep import type from this package" (breaks consumers who don't install it)

Impact

This affects any library that:

  1. Supports multiple frameworks/bundlers as optional peer dependencies
  2. Uses tsdown with deps.neverBundle for those packages
  3. Has consumers with skipLibCheck: false

unplugin is the most prominent example, but any multi-framework library faces the same issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions