setup-tsup

Set up tsup (esbuild-based bundler) for a TypeScript package. Use when you need single-file bundled output, faster builds, and better startup time for Node.js CLIs or libraries. Covers tsup config, bi

SKILL.md

GitHub

Set Up tsup Build

Goal

Replace or add tsup as the build tool for a TypeScript package. Keep tsc --noEmit for type-checking only.

Why

  • Single-file bundled output = 2-5x faster startup (fewer fs reads during module resolution)
  • Tree-shakes unused code from dependencies
  • esbuild-based = sub-second builds vs multi-second tsc

Steps

1. Install tsup

pnpm add -D tsup

2. Create tsup.config.ts

import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm'],
  target: 'node22',
  clean: true,
  splitting: false,
  sourcemap: true,
  dts: false,
  // For CLIs: add shebang
  banner: {
    js: '#!/usr/bin/env node',
  },
  // Native/dynamic deps that shouldn't be bundled
  external: [],
})

For libraries (not CLIs), remove the banner and consider dts: true for declaration files.

3. Update package.json scripts

{
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "ts:check": "tsc --noEmit"
  }
}

4. Update bin entry point (CLIs only)

The bin file should point to the tsup output. tsup outputs to dist/ by default:

  • Verify the bin field references dist/index.js (or dist/index.mjs)
  • Since tsup adds the shebang via banner, the bin file can point directly to the bundled output

5. Verify

pnpm build           # Should produce dist/index.js (single file)
pnpm ts:check        # Type checking still works
node dist/index.js --help  # CLI works (if applicable)

6. Update .gitignore

Ensure dist/ is in .gitignore.

Gotchas

  • If any dependency uses __dirname or require.resolve, tsup may need those deps in external
  • Dynamic import() calls are preserved by esbuild — this is fine
  • Keep prepare or prepublishOnly script as pnpm build for npm publish workflow
  • For ESM packages, ensure "type": "module" in package.json