tsdown
> Base tsdown configuration for jmlweb projects. The successor to @jmlweb/tsup-config-base (deprecated), with the same helper API. Recommended build config.
✨ Features
Section titled “✨ Features”- 🎯 Sensible defaults: Entry, dual CJS/ESM format, declarations, clean,
dist - 🔁 Plain tsdown options: Every tsdown option goes at the top level; the presets only add defaults
- 🧭 Stable file names: Keeps
.js/.cjs/.d.ts/.d.ctsso existingexportsmaps keep working - ⚡ Rolldown + Oxc: Fast builds, and declarations without the TypeScript compiler API when you opt in
- 🖥️ CLI preset: Shebang injection, including per-entry shebangs in one config
📦 Installation
Section titled “📦 Installation”npm install --save-dev @jmlweb/tsdown-config-base tsdown typescript> typescript is needed for declarations unless you enable isolatedDeclarations (see Declaration Files).
🚀 Quick Start
Section titled “🚀 Quick Start”Create tsdown.config.ts in your project root:
import { createTsdownConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownConfig();💡 Examples
Section titled “💡 Examples”External Dependencies
Section titled “External Dependencies”import { createTsdownConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownConfig({ deps: { neverBundle: ['@jmlweb/eslint-config-base-js', '@eslint/js', 'eslint'], },});Multiple Entries and Extra Options
Section titled “Multiple Entries and Extra Options”import { createTsdownConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownConfig({ entry: { index: 'src/index.ts', utils: 'src/utils/index.ts' }, deps: { neverBundle: ['vitest'] }, minify: true, sourcemap: true,});CLI Package
Section titled “CLI Package”import { createTsdownCliConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownCliConfig();// Output: dist/cli.js with a #!/usr/bin/env node shebangCLI with Library API
Section titled “CLI with Library API”import { createTsdownCliConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownCliConfig({ entry: { cli: 'src/cli.ts', index: 'src/index.ts' }, shebang: 'cli', // Only the cli entry gets a shebang deps: { neverBundle: ['commander'] },});📋 Configuration Details
Section titled “📋 Configuration Details”Defaults
Section titled “Defaults”| Setting | Default | Description |
|---|---|---|
entry |
['src/index.ts'] |
Entry point(s) |
format |
['cjs', 'esm'] |
Dual publishing |
dts |
true |
Generate declarations |
clean |
true |
Clean output before build |
outDir |
'dist' |
Output directory |
fixedExtension |
false |
.js/.cjs by package.json type, not .mjs |
checks |
{ legacyCjs: false } |
Silences tsdown’s CommonJS warning (dual is on purpose) |
target |
From engines.node |
Inferred by tsdown; without engines.node there is no syntax lowering |
createTsdownConfig(options?): UserConfig
Section titled “createTsdownConfig(options?): UserConfig”| Option | Type | Default | Description |
|---|---|---|---|
entry |
string[] | Record<string, string> |
['src/index.ts'] |
Entry point files |
format |
('cjs' | 'esm' | 'iife' | 'umd')[] |
['cjs', 'esm'] |
Output formats |
dts |
boolean | DtsOptions |
true |
Declaration files (or tsdown dts options) |
clean |
boolean |
true |
Clean output before build |
outDir |
string |
'dist' |
Output directory |
external |
(string | RegExp)[] |
[] |
Deprecated: use deps.neverBundle |
options |
AdditionalOptions |
{} |
Deprecated: pass tsdown options at top level |
Any other tsdown option is accepted at the top level.
createTsdownCliConfig(options?): UserConfig
Section titled “createTsdownCliConfig(options?): UserConfig”Same options, with format defaulting to ['esm'] and entry to { cli: 'src/cli.ts' }, plus:
| Option | Type | Default | Description |
|---|---|---|---|
target |
NodeTarget |
From engines.node |
Node.js target version |
shebang |
boolean | string | string[] |
true |
Shebang on all entries (true) or the named ones |
Shebang names are output names without extension (cli for dist/cli.js). An entry whose source already starts with a shebang keeps its own; no second one is added.
Conflicts and Deprecations
Section titled “Conflicts and Deprecations”externalmaps todeps.neverBundle; passing both throwsTsdownConfigError- Setting the same key at the top level and in
optionsthrowsTsdownConfigError checks: { legacyCjs: true }brings back the CommonJS warning;fixedExtension: trueemits.mjs/.cjs(update yourexportsmap)
Exports
Section titled “Exports”createTsdownConfig,createTsdownCliConfig- HelpersBASE_DEFAULTS,CLI_DEFAULTS- Default valuesTsdownConfigError- Thrown on conflicting optionsTsdownConfigOptions,TsdownCliConfigOptions,EntryConfig,NodeTarget,OutputFormat,DepsOptions- TypesAdditionalOptions,AdditionalCliOptions- Types of the deprecatedoptionsfieldUserConfig- Re-exported from tsdown, also asOptions
Differences from @jmlweb/tsup-config-base
Section titled “Differences from @jmlweb/tsup-config-base”| Area | tsup-config-base | tsdown-config-base |
|---|---|---|
| Helpers | createTsupConfig, createTsupCliConfig |
createTsdownConfig, createTsdownCliConfig |
| Config file | tsup.config.ts |
tsdown.config.ts |
| Return type | Options (Options[] for selective shebang) |
Always one UserConfig |
external |
tsup’s external |
Deprecated alias of deps.neverBundle |
| Selective shebang | Split configs | One config with a per-file banner |
format |
cjs, esm, iife |
Adds umd |
splitting, bundle |
In options |
Not available; use unbundle: true instead of bundle: false |
noExternal |
In options |
deps.alwaysBundle |
| Declarations | rollup-plugin-dts | tsc, tsgo or Oxc |
| Node.js | >= 22.12.0 | ^22.18.0 || ^24.11.0 || >=26.0.0 (tsdown’s requirement) |
Declaration Files and TypeScript 7
Section titled “Declaration Files and TypeScript 7”tsdown picks the declaration generator automatically (verified with tsdown 0.23.0):
| Project setup | Generator | Needs the TypeScript compiler API? |
|---|---|---|
typescript 5.x / 6.x installed |
tsc |
Yes |
typescript 7.x installed |
tsgo |
No: native binary (experimental in tsdown) |
isolatedDeclarations: true in tsconfig.json |
oxc |
No: works without typescript (needs explicit export types) |
| Neither | - | Build fails: TypeScript is not installed |
Force one with dts: { generator: 'oxc' } (or 'tsc' / 'tsgo').
> ⚠️ Much of the toolchain does not support TypeScript 7 yet (typescript-eslint <6.1.0, ts-jest <7, @astrojs/check ^5 || ^6). Stay on TypeScript 6 if you use them.
🎯 When to Use
Section titled “🎯 When to Use”- ✅ Building a TypeScript library with dual CJS/ESM output and declarations
- ✅ Migrating from
@jmlweb/tsup-config-basewithout changing published file names - ✅ Declaration generation ready for TypeScript 7
- ✅ CLI packages with shebang injection
Staying on tsup? @jmlweb/tsup-config-base still works but is deprecated and unmaintained.
🔧 Extending the Configuration
Section titled “🔧 Extending the Configuration”import { createTsdownConfig } from '@jmlweb/tsdown-config-base';
export default createTsdownConfig({ format: ['esm'], // ESM only outDir: 'build', fixedExtension: true, // Emit .mjs/.d.mts unbundle: true, // Mirror src/ instead of bundling deps: { neverBundle: ['eslint'], alwaysBundle: ['tiny-utils'], },});tsdown already externalizes dependencies, peerDependencies and optionalDependencies; deps.neverBundle covers anything else.
📝 Usage with Scripts
Section titled “📝 Usage with Scripts”{ "scripts": { "build": "tsdown", "clean": "rm -rf dist", "prepublishOnly": "npm run build" }}npm run build # Build the packagenpm run clean # Clean build output📋 Requirements
Section titled “📋 Requirements”- Node.js ^22.18.0, ^24.11.0 or >=26.0.0
- tsdown ~0.23.0
- TypeScript 5.x, 6.x or 7.x (optional with
isolatedDeclarations)
📦 Peer Dependencies
Section titled “📦 Peer Dependencies”tsdown(~0.23.0)
tsdown is pre-1.0 and ships breaking changes in minor releases, so the range allows patch updates only. It is widened as tsdown releases are verified.
🔗 Related Packages
Section titled “🔗 Related Packages”@jmlweb/tsup-config-base- Deprecated tsup equivalent@jmlweb/tsconfig-base- TypeScript configuration@jmlweb/oxlint-config-base- Oxlint config for TypeScript projects@jmlweb/vitest-config- Vitest configuration- tsdown - Library bundler powered by Rolldown
🔄 Migration Guide
Section titled “🔄 Migration Guide”From @jmlweb/tsup-config-base
Section titled “From @jmlweb/tsup-config-base”-
Install
@jmlweb/tsdown-config-baseandtsdown; remove@jmlweb/tsup-config-baseandtsup -
Rename
tsup.config.tstotsdown.config.tsand change thebuildscript fromtsuptotsdown -
Change the import and helper name, and move
externaltodeps.neverBundle(externalstill works, deprecated):// Beforeimport { createTsupConfig } from '@jmlweb/tsup-config-base';export default createTsupConfig({ external: ['eslint'] });// Afterimport { createTsdownConfig } from '@jmlweb/tsdown-config-base';export default createTsdownConfig({deps: { neverBundle: ['eslint'] },}); -
Move everything from
optionsto the top level, and replace tsup-only options (splitting,bundle,noExternal,esbuildPlugins, …) using Differences -
Build and compare
dist/with the previous output
See the Changelog for version history.
📄 License
Section titled “📄 License”MIT