- 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
11 KiB
TL;DR
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
- 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
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
tsup — zero-config library bundler:
Setup
npm install -D tsup typescript
# Add to package.json scripts:
{
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}
tsup.config.ts
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
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
{
"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
# 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
tsdown — the Rolldown-based tsup successor:
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)
// 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"],
})
# Commands are the same as tsup:
npx tsdown # Build
npx tsdown --watch # Watch mode
tsup → tsdown migration
# 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
unbuild — UnJS library bundler:
Setup
// 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,
})
# Build:
npx unbuild
# Stub mode (development):
npx unbuild --stub
Stub mode (unique to unbuild)
// "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
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
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
| 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
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
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 →
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