From tsup to tsdown: How Rust-Based Rolldown and Oxc Cut TypeScript Library Build Times by Orders of Magnitude
There comes a moment when you truly feel how slow a library build is. Maybe CI takes over two minutes to rebuild a single package, or you run pnpm build locally and have to go grab a coffee while you wait. Managing multiple shared packages in a monorepo, I felt that frustration for quite a while.
This article is for those already building TypeScript libraries with tsup. If you're new to tsup, it may help to read up on the basics first so the context lands better.
After switching to tsdown, everything changed. The shared package builds in my monorepo dropped from 2 minutes 30 seconds to 18 seconds, and .d.ts (TypeScript type declaration file) generation alone got up to 8× faster. Here's the core takeaway: tsdown, built on top of the Rust-based bundle engine Rolldown and Oxc, can cut build times by orders of magnitude — especially for multi-entry libraries or monorepo setups — and the migration takes less time than you'd expect.
I'll also be honest about when the gains are significant and when they might fall short of expectations.
Core Concepts
What tsdown is, and why it's getting attention now
tsdown is, in a word, the "spiritual successor to tsup." Just as tsup uses esbuild as its bundle engine, tsdown uses Rolldown.
Rolldown: A Rust-based JavaScript bundler developed by VoidZero, the company founded by Vue.js creator Evan You. Version 1.0 RC was released in January 2026, and it's set to replace both esbuild (dev server) and Rollup (production builds) with a single engine in Vite 8 Beta.
Rolldown is written in Rust, making it 10–30× faster than Rollup and comparable in speed to esbuild (per the official tsdown benchmark). It also maintains a Rollup-compatible API, which means the existing Rollup plugin ecosystem carries over — a significant advantage.
Here's the full tsdown stack at a glance:
| Component | Role |
|---|---|
| Rolldown | Rust-based core bundle engine (esbuild-level speed + Rollup-compatible API) |
| Oxc | Rust-based TypeScript parser / transpiler / .d.ts generator |
| tsdown | Tool abstracting library bundling workflow on top of Rolldown + Oxc |
The combination is powerful because the .d.ts generation bottleneck nearly disappears. .d.ts files are the TypeScript type declaration files shipped alongside a library so consumers can get type information — they're essential. The problem is that generating them takes longer than you'd expect. The old tsup approach ran through tsc (the TypeScript compiler) or rollup-plugin-dts, which often ate up more than half the total build time. tsdown delegates this work to Oxc, which handles it far faster.
What the isolatedDeclarations + Oxc combination changes
Honestly, I didn't expect the isolatedDeclarations TypeScript option to have this much impact at first. Introduced in TypeScript 5.5, enabling this option allows each file's type declarations to be generated independently — without cross-file dependencies.
isolatedDeclarations: A compiler option added in TypeScript 5.5+. It enforces that each file can generate its own
.d.tsindependently, without needing type information from other files. There are minor constraints on how you write code, but in exchange, type declaration generation speed improves dramatically.
// tsconfig.json
{
"compilerOptions": {
"isolatedDeclarations": true
}
}This is what allows tsdown to use Oxc to generate .d.ts files in parallel, at extreme speed. That said, enabling this option may surface a few type errors initially. The most common pattern is exported functions without explicit return types:
// isolatedDeclarations violation — return type cannot be inferred
export function createClient() { // ❌ return type must be explicit
return new HttpClient()
}
// Fixed
export function createClient(): HttpClient { // ✅
return new HttpClient()
}A handful of these patterns may come up, and once you fix them, the difference in .d.ts generation speed is immediately noticeable. It feels tedious at first, but it actually encourages you to explicitly declare your public API types — which turns out to be a good habit for library maintainability. Since Oxc doesn't yet perfectly support every complex type pattern, you may occasionally see an unsupported syntax warning. In those cases, the error message is usually quite clear about which expression is the problem.
Practical Application
A 30-second taste of the automatic migration CLI
The easiest starting point is the official migration CLI. The --dry-run flag lets you preview what changes will be made before anything is touched, so there's no risk in trying it out.
# Preview changes (no actual file modifications)
npx tsdown migrate --dry-run
# Run the actual migration
npx tsdown migrateHere's what the CLI handles automatically:
| Auto-handled item | Details |
|---|---|
| File rename | tsup.config.ts → tsdown.config.ts |
| Import replacement | from 'tsup' → from 'tsdown' |
| Dependency update | Removes tsup, adds tsdown in package.json |
| Script update | Replaces tsup commands with tsdown in package.json |
The one thing to verify manually after migration is the bundle: false → unbundle: true conversion. If you migrated the config by hand, be sure to check this.
The config file itself stays nearly identical — visually it's just one changed import:
// tsup.config.ts (before)
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})// tsdown.config.ts (after)
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true, // handled by Oxc — much faster
})The real difference you feel in multi-entry environments
For a small single-entry library, the impact may honestly not be dramatic. With Node.js startup costs factored in, a 1–2 second build dropping to 0.5 seconds isn't exactly jaw-dropping. But the story changes when you have multiple entries or a complex dependency graph.
One mistake I made when first setting up multi-entry configs: if even one path listed in entry is wrong, that entry can silently be skipped. Worth double-checking your paths.
// tsdown.config.ts — multi-entry configuration example
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: [
'src/index.ts',
'src/utils/index.ts',
'src/components/index.ts',
'src/hooks/index.ts',
],
format: ['esm', 'cjs'],
dts: true,
sourcemap: true,
clean: true,
})Here's what I actually measured with time pnpm build on a UI component package with 4 entries:
# tsup (before)
$ time pnpm build
✓ Building entry: src/index.ts, src/utils/index.ts, src/components/index.ts, src/hooks/index.ts
✓ Build success
pnpm build 43.21s user 4.87s system 98% cpu 48.612 total
# tsdown (after)
$ time pnpm build
✓ Build success
pnpm build 4.89s user 0.61s system 97% cpu 5.634 totalThat's roughly an 8.6× difference. This package had a high proportion of .d.ts generation, so it got the full benefit of Oxc processing.
TresJS (the Vue renderer for Three.js) also saw a 50% reduction in bundle size after switching to tsdown — though in their case, the main driver was the decision to drop CJS/UMD formats and ship ESM only, not tsdown's speed itself. tsdown's ESM-first design philosophy prompted the cleanup of unnecessary formats. This should be treated as separate from build speed improvements.
Bulk migration for monorepo CI
If your CI sequentially builds multiple shared packages in a monorepo, the gains are especially significant. On the first day after migrating my monorepo, when the CI completion notification came in, I thought the pipeline had failed. It went from 2 minutes 30 seconds to 18 seconds — I genuinely wondered if something had gone wrong.
The Makeplane/Plane project also moved all shared package and live server builds to tsdown in PR #7679, unifying the toolchain to TypeScript 5.8.3 and tsdown 0.14.x, which visibly improved the build pipeline.
In a pnpm monorepo, using --filter from the root is the more convenient approach:
# Install tsdown per package (from root)
pnpm add -D tsdown --filter @myorg/shared-utils
pnpm add -D tsdown --filter @myorg/ui-components
# Run migration with filter from root
pnpm exec tsdown migrate --filter @myorg/shared-utils
pnpm exec tsdown migrate --filter @myorg/ui-components
# Or run directly from each package directory
cd packages/shared-utils && npx tsdown migrate
cd packages/ui-components && npx tsdown migratePros and Cons
Advantages
| Item | Details |
|---|---|
| Build speed | ~2× faster than tsup for general builds, up to 8× faster for .d.ts generation (based on multi-entry + isolatedDeclarations enabled, official benchmark) |
| Migration convenience | Automatic migration CLI provided; config structure is nearly identical to tsup |
| Zero-config DTS | .d.ts auto-generated with just the types field in package.json or declaration: true |
| Plugin compatibility | Full use of the Rollup plugin ecosystem; unplugin and some Vite plugins also supported |
| ESM-first design | Design philosophy aligned with modern package distribution practices |
| Oxc integration | Ultra-fast parallel type declaration generation when isolatedDeclarations is enabled |
Drawbacks and Caveats
| Item | Details | Workaround |
|---|---|---|
| Package size | tsdown's install footprint is considerably larger than tsup | Only affects devDependencies, not your published package |
| No stub mode | No equivalent to unbuild's stub mode | Use pnpm link or workspace protocol for monorepo linking |
| Watch mode issues | Reported compilation-blocking bug when combining multiple useConfigs blocks with watch mode |
Consolidate into a single config block, or wait for a fix release |
| Oxc DTS compatibility | Unsupported syntax warnings may occur for some complex type patterns with isolatedDeclarations |
Check error messages and add explicit type declarations at the flagged locations |
| Limited gains for small libraries | Single-entry small libraries see minimal difference due to Node.js startup costs | Calibrate expectations accordingly |
| Ecosystem maturity | Plugin and reference ecosystem is still smaller than tsup's | Check whether the plugin you need exists for Rollup first |
unplugin: A unified plugin interface that works across multiple bundlers including Rollup, Vite, and Webpack. tsdown can use plugins from this ecosystem directly, which helps bridge some of the ecosystem gap.
The most common mistakes in practice
- Expecting DTS speed without
isolatedDeclarations— tsdown's ultra-fast type declaration generation only fully shines when this option is enabled. Without adding it to tsconfig, the improvement may be smaller than expected. - Blindly copying
bundle: false— In tsdown, this becomesunbundle: true. If you used the automatic migration CLI it's handled, but if you ported the config manually, be sure to verify. - Carrying over multiple config blocks with watch mode — There's a known bug with this combination. If you use watch mode, consolidating into a single config block is the safer path.
Closing Thoughts
tsdown is not simply "a faster tsup" — it's a demonstration that a Rust-based JavaScript toolchain can bring real, tangible change to the library bundling workflow. Especially for multi-entry or monorepo setups, the cost of migrating is far outweighed by what you gain.
Three steps you can take right now:
- Run the migration — In an existing tsup project, run
npx tsdown migrate --dry-runto see which files will change and how, thennpx tsdown migrateto do the actual switch. Since the config structure is nearly identical, most migrations complete in a matter of minutes. - Measure your build time — Use
time pnpm buildto compare before and after. The difference will be most apparent in multi-entry or monorepo environments. - Enable
isolatedDeclarations— Add"isolatedDeclarations": trueto your tsconfig. You may see a few type errors initially from required explicit return types, but once you fix them, the improvement in.d.tsgeneration speed is unmistakable.
If you're running a monorepo that builds multiple packages in CI, a dry-run right now is well worth the minute it takes.
References
- tsdown Official Documentation | tsdown.dev
- Official Migration Guide from tsup to tsdown | tsdown.dev
- tsdown Benchmark | tsdown.dev
- tsdown Declaration File (.d.ts) Options | tsdown.dev
- tsdown GitHub Repository | github.com/rolldown/tsdown
- TresJS tsdown Migration Case Study | tresjs.org
- Makeplane/Plane tsdown Migration PR #7679 | github.com
- Switching from tsup to tsdown | alan.norbauer.com
- Rolldown 1.0 Announcement | voidzero.dev
- Vite 8 Beta Announcement | voidzero.dev
- tsup vs tsdown vs unbuild 2026 Comparison | pkgpulse.com
- e18e Ecosystem tsup → tsdown Migration Issue | github.com
- Dual publish ESM and CJS with tsdown | dev.to