tsup: Bundle TypeScript Packages Without Config
Publishing a TypeScript npm package requires bundling to JavaScript, generating type declarations, supporting both ESM and CommonJS, and managing sourcemaps. Most bundler setups require significant configuration. tsup handles all of this with minimal configuration, powered by esbuild under the hood.
Install
npm install --save-dev tsup
# or
pnpm add -D tsup
Zero-Config Start
For a simple package with src/index.ts as the entry:
npx tsup src/index.ts
This outputs:
dist/
index.js # CommonJS
index.mjs # ESM
index.d.ts # TypeScript declarations
No config file needed for basic cases.
package.json Setup
{
"name": "my-package",
"version": "1.0.0",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}
Configuration File
For more complex packages, create tsup.config.ts:
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true, // Generate .d.ts files
splitting: false, // Disable code splitting for libraries
sourcemap: true,
clean: true, // Clean dist/ before build
minify: false, // Don't minify (readable library code)
external: ["react"], // Don't bundle peer dependencies
outDir: "dist",
});
Multiple Entry Points
For packages exporting multiple subpaths:
export default defineConfig({
entry: {
index: "src/index.ts",
utils: "src/utils.ts",
cli: "src/cli.ts",
},
format: ["cjs", "esm"],
dts: true,
});
With corresponding package.json exports:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.js"
}
}
}
CLI Tools
For CLI packages, use the banner option to add the shebang:
export default defineConfig({
entry: ["src/cli.ts"],
format: ["cjs"], // CLI tools typically ship as CJS
dts: false, // No types needed for CLI
banner: {
js: "#!/usr/bin/env node",
},
});
{
"bin": {
"my-cli": "./dist/cli.js"
}
}
Watch Mode for Development
tsup --watch
Rebuilds on file changes. Useful during development of a package that you're testing in another project via npm link or workspace references.
Type Declarations
tsup uses TypeScript's compiler API to generate .d.ts files when dts: true is set. This is slower than the JS bundling (esbuild doesn't generate types) but correct.
// If you only need types for some entries:
export default defineConfig({
entry: ["src/index.ts", "src/cli.ts"],
dts: {
entry: "src/index.ts", // Only generate types for index, not cli
},
});
Treeshaking
export default defineConfig({
treeshake: true, // Remove unused exports
splitting: true, // Enable code splitting (ESM only)
});
Treeshaking is most valuable for large packages where consumers may only use part of the API.
Environment Variables and Defines
export default defineConfig({
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
__VERSION__: JSON.stringify(process.env.npm_package_version),
},
});
Comparison: tsup vs Other Options
| tsup | Rollup | tsc | esbuild | |
|---|---|---|---|---|
| Config needed | Minimal | Extensive | tsconfig | Moderate |
| Speed | Very fast | Moderate | Slow | Fastest |
| Type declarations | Built-in | Plugin | Built-in | No |
| ESM + CJS output | One command | Multiple configs | Limited | Manual |
| Maturity | 2021+ | Established | — | Established |
tsup hits the sweet spot for library packages: fast enough (esbuild core), correct type declarations, dual ESM/CJS output without significant configuration.
Common Patterns
React Component Library
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
external: ["react", "react-dom"],
jsx: "react-jsx", // Handle JSX
sourcemap: true,
});
Pure ESM Package
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm"], // ESM only
dts: true,
});
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
Monorepo Package
In a pnpm workspace, tsup works with zero additional configuration:
packages/
ui/
src/index.ts
tsup.config.ts
package.json
utils/
src/index.ts
tsup.config.ts
package.json
Each package has its own tsup.config.ts. Turborepo or Nx handles build ordering.
Size Analysis
# Check bundle size after build
npx tsup --analyze
For detailed analysis, integrate with bundlephobia or bundlesize in CI.
tsup is the default choice for new TypeScript package projects — it handles the common cases correctly out of the box and gets out of the way. The tsup documentation covers advanced cases including custom plugins via esbuild's plugin API.