PettyUI/.firecrawl/pkgpulse-bundling.md
Mats Bosson db906fd85a Fix linting config and package fields
- Replace .eslintrc.cjs with eslint.config.mjs (ESLint 9 flat config)
  using direct eslint-plugin-solid + @typescript-eslint/parser approach
- Add @typescript-eslint/parser to root devDependencies
- Add main/module/types top-level fields to packages/core/package.json
- Add resolve.conditions to packages/core/vite.config.ts
- Create packages/core/tsconfig.test.json for test type-checking
- Remove empty paths:{} from packages/core/tsconfig.json
2026-03-29 02:35:57 +07:00

361 lines
11 KiB
Markdown

[Skip to main content](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tldr)
**tsup** is the most popular TypeScript library bundler — zero-config, generates ESM + CJS + `.d.ts` types automatically, used by thousands of npm packages. **tsdown** is the next-generation successor to tsup — built on Rolldown (Vite's Rust-based bundler), significantly faster, same DX but better performance. **unbuild** is from the UnJS ecosystem — supports multiple build presets, stub mode for development (no build step), and is used by Nuxt, Nitro, and UnJS libraries internally. In 2026: tsup is the safe choice with the largest community, tsdown is the emerging performance leader, unbuild if you're in the Nuxt/UnJS ecosystem.
## [Key Takeaways](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#key-takeaways)
- **tsup**: ~6M weekly downloads — esbuild-based, zero-config, ESM + CJS + types in one command
- **tsdown**: ~500K weekly downloads — Rolldown-based (Rust), 3-5x faster than tsup, tsup-compatible API
- **unbuild**: ~3M weekly downloads — UnJS, stub mode, multiple presets, Rollup-based
- All three generate dual ESM/CJS output — required for modern npm packages
- All three generate TypeScript declaration files (`.d.ts`) automatically
- tsdown is rapidly gaining adoption in 2026 as the fast tsup replacement
* * *
## [Why Library Bundling Matters](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#why-library-bundling-matters)
```
Problem: publishing TypeScript to npm requires:
1. Compile TypeScript → JavaScript
2. Generate .d.ts type declarations
3. Create both ESM (import) and CJS (require) versions
4. Tree-shaking to minimize bundle size
5. Correct package.json "exports" map
Without a bundler:
tsc --outDir dist → CJS only, no bundling
+ manually maintain package.json exports
+ separately configure dts generation
+ no tree-shaking
With tsup/tsdown/unbuild:
One command → dist/index.mjs + dist/index.cjs + dist/index.d.ts
```
* * *
## [tsup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsup)
[tsup](https://tsup.egoist.dev/) — zero-config library bundler:
### [Setup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup)
```bash
npm install -D tsup typescript
# Add to package.json scripts:
{
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}
```
### [tsup.config.ts](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsupconfigts)
```typescript
import { defineConfig } from "tsup"
export default defineConfig({
entry: ["src/index.ts"], // Entry point(s)
format: ["esm", "cjs"], // Output ESM + CommonJS
dts: true, // Generate .d.ts files
splitting: false, // Code splitting (for multiple entry points)
sourcemap: true, // Generate source maps
clean: true, // Clean dist/ before build
minify: false, // Don't minify libraries (let consumers decide)
external: ["react", "vue"], // Don't bundle peer dependencies
treeshake: true, // Remove unused code
target: "es2020", // Output target
outDir: "dist",
})
```
### [Multiple entry points](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#multiple-entry-points)
```typescript
import { defineConfig } from "tsup"
export default defineConfig({
// Multiple entry points (for sub-path exports):
entry: {
index: "src/index.ts",
server: "src/server.ts",
client: "src/client.ts",
},
format: ["esm", "cjs"],
dts: true,
splitting: true, // Share code between entry points
})
// Generates:
// dist/index.mjs + dist/index.js + dist/index.d.ts
// dist/server.mjs + dist/server.js + dist/server.d.ts
// dist/client.mjs + dist/client.js + dist/client.d.ts
```
### [package.json for dual ESM/CJS](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#packagejson-for-dual-esmcjs)
```json
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js", // CJS entry (legacy)
"module": "./dist/index.mjs", // ESM entry (bundlers)
"types": "./dist/index.d.ts", // TypeScript types
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./server": {
"import": "./dist/server.mjs",
"require": "./dist/server.js"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}
```
### [Watch mode for development](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#watch-mode-for-development)
```bash
# Rebuild on file changes:
tsup --watch
# Or in parallel with your dev server:
# package.json:
{
"scripts": {
"dev": "concurrently \"tsup --watch\" \"node dist/index.js\""
}
}
```
* * *
## [tsdown](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsdown)
[tsdown](https://tsdown.egoist.dev/) — the Rolldown-based tsup successor:
### [Why tsdown is faster](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#why-tsdown-is-faster)
```
tsup uses: esbuild (Go) → fast, but JS orchestration overhead
tsdown uses: Rolldown (Rust) → faster bundler + faster orchestration
Build time comparison (real-world library with 50 files):
tsup: ~2.5s
tsdown: ~0.6s
(varies by project size and machine)
```
### [Setup (nearly identical to tsup)](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup-nearly-identical-to-tsup)
```typescript
// tsdown.config.ts
import { defineConfig } from "tsdown"
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
clean: true,
sourcemap: true,
external: ["react"],
})
```
```bash
# Commands are the same as tsup:
npx tsdown # Build
npx tsdown --watch # Watch mode
```
### [tsup → tsdown migration](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsup--tsdown-migration)
```bash
# Install:
npm uninstall tsup
npm install -D tsdown
# Rename config file:
mv tsup.config.ts tsdown.config.ts
# Update import:
# - import { defineConfig } from "tsup"
# + import { defineConfig } from "tsdown"
# Update package.json scripts:
# - "build": "tsup"
# + "build": "tsdown"
```
* * *
## [unbuild](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#unbuild)
[unbuild](https://github.com/unjs/unbuild) — UnJS library bundler:
### [Setup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup-1)
```typescript
// build.config.ts
import { defineBuildConfig } from "unbuild"
export default defineBuildConfig({
entries: ["src/index"],
rollup: {
emitCJS: true, // Also emit CommonJS
},
declaration: true, // Generate .d.ts
clean: true,
})
```
```bash
# Build:
npx unbuild
# Stub mode (development):
npx unbuild --stub
```
### [Stub mode (unique to unbuild)](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#stub-mode-unique-to-unbuild)
```typescript
// "Stub mode" — generates proxy files that require/import the source directly
// No build step needed during development!
// dist/index.mjs (stub):
// export * from "../src/index.ts"
// dist/index.js (stub):
// module.exports = require("../src/index.ts") // via jiti
// Benefits:
// - No watch mode needed — file changes are picked up immediately
// - Faster feedback loop when developing a library locally
// - Link the package to a consumer with npm link — changes are live
// Production build:
// npx unbuild ← produces real bundles (no stub)
```
### [Multiple presets](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#multiple-presets)
```typescript
import { defineBuildConfig } from "unbuild"
export default defineBuildConfig([\
// Main package:\
{\
entries: ["src/index"],\
declaration: true,\
rollup: { emitCJS: true },\
},\
// CLI entry (no types needed):\
{\
entries: [{ input: "src/cli", name: "cli" }],\
declaration: false,\
rollup: {\
emitCJS: false,\
inlineDependencies: true, // Bundle everything into the CLI binary\
},\
},\
])
```
### [Used by the UnJS ecosystem](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#used-by-the-unjs-ecosystem)
```
unbuild is used by:
- nuxt → @nuxt/... packages
- nitro → the Nuxt server engine
- h3 → the HTTP framework
- ofetch → the fetch wrapper
- Most @unjs/* packages
If you're contributing to or building in this ecosystem, unbuild
is the natural choice.
```
* * *
## [Feature Comparison](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#feature-comparison)
| Feature | tsup | tsdown | unbuild |
| --- | --- | --- | --- |
| Build engine | esbuild (Go) | Rolldown (Rust) | Rollup (JS) |
| Build speed | Fast | ⚡ Fastest | Moderate |
| ESM + CJS | ✅ | ✅ | ✅ |
| .d.ts generation | ✅ | ✅ | ✅ |
| Stub mode (no build) | ❌ | ❌ | ✅ |
| Code splitting | ✅ | ✅ | ✅ |
| treeshake | ✅ | ✅ | ✅ |
| Plugin ecosystem | esbuild plugins | Rolldown plugins | Rollup plugins |
| TypeScript config | tsup.config.ts | tsdown.config.ts | build.config.ts |
| Community size | ⭐ Large | Growing fast | Medium |
| Weekly downloads | ~6M | ~500K | ~3M |
* * *
## [When to Use Each](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#when-to-use-each)
**Choose tsup if:**
- The safe, battle-tested choice — most tutorials and examples use it
- Large community, most Stack Overflow answers, most plugins
- Works for 95% of library use cases out of the box
**Choose tsdown if:**
- Build speed is a priority (large libraries, frequent CI builds)
- You're migrating from tsup — API is nearly identical
- On the cutting edge of tooling in 2026
**Choose unbuild if:**
- Working in the Nuxt, Nitro, or UnJS ecosystem
- Want stub mode for instant development without watch rebuilds
- Need Rollup-specific plugins not available in esbuild/Rolldown
**Also consider:**
- **Vite Library Mode** — for libraries that need Vite plugins (CSS modules, etc.)
- **pkgroll** — minimal bundler for packages with simple needs
- **microbundle** — smaller alternative, but less actively maintained in 2026
* * *
## [Methodology](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#methodology)
Download data from npm registry (weekly average, February 2026). Feature comparison based on tsup v8.x, tsdown v0.x, and unbuild v2.x.
_[Compare build tooling and bundler packages on PkgPulse →](https://www.pkgpulse.com/)_
## Comments
### 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.
Get the Free Cheatsheet