tsup 8.5.1 vs unbuild 3.6.1 on a 150-module TypeScript library (ESM+CJS): median 931 ms vs 2034 ms (~2.2x); with .d.ts 3213 ms vs 3740 ms (~1.16x). unbuild ESM 4.9% smaller, CJS 8.3% smaller; tsup README says not actively maintained.
5 min read
On a 150-file TypeScript library built to ESM plus CJS, tsup 8.5.1 took a median 931 ms and unbuild 3.6.1 took 2034 ms (~2.2x slower). With .d.ts generation on, the gap shrank to 3213 ms vs 3740 ms, and unbuild's ESM output was about 5% smaller (114.6 KB vs 120.5 KB).
Short answer: stay on tsup if raw build speed is your main concern and you are fine with a project whose README now says it is not actively maintained. Move to unbuild if you want an actively developed Rollup-based builder, smaller output, and --stub linking during development. No affiliate links in this post.
.ts modules plus one index.ts barrel. Each module has an interface, a union type, a string enum, a generic class, and a function that imports from the previous module (600 exports in total).tsup 8.5.1 (esbuild 0.27.7) and unbuild 3.6.1 (rollup 4.64.0, rollup-plugin-dts 6.5.1), TypeScript 5.9.3. npm installed TS 5.9 rather than the 7.0 latest tag for these two builders. Both set to target: es2022, formats ESM and CJS, one entry.process.hrtime around the full npx command.util149(1) returned 11176 from ESM and CJS, and both exposed 600 exports.package.json. Setting failOnWarn: false (or adding a proper exports map) fixed it.mkdist bundleless mode, multi-entry libraries, watch mode, Vue or JSX sources, Windows/macOS.| Variant | Median wall | Runs (ms) |
|---|---|---|
| tsup 8.5.1, ESM+CJS | 931 ms | 1027 / 890 / 931 / 1004 / 870 |
| unbuild 3.6.1, ESM+CJS | 2034 ms | 2034 / 2099 / 1939 / 2054 / 1964 |
| tsup 8.5.1, ESM+CJS + .d.ts | 3213 ms | 3448 / 3130 / 3162 / 3213 / 3234 |
| unbuild 3.6.1, ESM+CJS + .d.ts | 3740 ms | 3817 / 3740 / 3570 / 3802 / 3707 |
Yes. tsup transpiles and bundles with esbuild, while unbuild runs Rollup with an esbuild transform, and on this fixture that Rollup path cost about 1.1 s more. Once declaration files are on, both spend most of their time in the TypeScript compiler, so the difference drops to about 0.5 s (~1.16x). For most libraries that publish types, that is the number that matters.
The bundler underneath is the real split. The Rolldown vs Rollup post and the SWC vs esbuild vs Babel post have engine-level numbers from the same box.
| File | tsup 8.5.1 | unbuild 3.6.1 |
|---|---|---|
| ESM bundle | index.js 120,537 B | index.mjs 114,578 B |
| CJS bundle | index.cjs 136,252 B | index.cjs 124,942 B |
| Type declarations | index.d.ts + index.d.cts, 106,671 B each | index.d.ts + .d.mts + .d.cts, 105,187 B each |
| Total on disk | 470,131 B | 555,081 B |
unbuild's JS was smaller: 4.9% for ESM and 8.3% for CJS. Most of the gap is tsup writing a // src/modN.ts comment before each module and emitting each class as var Svc0 = class with an extra prefix; field line, where Rollup kept plain class Svc0. unbuild's total is larger only because declaration mode compatible writes a third .d.ts copy. Neither output was minified; if you minify, see the Terser vs esbuild vs SWC minify post.
| Situation | My pick |
|---|---|
| Single-entry library that ships types, builds in CI | Either works; unbuild cost about 0.5 s more here |
| You rebuild constantly without types (local loops) | tsup was 2.2x faster in this test |
| You want an actively developed tool for the next few years | Plan a move: unbuild, or tsdown as the tsup README suggests |
| You link the package into an app during development | unbuild, for unbuild --stub |
Strict "no warnings" CI and no exports map yet | Fix package.json first or unbuild will fail the build |
Bottom line: tsup won on speed (931 ms vs 2034 ms without types, 3.2 s vs 3.7 s with types) and unbuild won on output size and maintenance status. Who should not switch yet: teams on tsup with no build pain and no plan to touch their tooling this quarter. If you are also weighing TypeScript itself, read the TypeScript 7 upgrade post first, because declaration builds here still ran on TS 5.9.
How this was made: I generated the TypeScript fixture, ran the interleaved builds on the ShopperCove test box, and kept the timing and size JSON; the write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 5 Oct 2026 ~23:16-23:17 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). Fixture: 150 .ts modules + index.ts barrel (interface, union type, string enum, generic class, chained function each; 600 exports). Versions: tsup 8.5.1 (esbuild 0.27.7), unbuild 3.6.1 (rollup 4.64.0, rollup-plugin-dts 6.5.1), typescript 5.9.3; target es2022, ESM+CJS, single entry. 1 warmup each, 5 interleaved rounds. Medians: tsup no-dts 930.9 ms (1027.3/890/930.9/1003.7/870.2); unbuild no-dts 2033.9 ms (2033.9/2099.3/1939/2054.2/1964.2); tsup dts 3213 ms (3447.8/3130.3/3161.6/3213/3234.1); unbuild dts (compatible) 3740.2 ms (3817.4/3740.2/3570.4/3802.4/3707.1). Sizes: tsup index.js 120537, index.cjs 136252, d.ts/d.cts 106671 each; unbuild index.mjs 114578, index.cjs 124942, d.ts/d.mts/d.cts 105187 each. Smoke: util149(1)=11176 ESM+CJS both, 600 exports. Gotcha: unbuild failOnWarn exit 1 on missing package.json exports; set failOnWarn:false. Raw: /workspace/bench-tsup-unbuild/res-tsup-unbuild.json. Not tested: tsdown, mkdist mode, multi-entry, watch, Windows/macOS. No affiliate.
Hands-on6 Oct 20266 min read
Hands-on6 Oct 20265 min read
Rolldown 1.2.12 vs Rollup 4.64.0 on 501 ESM files (197380 B source): median wall 245.617 ms vs 964.3 ms (~3.9x). Output 130522 vs 126894 B. Direct node bin, 5 interleaved warm runs, no minify.
Summary5 Oct 20264 min read