# unplugin-dts for cross-bundler TypeScript declarations

> unplugin-dts generates .d.ts, .d.mts, and .d.cts output for TypeScript, TSX, and Vue sources across six named bundlers. It suits library packaging, while package exports and declaration layouts still require deliberate configuration.

**qmhc/unplugin-dts** — An unplugin for generating declaration (dts) files.

- Repository: https://github.com/qmhc/unplugin-dts
- Stars: 1,531 · Forks: 113
- Language: TypeScript
- License: not declared
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/qmhc-unplugin-dts

## Why unplugin-dts exists for TypeScript library builds

unplugin-dts addresses the gap between JavaScript bundling and TypeScript declaration output. A library can have its JavaScript bundled while still needing type information that downstream editors and consumers can resolve. The project targets that packaging step for `.ts`, `.tsx`, and `.vue` source files, particularly when a Vite library build is being assembled.

Its main audience is library authors rather than application developers who only compile code for one internal deployment. Those authors often need declarations to survive a bundling pipeline, match the exported package surface, and work across supported module systems. The README also points to Vexip UI as a real project using the plugin, but it does not provide details about that project's build or maintenance requirements.

The plugin does not infer package metadata from the bundler. Its responsibility ends with writing configured declaration variants. Fields such as conditional `exports.types` in `package.json` remain the package author's responsibility. That separation can leave a build with valid declaration files while the package still resolves to the wrong types, making package-level verification an important part of adoption.

The repository is not archived, and the last push was on 2026-10-03. Releases include `unplugin-dts@1.1.2` on 2026-10-03, `unplugin-dts@1.1.1` on 2026-09-20, and `unplugin-dts@1.1.0` on 2026-09-01. These dates establish recent repository activity, but the README does not define a long-term support schedule or an upgrade compatibility policy.

## How unplugin-dts preserves TypeScript declaration formats

The plugin sits inside a bundler configuration and generates declarations from TypeScript-family sources as part of a library build. The documented integration exposes a default import from a bundler-specific entry point, with `unplugin-dts/vite` shown for Vite. The repository describes support for Vite, Rollup, Rolldown, Webpack, Rspack, and Esbuild, while directing readers to separate usage documentation for each setup.

Its default output behavior preserves TypeScript declaration extensions rather than flattening every declaration into one suffix. TypeScript input files map from `.ts` to `.d.ts`, Module TypeScript files map from `.mts` to `.d.mts`, and CommonJS TypeScript files map from `.cts` to `.d.cts`. Declaration maps are included according to the same preservation rules described by the README. This distinction matters when a package publishes multiple declaration styles rather than one conventional `.d.ts` tree.

Source locations carried by declaration maps retain absolute `file://` URLs. The README specifically states that this behavior also covers Windows paths that cross drive boundaries. That can preserve editor navigation information, but declaration artifacts can therefore contain absolute source references rather than only portable relative paths. The README does not provide a separate configuration recipe for suppressing or rewriting those URLs.

`moduleFormat` controls which declaration variants the plugin writes. It is not inferred from the JavaScript formats selected in the bundler. To emit declarations for both ESM and CommonJS, the configuration must name both output directories and module formats explicitly. The plugin can therefore produce `dist` trees for both systems while `package.json` separately controls how `exports.types` selects between them.

## Installing unplugin-dts and adding a Vite build

The consumer package requires Node.js 20 or later. The README uses pnpm and installs the plugin as a development dependency because declaration generation belongs to the package build rather than runtime dependencies.

Install it with the documented command:

```bash
pnpm i -D unplugin-dts
```

A successful pnpm installation adds `unplugin-dts` to the project's development dependencies. The README does not document npm or Yarn installation commands, so this guide does not infer those package-manager variants.

For a Vite library-mode configuration, add the documented plugin entry. This is the first integration step. The README's quick-start snippet relies on the surrounding configuration context to provide `defineConfig`.

```ts
import dts from 'unplugin-dts/vite'

export default defineConfig({
  plugins: [dts()],
})
```

After the Vite library build runs, the plugin is expected to emit declaration files according to TypeScript's input extensions. The README does not provide a complete consumer-side build command or a sample resulting directory tree. It therefore does not document enough detail to claim what every Vite configuration will emit without its other settings.

The previous package name is still mentioned for Vite, but the README explicitly says that using `vite-plugin-dts` is not recommended. New cross-bundler setups should use `unplugin-dts`; the migration guide is listed for projects moving from `vite-plugin-dts` v4.

## Publishing separate ESM and CommonJS declarations

A package that publishes both ESM and CommonJS needs two declaration variants. The plugin does not derive these from JavaScript output formats, so the configuration must define each `outDirs` entry. The following is the README's complete dual-format example:

```ts
dts({
  outDirs: [
    { dir: 'dist', moduleFormat: 'esm' },
    { dir: 'dist', moduleFormat: 'cjs' },
  ],
  bundleTypes: true,
})
```

The configuration directs both writes to `dist`, with one request for ESM and one for CommonJS. `bundleTypes: true` enables bundling declarations, but the excerpt does not explain collision handling or show the exact final layout. Readers should not assume that two entries create separate directories merely because the same `dir` value appears twice. The output structure must be checked against the plugin's detailed options documentation and the package's intended exports map.

This creates a concrete failure mode. A build can emit declarations while `package.json` still points `exports.types` at the wrong file or condition. The README is direct about the boundary: the plugin writes configured variants, while conditional `exports.types` entries remain package metadata. Adoption therefore requires inspecting both the generated files and the package manifest.

The README does not document a declaration-validation command. A first release check should confirm that editors resolve the published type entry for ESM and CommonJS, rather than relying only on JavaScript import success.

## Cross-bundler reach and the vite-plugin-dts alternative

The main difference from the named predecessor is placement. According to the README, `unplugin-dts` evolved from `vite-plugin-dts` and was rewritten with unplugin. The older package is described as Vite-only, while the new plugin supports Vite, Rollup, Rolldown, Webpack, Rspack, and Esbuild. `vite-plugin-dts` is therefore the direct documented alternative for a Vite-only project, although the README says it is not recommended.

This does not mean every project should move immediately. A Vite-only library that already works with `vite-plugin-dts` may gain little from changing packages, particularly if its declaration configuration is already settled. The migration page is specifically scoped from v4 to v5, so users should consult it before treating installation of `unplugin-dts` as a drop-in replacement.

The plugin is a poor fit when the build does not use one of the six named bundlers and the repository's usage documentation offers no supported integration. It is also a poor fit for an application project that does not publish or consume generated declarations. Its documented job is declaration output for a library build, not general type checking or source transformation.

The repository includes playground commands for several combinations, including `demo:react-vite`, `demo:svelte-vite`, `demo:ts-esbuild`, `demo:ts-rolldown`, `demo:ts-rollup`, `demo:ts-rspack`, `demo:ts-vite`, `demo:ts-webpack`, and Vue variants. Those scripts are repository maintenance examples, not documented consumer installation steps.

## Upgrade work and the MIT package boundary

The package is published under the MIT License, as stated in the README and indicated by the repository's license entry. That designation gives adoption a clear licensing basis, but teams still need to review `packages/unplugin-dts/LICENSE` for the actual terms and notice obligations that apply to the version they use. This article does not interpret the license text or provide legal advice.

Upgrading may require more than changing the version number. The README links to a dedicated migration guide for `vite-plugin-dts` v4, which indicates that v5 was treated as a package transition rather than documented as a transparent drop-in change. The README does not list changed options or breaking changes, so their scope cannot be established from the README.

Node.js compatibility is explicit: version 20 or later is required. A project on an older runtime cannot adopt the documented release without changing its runtime baseline or selecting a different declaration strategy. The consumer package manager is not constrained, although the README's installation example uses pnpm and the repository identifies `pnpm@10.11.0` as its package manager.

Declaration maps also deserve attention during release review because they retain absolute `file://` source URLs. The project documents this behavior as intentional, including Windows cross-drive paths, but it does not discuss whether consumers should publish those maps or how they affect redistribution. That policy decision must be checked against package requirements rather than assumed.

## Conclusion

Library authors who need one declaration-generation interface across several supported bundlers should adopt unplugin-dts. Projects limited to Vite or already settled on direct TypeScript declaration output should compare that simpler path first. Before adoption, verify declaration extensions, declaration-map URLs, package.json exports.types mappings, and dual ESM and CJS output paths.

## FAQ

### Which bundlers does unplugin-dts support?

The README lists Vite, Rollup, Rolldown, Webpack, Rspack, and Esbuild. Bundler-specific setup is delegated to the linked usage documentation.

### What Node.js version does unplugin-dts require?

The documented requirement is Node.js 20 or later. The package is installed as a development dependency in the README example.

### Does unplugin-dts automatically configure package.json exports.types?

No. The plugin writes configured declaration variants, while conditional `exports.types` entries remain package metadata that the package author configures in package.json.

## Sources

- [Issues](https://github.com/qmhc/unplugin-dts/issues)
- [qmhc/unplugin-dts on GitHub](https://github.com/qmhc/unplugin-dts)
- [README](https://github.com/qmhc/unplugin-dts/blob/main/README.md)
- [Releases](https://github.com/qmhc/unplugin-dts/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qmhc-unplugin-dts
