
Tsdown
- 19 installs
- 951 repo stars
- Updated July 31, 2026
- sanity-io/next-sanity
This is a copy of tsdown by antfu - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks during AI-assisted development.
About
tsdown is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- tsdown
- AI & Agent Building
- AI-coding skill
Tsdown by the numbers
- 19 all-time installs (skills.sh)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sanity-io/next-sanity --skill tsdownAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 951 |
| Last updated | July 31, 2026 |
| Repository | sanity-io/next-sanity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
tsdown - The Elegant Library Bundler
Blazing-fast bundler for TypeScript/JavaScript libraries powered by Rolldown and Oxc.
When to Use
- Building TypeScript/JavaScript libraries for npm
- Generating TypeScript declaration files (.d.ts)
- Bundling for multiple formats (ESM, CJS, IIFE, UMD)
- Optimizing bundles with tree shaking and minification
- Migrating from tsup with minimal changes
- Building React, Vue, Solid, or Svelte component libraries
Quick Start
# Install
pnpm add -D tsdown
# Basic usage
npx tsdown
# With config file
npx tsdown --config tsdown.config.ts
# Watch mode
npx tsdown --watch
# Migrate from tsup
npx tsdown-migrateBasic Configuration
import {defineConfig} from 'tsdown'
export default defineConfig({
entry: ['./src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
})Core References
| Topic | Description | Reference |
|---|---|---|
| Getting Started | Installation, first bundle, CLI basics | guide-getting-started |
| Configuration File | Config file formats, multiple configs, workspace | option-config-file |
| CLI Reference | All CLI commands and options | reference-cli |
| Migrate from tsup | Migration guide and compatibility notes | guide-migrate-from-tsup |
| Plugins | Rolldown, Rollup, Unplugin support | advanced-plugins |
| Hooks | Lifecycle hooks for custom logic | advanced-hooks |
| Programmatic API | Build from Node.js scripts | advanced-programmatic |
| Rolldown Options | Pass options directly to Rolldown | advanced-rolldown-options |
| CI Environment | CI detection, 'ci-only' / 'local-only' values | advanced-ci |
Build Options
| Option | Usage | Reference |
|---|---|---|
| Entry points | entry: ['src/*.ts', '!**/*.test.ts'] | option-entry |
| Output formats | format: ['esm', 'cjs', 'iife', 'umd'] | option-output-format |
| Output directory | outDir: 'dist', outExtensions | option-output-directory |
| Type declarations | dts: true, dts: { sourcemap, compilerOptions, vue } | option-dts |
| Target environment | target: 'es2020', target: 'esnext' | option-target |
| Platform | platform: 'node', platform: 'browser' | option-platform |
| Tree shaking | treeshake: true, custom options | option-tree-shaking |
| Minification | minify: true, minify: 'dce-only' | option-minification |
| Source maps | sourcemap: true, 'inline', 'hidden' | option-sourcemap |
| Watch mode | watch: true, watch options | option-watch-mode |
| Cleaning | clean: true, clean patterns | option-cleaning |
| Log level | logLevel: 'silent', failOnWarn: false | option-log-level |
Dependency Handling
| Feature | Usage | Reference |
|---|---|---|
| Never bundle | deps: { neverBundle: ['react', /^@myorg\//] } | option-dependencies |
| Always bundle | deps: { alwaysBundle: ['dep-to-bundle'] } | option-dependencies |
| Only bundle | deps: { onlyBundle: ['cac', 'bumpp'] } - Whitelist | option-dependencies |
| Skip node_modules | deps: { skipNodeModulesBundle: true } | option-dependencies |
| Auto external | Automatic peer/dependency externalization | option-dependencies |
Output Enhancement
| Feature | Usage | Reference |
|---|---|---|
| Shims | shims: true - Add ESM/CJS compatibility | option-shims |
| CJS default | cjsDefault: true (default) / false | option-cjs-default |
| Package exports | exports: true - Auto-generate exports field | option-package-exports |
| CSS handling | [experimental] css: { ... } — full pipeline with preprocessors, Lightning CSS, PostCSS, code splitting; requires @tsdown/css | option-css |
| CSS inject | css: { inject: true } — preserve CSS imports in JS output | option-css |
| Unbundle mode | unbundle: true - Preserve directory structure | option-unbundle |
| Root directory | root: 'src' - Control output directory mapping | option-root |
| Executable | [experimental] exe: true - Bundle as standalone executable, cross-platform via @tsdown/exe | option-exe |
| Package validation | publint: true, attw: true - Validate package | option-lint |
Framework & Runtime Support
| Framework | Guide | Reference |
|---|---|---|
| React | JSX transform, React Compiler | recipe-react |
| Vue | SFC support, JSX | recipe-vue |
| Solid | SolidJS JSX transform | recipe-solid |
| Svelte | Svelte component libraries (source distribution recommended) | recipe-svelte |
| WASM | WebAssembly modules via rolldown-plugin-wasm | recipe-wasm |
Common Patterns
Basic Library Bundle
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
})Multiple Entry Points
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils.ts',
cli: 'src/cli.ts',
},
format: ['esm', 'cjs'],
dts: true,
})Browser Library (IIFE/UMD)
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'],
globalName: 'MyLib',
platform: 'browser',
minify: true,
})React Component Library
export default defineConfig({
entry: ['src/index.tsx'],
format: ['esm', 'cjs'],
dts: true,
deps: {
neverBundle: ['react', 'react-dom'],
},
inputOptions: {
jsx: {runtime: 'automatic'},
},
})Preserve Directory Structure
export default defineConfig({
entry: ['src/**/*.ts', '!**/*.test.ts'],
unbundle: true, // Preserve file structure
format: ['esm'],
dts: true,
})CI-Aware Configuration
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
failOnWarn: 'ci-only', // opt-in: fail on warnings in CI
publint: 'ci-only',
attw: 'ci-only',
})WASM Support
import {wasm} from 'rolldown-plugin-wasm'
import {defineConfig} from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [wasm()],
})Library with CSS and Sass
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
target: 'chrome100',
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "src/styles/variables" as *;`,
},
},
},
})Standalone Executable
export default defineConfig({
entry: ['src/cli.ts'],
exe: true,
})Cross-Platform Executable (requires @tsdown/exe)
export default defineConfig({
entry: ['src/cli.ts'],
exe: {
targets: [
{platform: 'linux', arch: 'x64', nodeVersion: '25.7.0'},
{platform: 'darwin', arch: 'arm64', nodeVersion: '25.7.0'},
{platform: 'win', arch: 'x64', nodeVersion: '25.7.0'},
],
},
})Advanced with Hooks
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
hooks: {
'build:before': async (context) => {
console.log('Building...')
},
'build:done': async (context) => {
console.log('Build complete!')
},
},
})Configuration Features
Multiple Configs
Export an array for multiple build configurations:
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
},
{
entry: ['src/cli.ts'],
format: ['esm'],
platform: 'node',
},
])Conditional Config
Use functions for dynamic configuration:
export default defineConfig((options) => {
const isDev = options.watch
return {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: !isDev,
sourcemap: isDev,
}
})Workspace/Monorepo
Use glob patterns to build multiple packages:
export default defineConfig({
workspace: 'packages/*',
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})CLI Quick Reference
# Basic commands
tsdown # Build once
tsdown --watch # Watch mode
tsdown --config custom.ts # Custom config
npx tsdown-migrate # Migrate from tsup
# Output options
tsdown --format esm,cjs # Multiple formats
tsdown -d lib # Custom output directory (--out-dir)
tsdown --minify # Enable minification
tsdown --dts # Generate declarations
tsdown --exe # Bundle as standalone executable
tsdown --unbundle # Bundleless mode
# Entry options
tsdown src/index.ts # Single entry
tsdown src/*.ts # Glob patterns
tsdown src/a.ts src/b.ts # Multiple entries
# Workspace / Monorepo
tsdown -W # Enable workspace mode
tsdown -W -F my-package # Filter specific package
tsdown --filter /^pkg-/ # Filter by regex
# Development
tsdown --watch # Watch mode
tsdown --sourcemap # Generate source maps
tsdown --clean # Clean output directory
tsdown --from-vite # Reuse Vite config
tsdown --tsconfig tsconfig.build.json # Custom tsconfigBest Practices
1. Always generate type declarations for TypeScript libraries:
{
dts: true
}2. Externalize dependencies to avoid bundling unnecessary code:
{
deps: {
neverBundle: [/^react/, /^@myorg\//]
}
}3. Use tree shaking for optimal bundle size:
{
treeshake: true
}4. Enable minification for production builds:
{
minify: true
}5. Add shims for better ESM/CJS compatibility:
{
shims: true
} // Adds __dirname, __filename, etc.6. Auto-generate package.json exports:
{
exports: true
} // Creates proper exports field7. Use watch mode during development:
tsdown --watch8. Preserve structure for utilities with many files:
{
unbundle: true
} // Keep directory structure9. Validate packages in CI before publishing:
{ publint: 'ci-only', attw: 'ci-only' }Resources
- Documentation: https://tsdown.dev
- GitHub: https://github.com/rolldown/tsdown
- Rolldown: https://rolldown.rs
- Migration Guide: https://tsdown.dev/guide/migrate-from-tsup
The MIT License (MIT)
Copyright (c) 2025-present VoidZero Inc. & Contributors Copyright (c) 2024 Kevin Deng (https://github.com/sxzz)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
tsdown Skills for Claude Code
Agent skills that help Claude Code understand and work with tsdown, the elegant library bundler.
Installation
npx skills add rolldown/tsdownThis will add the tsdown skill to your Claude Code configuration.
What's Included
The tsdown skill provides Claude Code with knowledge about:
- Core Concepts - What tsdown is, why use it, key features
- Configuration - Config file formats, options, multiple configs, workspace support
- Build Options - Entry points, output formats, type declarations, targets
- Dependency Handling - External/inline dependencies, auto-externalization
- Output Enhancement - Shims, CJS defaults, package exports
- Framework Support - React, Vue, Solid, Svelte integration
- Advanced Features - Plugins, hooks, programmatic API, Rolldown options
- CLI Commands - All CLI options and usage patterns
- Migration - Migrating from tsup to tsdown
Usage
Once installed, Claude Code will automatically use tsdown knowledge when:
- Building TypeScript/JavaScript libraries
- Configuring bundlers for library projects
- Setting up type declaration generation
- Working with multi-format builds (ESM, CJS, IIFE, UMD)
- Migrating from tsup
- Building framework component libraries
Example Prompts
Set up tsdown to build my TypeScript library with ESM and CJS formatsConfigure tsdown to generate type declarations and bundle for browsersAdd React support to my tsdown config with Fast RefreshHelp me migrate from tsup to tsdownSet up a monorepo build with tsdown workspace supportDocumentation
License
MIT
Benchmark
tsdown delivers exceptional performance:
- ~2x faster than tsup for standard builds
- Up to 8x faster for TypeScript declaration generation
For detailed comparisons, see bundler-benchmark.
CI Environment Support
Automatically detect CI environments and toggle features based on local vs CI builds.
Overview
tsdown uses the `is-in-ci` package to detect CI environments. This covers GitHub Actions, GitLab CI, Jenkins, CircleCI, Travis CI, and more.
CI-Aware Values
Several options accept CI-aware string values:
| Value | Behavior |
|---|---|
true | Always enabled |
false | Always disabled |
'ci-only' | Enabled only in CI, disabled locally |
'local-only' | Enabled only locally, disabled in CI |
Supported Options
These options accept CI-aware values:
dts- TypeScript declaration file generationpublint- Package lint validationattw- "Are the types wrong" validationreport- Bundle size reportingexports- Auto-generatepackage.jsonexportsunused- Unused dependency checkdevtools- DevTools integrationfailOnWarn- Fail on warnings (defaults tofalse)
Usage
String Form
export default defineConfig({
dts: 'local-only', // Skip DTS in CI for faster builds
publint: 'ci-only', // Only run publint in CI
failOnWarn: 'ci-only', // Fail on warnings in CI only (opt-in)
})Object Form
When an option takes a configuration object, set enabled to a CI-aware value:
export default defineConfig({
publint: {
enabled: 'ci-only',
level: 'error',
},
attw: {
enabled: 'ci-only',
profile: 'node16',
},
})Config Function
The config function receives a ci boolean in its context:
export default defineConfig((_, {ci}) => ({
minify: ci,
sourcemap: !ci,
}))Typical CI Configuration
export default defineConfig({
entry: 'src/index.ts',
format: ['esm', 'cjs'],
dts: true,
failOnWarn: 'ci-only',
publint: 'ci-only',
attw: 'ci-only',
})Related Options
- Package Validation - publint and attw configuration
- Log Level -
failOnWarnoption details
Lifecycle Hooks
Extend the build process with lifecycle hooks.
Overview
Hooks provide a way to inject custom logic at specific stages of the build lifecycle. Inspired by unbuild.
Recommendation: Use plugins for most extensions. Use hooks for simple custom tasks or Rolldown plugin injection.
Usage Patterns
Object Syntax
export default defineConfig({
entry: ['src/index.ts'],
hooks: {
'build:prepare': async (context) => {
console.log('Build starting...')
},
'build:done': async (context) => {
console.log('Build complete!')
},
},
})Function Syntax
export default defineConfig({
entry: ['src/index.ts'],
hooks(hooks) {
hooks.hook('build:prepare', () => {
console.log('Preparing build...')
})
hooks.hook('build:before', (context) => {
console.log(`Building format: ${context.format}`)
})
},
})Available Hooks
build:prepare
Called before the build process starts.
When: Once per build session
Context:
{
options: ResolvedConfig,
hooks: Hookable
}Use cases:
- Setup tasks
- Validation
- Environment preparation
Example:
hooks: {
'build:prepare': async (context) => {
console.log('Starting build for:', context.options.entry)
await cleanOldFiles()
},
}build:before
Called before each Rolldown build.
When: Once per format (ESM, CJS, etc.)
Context:
{
options: ResolvedConfig,
buildOptions: BuildOptions,
hooks: Hookable
}Use cases:
- Modify build options per format
- Inject plugins dynamically
- Format-specific setup
Example:
hooks: {
'build:before': async (context) => {
console.log(`Building ${context.buildOptions.format} format...`)
// Add format-specific plugin
if (context.buildOptions.format === 'iife') {
context.buildOptions.plugins.push(browserPlugin())
}
},
}build:done
Called after the build completes.
When: Once per build session
Context:
{
options: ResolvedConfig,
chunks: RolldownChunk[],
hooks: Hookable
}Use cases:
- Post-processing
- Asset copying
- Notifications
- Deployment
Example:
hooks: {
'build:done': async (context) => {
console.log(`Built ${context.chunks.length} chunks`)
// Copy additional files
await copyAssets()
// Send notification
notifyBuildComplete()
},
}Common Patterns
Build Notifications
export default defineConfig({
hooks: {
'build:prepare': () => {
console.log('🚀 Starting build...')
},
'build:done': (context) => {
const size = context.chunks.reduce((sum, c) => sum + c.code.length, 0)
console.log(`✅ Build complete! Total size: ${size} bytes`)
},
},
})Conditional Plugin Injection
export default defineConfig({
hooks(hooks) {
hooks.hook('build:before', (context) => {
// Add minification only for production
if (process.env.NODE_ENV === 'production') {
context.buildOptions.plugins.push(minifyPlugin())
}
})
},
})Custom File Copy
import {copyFile} from 'fs/promises'
export default defineConfig({
hooks: {
'build:done': async (context) => {
// Copy README to dist
await copyFile('README.md', `${context.options.outDir}/README.md`)
},
},
})Build Metrics
export default defineConfig({
hooks: {
'build:prepare': (context) => {
context.startTime = Date.now()
},
'build:done': (context) => {
const duration = Date.now() - context.startTime
console.log(`Build took ${duration}ms`)
// Log chunk sizes
context.chunks.forEach((chunk) => {
console.log(`${chunk.fileName}: ${chunk.code.length} bytes`)
})
},
},
})Format-Specific Logic
export default defineConfig({
format: ['esm', 'cjs', 'iife'],
hooks: {
'build:before': (context) => {
const format = context.buildOptions.format
if (format === 'iife') {
// Browser-specific setup
context.buildOptions.globalName = 'MyLib'
} else if (format === 'cjs') {
// Node-specific setup
context.buildOptions.platform = 'node'
}
},
},
})Deployment Hook
export default defineConfig({
hooks: {
'build:done': async (context) => {
if (process.env.DEPLOY === 'true') {
console.log('Deploying to CDN...')
await deployToCDN(context.options.outDir)
}
},
},
})Advanced Usage
Multiple Hooks
export default defineConfig({
hooks(hooks) {
// Register multiple hooks
hooks.hook('build:prepare', setupEnvironment)
hooks.hook('build:prepare', validateConfig)
hooks.hook('build:before', injectPlugins)
hooks.hook('build:before', logFormat)
hooks.hook('build:done', generateManifest)
hooks.hook('build:done', notifyComplete)
},
})Async Hooks
export default defineConfig({
hooks: {
'build:prepare': async (context) => {
await fetchRemoteConfig()
await initializeDatabase()
},
'build:done': async (context) => {
await uploadToS3(context.chunks)
await invalidateCDN()
},
},
})Error Handling
export default defineConfig({
hooks: {
'build:done': async (context) => {
try {
await riskyOperation()
} catch (error) {
console.error('Hook failed:', error)
// Don't throw - allow build to complete
}
},
},
})Hookable API
tsdown uses hookable for hooks. Additional methods:
export default defineConfig({
hooks(hooks) {
// Register hook
hooks.hook('build:done', handler)
// Register hook once
hooks.hookOnce('build:prepare', handler)
// Remove hook
hooks.removeHook('build:done', handler)
// Clear all hooks for event
hooks.removeHooks('build:done')
// Call hooks manually
await hooks.callHook('build:done', context)
},
})Tips
1. Use plugins for most extensions 2. Hooks for simple tasks like notifications or file copying 3. Async hooks supported for I/O operations 4. Don't throw errors unless you want to fail the build 5. Context is mutable in build:before for advanced use cases 6. Multiple hooks allowed for the same event
Troubleshooting
Hook Not Called
- Verify hook name is correct
- Check hook is registered in config
- Ensure async hooks are awaited
Build Fails in Hook
- Add try/catch for error handling
- Don't throw unless intentional
- Log errors for debugging
Context Undefined
- Check which hook you're using
- Verify context properties available for that hook
Related
- Plugins - Plugin system
- Rolldown Options - Build options
- Watch Mode - Development workflow
Plugins
Extend tsdown with plugins from multiple ecosystems.
Overview
tsdown, built on Rolldown, supports plugins from multiple ecosystems to extend and customize the bundling process.
Supported Ecosystems
1. Rolldown Plugins
Native plugins designed for Rolldown:
import RolldownPlugin from 'rolldown-plugin-something'
export default defineConfig({
plugins: [RolldownPlugin()],
})Compatibility: ✅ Full support
2. Unplugin
Universal plugins that work across bundlers:
import UnpluginPlugin from 'unplugin-something'
export default defineConfig({
plugins: [UnpluginPlugin.rolldown()],
})Compatibility: ✅ Most unplugin-\* plugins work
Examples:
unplugin-vue-componentsunplugin-auto-importunplugin-icons
3. Rollup Plugins
Most Rollup plugins work with tsdown:
import RollupPlugin from '@rollup/plugin-something'
export default defineConfig({
plugins: [RollupPlugin()],
})Compatibility: ✅ High compatibility
Type Issues: May cause TypeScript errors - use type casting:
import RollupPlugin from 'rollup-plugin-something'
export default defineConfig({
plugins: [
// @ts-expect-error Rollup plugin type mismatch
RollupPlugin(),
// Or cast to any
RollupPlugin() as any,
],
})4. Vite Plugins
Some Vite plugins may work:
import VitePlugin from 'vite-plugin-something'
export default defineConfig({
plugins: [
// @ts-expect-error Vite plugin type mismatch
VitePlugin(),
],
})Compatibility: ⚠️ Limited - only if not using Vite-specific APIs
Note: Improved support planned for future releases.
Usage
Basic Plugin Usage
import {defineConfig} from 'tsdown'
import SomePlugin from 'some-plugin'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [SomePlugin()],
})Multiple Plugins
import PluginA from 'plugin-a'
import PluginB from 'plugin-b'
import PluginC from 'plugin-c'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [PluginA(), PluginB({option: true}), PluginC()],
})Conditional Plugins
export default defineConfig((options) => ({
entry: ['src/index.ts'],
plugins: [SomePlugin(), options.watch && DevPlugin(), !options.watch && ProdPlugin()].filter(
Boolean,
),
}))Common Plugin Patterns
JSON Import
import json from '@rollup/plugin-json'
export default defineConfig({
plugins: [json()],
})Node Resolve
import {nodeResolve} from '@rollup/plugin-node-resolve'
export default defineConfig({
plugins: [nodeResolve()],
})CommonJS
import commonjs from '@rollup/plugin-commonjs'
export default defineConfig({
plugins: [commonjs()],
})Replace
import replace from '@rollup/plugin-replace'
export default defineConfig({
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
'__VERSION__': JSON.stringify('1.0.0'),
}),
],
})Auto Import
import AutoImport from 'unplugin-auto-import/rolldown'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router'],
dts: 'src/auto-imports.d.ts',
}),
],
})Vue Components
import Components from 'unplugin-vue-components/rolldown'
export default defineConfig({
plugins: [
Components({
dts: 'src/components.d.ts',
}),
],
})Framework-Specific Plugins
React
import react from '@vitejs/plugin-react'
export default defineConfig({
entry: ['src/index.tsx'],
plugins: [
// @ts-expect-error Vite plugin
react(),
],
})Vue
import vue from '@vitejs/plugin-vue'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [
// @ts-expect-error Vite plugin
vue(),
],
})Solid
import solid from 'vite-plugin-solid'
export default defineConfig({
entry: ['src/index.tsx'],
plugins: [
// @ts-expect-error Vite plugin
solid(),
],
})Svelte
import {svelte} from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [
// @ts-expect-error Vite plugin
svelte(),
],
})Writing Custom Plugins
Follow Rolldown's plugin development guide:
Basic Plugin Structure
import type {Plugin} from 'rolldown'
function myPlugin(): Plugin {
return {
name: 'my-plugin',
// Transform hook
transform(code, id) {
if (id.endsWith('.custom')) {
return {
code: transformCode(code),
map: null,
}
}
},
// Other hooks...
}
}Using Custom Plugin
import {myPlugin} from './my-plugin'
export default defineConfig({
plugins: [myPlugin()],
})Plugin Configuration
Plugin-Specific Options
Refer to each plugin's documentation for configuration options.
Plugin Order
Plugins run in the order they're defined:
export default defineConfig({
plugins: [
PluginA(), // Runs first
PluginB(), // Runs second
PluginC(), // Runs last
],
})Troubleshooting
Type Errors with Rollup/Vite Plugins
Use type casting:
plugins: [
// Option 1: @ts-expect-error
// @ts-expect-error Plugin type mismatch
SomePlugin(),
// Option 2: as any
SomePlugin() as any,
]Plugin Not Working
1. Check compatibility - Verify plugin supports your bundler 2. Read documentation - Follow plugin's setup instructions 3. Check plugin order - Some plugins depend on execution order 4. Enable debug mode - Use --debug flag
Vite Plugin Fails
Vite plugins may rely on Vite-specific APIs:
1. Find Rollup equivalent - Look for Rollup version of plugin 2. Use Unplugin version - Check for unplugin-* alternative 3. Wait for support - Vite plugin support improving
Resources
Tips
1. Prefer Rolldown plugins for best compatibility 2. Use Unplugin for cross-bundler support 3. Cast types for Rollup/Vite plugins 4. Test thoroughly when using cross-ecosystem plugins 5. Check plugin docs for specific configuration 6. Write custom plugins for unique needs
Related
- Hooks - Lifecycle hooks
- Rolldown Options - Advanced Rolldown config
- React Recipe - React setup with plugins
- Vue Recipe - Vue setup with plugins
Programmatic Usage
Use tsdown from JavaScript/TypeScript code.
Overview
tsdown can be imported and used programmatically in your Node.js scripts, custom build tools, or automation workflows.
Basic Usage
Simple Build
import {build} from 'tsdown'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})With Options
import {build} from 'tsdown'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist',
dts: true,
minify: true,
sourcemap: true,
clean: true,
})API Reference
build()
Main function to run a build.
import {build} from 'tsdown'
await build(options)Parameters:
options- Build configuration object (same as config file)
Returns:
Promise<void>- Resolves when build completes
Throws:
- Build errors if compilation fails
Configuration Object
All config file options are available:
import {build, defineConfig} from 'tsdown'
const config = defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
minify: true,
sourcemap: true,
deps: {
neverBundle: ['react', 'react-dom'],
},
plugins: [
/* plugins */
],
hooks: {
'build:done': async () => {
console.log('Build complete!')
},
},
})
await build(config)See Config Reference for all options.
Common Patterns
Custom Build Script
// scripts/build.ts
import {build} from 'tsdown'
async function main() {
console.log('Building library...')
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
})
console.log('Build complete!')
}
main().catch(console.error)Run with:
tsx scripts/build.tsMultiple Builds
import {build} from 'tsdown'
// Build main library
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist',
dts: true,
})
// Build CLI tool
await build({
entry: ['src/cli.ts'],
format: ['esm'],
outDir: 'dist/bin',
platform: 'node',
shims: true,
})Conditional Build
import {build} from 'tsdown'
const isDev = process.env.NODE_ENV === 'development'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: !isDev,
sourcemap: isDev,
clean: !isDev,
})With Error Handling
import {build} from 'tsdown'
try {
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})
console.log('✅ Build successful')
} catch (error) {
console.error('❌ Build failed:', error)
process.exit(1)
}Automated Workflow
import {build} from 'tsdown'
import {execSync} from 'child_process'
async function release() {
// Clean
console.log('Cleaning...')
execSync('rm -rf dist')
// Build
console.log('Building...')
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
minify: true,
})
// Test
console.log('Testing...')
execSync('npm test')
// Publish
console.log('Publishing...')
execSync('npm publish')
}
release().catch(console.error)Build with Post-Processing
import {build} from 'tsdown'
import {copyFileSync} from 'fs'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
hooks: {
'build:done': async () => {
// Copy additional files
copyFileSync('README.md', 'dist/README.md')
copyFileSync('LICENSE', 'dist/LICENSE')
console.log('Copied additional files')
},
},
})Watch Mode
Unfortunately, watch mode is not directly exposed in the programmatic API. Use the CLI for watch mode:
// Use CLI for watch mode
import {spawn} from 'child_process'
spawn('tsdown', ['--watch'], {
stdio: 'inherit',
shell: true,
})Integration Examples
With Task Runner
// gulpfile.js
import {build} from 'tsdown'
import gulp from 'gulp'
gulp.task('build', async () => {
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})
})
gulp.task('watch', () => {
return gulp.watch('src/**/*.ts', gulp.series('build'))
})With Custom CLI
// scripts/cli.ts
import {build} from 'tsdown'
import {Command} from 'commander'
const program = new Command()
program
.command('build')
.option('--prod', 'Production build')
.action(async (options) => {
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: options.prod,
sourcemap: !options.prod,
})
})
program.parse()With CI/CD
// .github/scripts/build.ts
import {build} from 'tsdown'
const isCI = process.env.CI === 'true'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
minify: isCI,
clean: true,
})
// Upload to artifact storage
if (isCI) {
// Upload dist/ to S3, etc.
}TypeScript Support
// scripts/build.ts
import {build, type UserConfig} from 'tsdown'
const config: UserConfig = {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
}
await build(config)Tips
1. Use TypeScript for type safety 2. Handle errors properly 3. Use hooks for custom logic 4. Log progress for visibility 5. Use CLI for watch mode 6. Exit on error in scripts
Troubleshooting
Import Errors
Ensure tsdown is installed:
pnpm add -D tsdownType Errors
Import types:
import type {UserConfig} from 'tsdown'Build Fails Silently
Add error handling:
try {
await build(config)
} catch (error) {
console.error(error)
process.exit(1)
}Options Not Working
Check spelling and types:
// ✅ Correct
{
format: ['esm', 'cjs']
}
// ❌ Wrong
{
formats: ['esm', 'cjs']
}Related
- Config File - Configuration options
- Hooks - Lifecycle hooks
- CLI - Command-line interface
- Plugins - Plugin system
Customizing Rolldown Options
Pass options directly to the underlying Rolldown bundler.
Overview
tsdown uses Rolldown as its core bundling engine. You can override Rolldown's input and output options directly for fine-grained control.
Warning: You should be familiar with Rolldown's behavior before overriding options. Refer to the Rolldown Config Options documentation.
Input Options
Using an Object
export default defineConfig({
inputOptions: {
cwd: './custom-directory',
},
})Using a Function
Dynamically modify options based on the output format:
export default defineConfig({
inputOptions(inputOptions, format) {
inputOptions.cwd = './custom-directory'
return inputOptions
},
})Output Options
Using an Object
export default defineConfig({
outputOptions: {
legalComments: 'inline',
},
})Using a Function
export default defineConfig({
outputOptions(outputOptions, format) {
if (format === 'esm') {
outputOptions.legalComments = 'inline'
}
return outputOptions
},
})Common Use Cases
Preserve Legal Comments
export default defineConfig({
entry: ['src/index.ts'],
outputOptions: {
legalComments: 'inline',
},
})Custom Working Directory
export default defineConfig({
entry: ['src/index.ts'],
inputOptions: {
cwd: './packages/my-lib',
},
})Format-Specific Options
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outputOptions(outputOptions, format) {
if (format === 'esm') {
outputOptions.legalComments = 'inline'
}
return outputOptions
},
})When to Use
- When tsdown doesn't expose a specific Rolldown option
- For format-specific Rolldown customizations
- For advanced bundling scenarios
Tips
1. Read Rolldown docs before overriding options 2. Use functions for format-specific customization 3. Test thoroughly when overriding defaults 4. Prefer tsdown options when available (e.g., use minify instead of setting it via outputOptions)
Related
- Plugins - Plugin system
- Hooks - Lifecycle hooks
- Config File - Configuration options
Getting Started
Quick guide to installing and using tsdown for the first time.
Installation
Install tsdown as a development dependency:
pnpm add -D tsdown
# Optionally install TypeScript if not using isolatedDeclarations
pnpm add -D typescriptRequirements:
- Node.js 20.19 or higher
- Experimental support for Deno and Bun
Quick Start Templates
Use create-tsdown CLI for instant setup:
pnpm create tsdown@latestProvides templates for:
- Pure TypeScript libraries
- React component libraries
- Vue component libraries
- Ready-to-use configurations
First Bundle
1. Create Source Files
// src/index.ts
import {hello} from './hello.ts'
hello()
// src/hello.ts
export function hello() {
console.log('Hello tsdown!')
}2. Create Config File
// tsdown.config.ts
import {defineConfig} from 'tsdown'
export default defineConfig({
entry: ['./src/index.ts'],
})3. Run Build
./node_modules/.bin/tsdownOutput: dist/index.mjs
4. Test Output
node dist/index.mjs
# Output: Hello tsdown!Add to npm Scripts
{
"scripts": {
"build": "tsdown"
}
}Run with:
pnpm buildCLI Commands
# Check version
tsdown --version
# View help
tsdown --help
# Build with watch mode
tsdown --watch
# Build with specific format
tsdown --format esm,cjs
# Generate type declarations
tsdown --dtsBasic Configurations
TypeScript Library (ESM + CJS)
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
})Browser Library (IIFE)
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'],
globalName: 'MyLib',
platform: 'browser',
minify: true,
})Multiple Entry Points
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils.ts',
cli: 'src/cli.ts',
},
format: ['esm', 'cjs'],
dts: true,
})Using Plugins
Add Rolldown, Rollup, or Unplugin plugins:
import SomePlugin from 'some-plugin'
export default defineConfig({
entry: ['src/index.ts'],
plugins: [SomePlugin()],
})Watch Mode
Enable automatic rebuilds on file changes:
tsdown --watch
# or
tsdown -wNext Steps
- Configure entry points with glob patterns
- Set up multiple output formats
- Enable type declaration generation
- Explore plugins for extended functionality
- Read migration guide if coming from tsup
Introduction
tsdown is _The Elegant Library Bundler_ — a fast, simple bundler for TypeScript and JavaScript libraries powered by Rolldown (Rust-based).
Why tsdown?
Built on Rolldown, tsdown provides a complete out-of-the-box solution for library authors:
- Simplified Configuration: Sensible defaults for library development, minimal boilerplate
- Library-Specific Features: Auto TypeScript declarations, multiple output formats, package validation
- Future-Ready: Official Rolldown project, foundation for Rolldown Vite's Library Mode
Plugin Ecosystem
Supports the full Rolldown plugin ecosystem plus most Rollup plugins. See Plugins.
What Can It Bundle?
- TypeScript/JavaScript:
.ts,.jswith modern syntax - TypeScript Declarations: Auto-generate
.d.tsfiles - Multiple Formats:
esm,cjs,iife,umd - Assets:
.json,.wasm, CSS files - Built-in tree shaking, minification, and source maps
Key Differences from Rolldown
tsdown wraps Rolldown with library-specific features:
- Auto-external dependencies from
package.json - DTS generation
package.jsonexports field generation- Watch mode with keyboard shortcuts
- CSS preprocessing pipeline
- Executable bundling (SEA)
Prior Arts
Inspired by: Rollup, esbuild, tsup, unbuild. Powered by Rolldown.
Related
- Getting Started - Installation and first build
- Migrate from tsup - Migration guide
Migrate from tsup
Migration guide for switching from tsup to tsdown.
Overview
tsdown is built on Rolldown (Rust-based) vs tsup's esbuild, providing faster and more powerful bundling while maintaining compatibility.
Automatic Migration
Single Package
npx tsdown-migrateMonorepo
# Using glob patterns
npx tsdown-migrate packages/*
# Multiple directories
npx tsdown-migrate packages/foo packages/barMigration Options
[...dirs]- Directories to migrate (supports globs)--dry-runor-d- Preview changes without modifying files
Important: Commit your changes before running migration.
Key Differences
Default Values
| Option | tsup | tsdown |
|---|---|---|
format | ['cjs'] | ['esm'] |
clean | false | true |
dts | false | Auto-enabled if types/typings in package.json |
target | Manual | Auto-read from engines.node in package.json |
New Features in tsdown
Node Protocol Control
export default defineConfig({
nodeProtocol: true, // Add node: prefix (fs → node:fs)
nodeProtocol: 'strip', // Remove node: prefix (node:fs → fs)
nodeProtocol: false, // Keep as-is (default)
})Better Workspace Support
export default defineConfig({
workspace: 'packages/*', // Build all packages
})Migration Checklist
1. Backup your code - Commit all changes 2. Run migration tool - npx tsdown-migrate 3. Review changes - Check modified config files 4. Update scripts - Change tsup to tsdown in package.json 5. Test build - Run pnpm build to verify 6. Adjust config - Fine-tune based on your needs
Common Migration Patterns
Basic Library
Before (tsup):
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
})After (tsdown):
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'], // ESM now default
dts: true,
clean: true, // Now enabled by default
})With Custom Target
Before (tsup):
export default defineConfig({
entry: ['src/index.ts'],
target: 'es2020',
})After (tsdown):
export default defineConfig({
entry: ['src/index.ts'],
// target auto-reads from package.json engines.node
// Or override explicitly:
target: 'es2020',
})CLI Scripts
Before (package.json):
{
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}After (package.json):
{
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch"
}
}Feature Compatibility
Supported tsup Features
Most tsup features are supported:
- ✅ Multiple entry points
- ✅ Multiple formats (ESM, CJS, IIFE, UMD)
- ✅ TypeScript declarations
- ✅ Source maps
- ✅ Minification
- ✅ Watch mode
- ✅ External dependencies
- ✅ Tree shaking
- ✅ Shims
- ✅ Plugins (Rollup compatible)
Missing Features
Some tsup features are not yet available. Check GitHub issues for status and request features.
Troubleshooting
Build Fails After Migration
1. Check Node.js version - Requires Node.js 20.19+ 2. Install TypeScript - Required for DTS generation 3. Review config changes - Ensure format and options are correct 4. Check dependencies - Verify all dependencies are installed
Different Output
- Format order - tsdown defaults to ESM first
- Clean behavior - tsdown cleans outDir by default
- Target - tsdown auto-detects from package.json
Performance Issues
tsdown should be faster than tsup. If not:
1. Enable isolatedDeclarations for faster DTS generation 2. Check for large dependencies being bundled 3. Use skipNodeModulesBundle if needed
Getting Help
- GitHub Issues - Report bugs or request features
- Documentation - Full documentation
- Migration Tool - Source code
Acknowledgements
tsdown is heavily inspired by tsup and incorporates parts of its codebase. Thanks to @egoist and the tsup community.
CJS Default Export
Control how default exports are handled in CommonJS output.
Overview
The cjsDefault option improves compatibility when generating CommonJS modules. When enabled (default), modules with only a single default export use module.exports = ... instead of exports.default = ....
Type
cjsDefault?: boolean // default: trueBasic Usage
Enabled (Default)
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs'],
cjsDefault: true, // default behavior
})Disabled
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs'],
cjsDefault: false,
})How It Works
With cjsDefault: true (Default)
When your module has only a single default export, tsdown transforms:
Source:
// src/index.ts
export default function greet() {
console.log('Hello, world!')
}Generated CJS:
// dist/index.cjs
function greet() {
console.log('Hello, world!')
}
module.exports = greetGenerated Declaration:
// dist/index.d.cts
declare function greet(): void
export = greetThis allows consumers to use const greet = require('your-module') directly.
With cjsDefault: false
The default export stays as exports.default:
// dist/index.cjs
function greet() {
console.log('Hello, world!')
}
exports.default = greetConsumers need require('your-module').default.
When to Disable
- When your module has both default and named exports
- When you need consistent
exports.defaultbehavior - When consumers always use ESM imports
Tips
1. Leave enabled for most libraries (default true) 2. Disable if you have both default and named exports and need consistent behavior 3. Test CJS consumers to verify compatibility
Related Options
- Output Format - Module formats
- Shims - ESM/CJS compatibility
Output Directory Cleaning
Control how the output directory is cleaned before builds.
Overview
By default, tsdown cleans the output directory before each build to remove stale files from previous builds.
Basic Usage
CLI
# Clean enabled (default)
tsdown
# Disable cleaning
tsdown --no-cleanConfig File
export default defineConfig({
entry: ['src/index.ts'],
clean: true, // Default
})Behavior
With Cleaning (Default)
Before each build:
1. All files in outDir are removed 2. Fresh build starts with empty directory 3. Only current build outputs remain
Benefits:
- No stale files
- Predictable output
- Clean slate each build
Without Cleaning
Build outputs are added to existing files:
export default defineConfig({
clean: false,
})Use when:
- Multiple builds to same directory
- Incremental builds
- Preserving other files
- Watch mode (faster rebuilds)
Common Patterns
Production Build
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
clean: true, // Ensure clean output
minify: true,
})Development Mode
export default defineConfig((options) => ({
entry: ['src/index.ts'],
clean: !options.watch, // Don't clean in watch mode
sourcemap: options.watch,
}))Multiple Builds
export default defineConfig([
{
entry: ['src/index.ts'],
outDir: 'dist',
clean: true, // Clean once
},
{
entry: ['src/cli.ts'],
outDir: 'dist',
clean: false, // Don't clean, add to same dir
},
])Monorepo Package
export default defineConfig({
workspace: 'packages/*',
entry: ['src/index.ts'],
clean: true, // Clean each package's dist
})Preserve Static Files
export default defineConfig({
entry: ['src/index.ts'],
clean: false, // Keep manually added files
outDir: 'dist',
})
// Manually copy files first
// Then run tsdown --no-cleanClean Patterns
Selective Cleaning
import {rmSync} from 'fs'
export default defineConfig({
clean: false, // Disable auto clean
hooks: {
'build:prepare': () => {
// Custom cleaning logic
rmSync('dist/*.js', {force: true})
// Keep other files
},
},
})Clean Specific Directories
export default defineConfig({
clean: false,
hooks: {
'build:prepare': async () => {
const {rm} = await import('fs/promises')
// Only clean specific subdirectories
await rm('dist/esm', {recursive: true, force: true})
await rm('dist/cjs', {recursive: true, force: true})
// Keep dist/types
},
},
})Watch Mode Behavior
In watch mode, cleaning behavior is important:
Clean on First Build Only
export default defineConfig((options) => ({
entry: ['src/index.ts'],
watch: options.watch,
clean: !options.watch, // Only clean initial build
}))Result:
- First build: Clean
- Subsequent rebuilds: Incremental
Always Clean
export default defineConfig({
watch: true,
clean: true, // Clean every rebuild
})Trade-off: Slower rebuilds, but always fresh output.
Tips
1. Leave enabled for production builds 2. Disable in watch mode for faster rebuilds 3. Use multiple configs carefully with cleaning 4. Custom clean logic via hooks if needed 5. Be cautious - cleaning removes ALL files in outDir 6. Test cleaning - ensure no important files are lost
Troubleshooting
Important Files Deleted
- Don't put non-build files in outDir
- Use separate directory for static files
- Disable cleaning and manage manually
Stale Files in Output
- Enable cleaning:
clean: true - Or manually remove before build
Slow Rebuilds in Watch
- Disable cleaning in watch mode
- Use incremental builds
CLI Examples
# Default (clean enabled)
tsdown
# Disable cleaning
tsdown --no-clean
# Watch mode without cleaning
tsdown --watch --no-clean
# Multiple formats with cleaning
tsdown --format esm,cjs --cleanExamples
Safe Production Build
# Clean before build
rm -rf dist
tsdown --cleanIncremental Development
export default defineConfig({
entry: ['src/index.ts'],
watch: true,
clean: false, // Faster rebuilds
sourcemap: true,
})Multi-Stage Build
// Stage 1: Clean and build main
export default defineConfig([
{
entry: ['src/index.ts'],
outDir: 'dist',
clean: true,
},
{
entry: ['src/utils.ts'],
outDir: 'dist',
clean: false, // Add to same directory
},
])Related Options
- Output Directory - Configure outDir
- Watch Mode - Development workflow
- Hooks - Custom clean logic
- Entry - Entry points
Configuration File
Centralize and manage build settings with a configuration file.
Overview
tsdown searches for config files automatically in the current directory and parent directories.
Supported File Names
tsdown looks for these files (in order):
tsdown.config.tstsdown.config.mtstsdown.config.ctstsdown.config.jstsdown.config.mjstsdown.config.cjstsdown.config.jsontsdown.configpackage.json(intsdownfield)
Basic Configuration
TypeScript Config
// tsdown.config.ts
import {defineConfig} from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
})JavaScript Config
// tsdown.config.js
export default {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
}JSON Config
// tsdown.config.json
{
"entry": ["src/index.ts"],
"format": ["esm", "cjs"],
"dts": true
}Package.json Config
// package.json
{
"name": "my-library",
"tsdown": {
"entry": ["src/index.ts"],
"format": ["esm", "cjs"],
"dts": true
}
}Multiple Configurations
Build multiple outputs with different settings:
export default defineConfig([
{
entry: 'src/index.ts',
format: ['esm', 'cjs'],
platform: 'node',
dts: true,
},
{
entry: 'src/browser.ts',
format: ['iife'],
platform: 'browser',
globalName: 'MyLib',
minify: true,
},
])Each configuration runs as a separate build.
Dynamic Configuration
Use a function for conditional config:
export default defineConfig((options) => {
const isDev = options.watch
return {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: !isDev,
sourcemap: isDev,
clean: !isDev,
}
})Available options:
watch- Whether watch mode is enabled- Other CLI flags passed to config
Config Loaders
Control how TypeScript config files are loaded:
Auto Loader (Default)
Uses native TypeScript support if available, otherwise falls back to unrun:
tsdown # Uses auto loaderNative Loader
Uses runtime's native TypeScript support (Node.js 23+, Bun, Deno):
tsdown --config-loader nativeUnrun Loader
Uses unrun library for loading:
tsdown --config-loader unrunTip: Use unrun loader if you need to load TypeScript configs without file extensions in Node.js.
Custom Config Path
Specify a custom config file location:
tsdown --config ./configs/build.config.ts
# or
tsdown -c custom-config.tsDisable Config File
Ignore config files and use CLI options only:
tsdown --no-config src/index.ts --format esmExtend Vite/Vitest Config (Experimental)
Reuse existing Vite or Vitest configurations:
# Extend vite.config.*
tsdown --from-vite
# Extend vitest.config.*
tsdown --from-vite vitestNote: Only specific options like resolve and plugins are reused. Test thoroughly as this feature is experimental.
Workspace / Monorepo
Build multiple packages with a single config:
export default defineConfig({
workspace: 'packages/*',
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})Each package directory matching the glob pattern will be built with the same configuration.
Common Patterns
Library with Multiple Builds
export default defineConfig([
// Node.js build
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
platform: 'node',
dts: true,
},
// Browser build
{
entry: ['src/browser.ts'],
format: ['iife'],
platform: 'browser',
globalName: 'MyLib',
},
])Development vs Production
export default defineConfig((options) => ({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: !options.watch,
sourcemap: options.watch ? true : false,
clean: !options.watch,
}))Monorepo Root Config
// Root tsdown.config.ts
export default defineConfig({
workspace: 'packages/*',
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
// Shared config for all packages
})Per-Package Override
// packages/special/tsdown.config.ts
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'], // Override: only ESM
platform: 'browser', // Override: browser only
})Config Precedence
When multiple configs exist:
1. CLI options (highest priority) 2. Config file specified with --config 3. Auto-discovered config files 4. Package.json tsdown field 5. Default values
Tips
1. Use TypeScript config for type checking and autocomplete 2. Use defineConfig helper for better DX 3. Export arrays for multiple build configurations 4. Use functions for dynamic/conditional configs 5. Keep configs simple - prefer convention over configuration 6. Use workspace for monorepo builds 7. Test experimental features thoroughly before production use
Related Options
- Entry - Configure entry points
- Output Format - Output formats
- Watch Mode - Watch mode configuration
CSS Support
Status: Experimental — API and behavior may change.
Configure CSS handling including preprocessors, syntax lowering, minification, and code splitting.
Getting Started
All CSS support in tsdown is provided by the @tsdown/css package. Install it to enable CSS handling:
npm install -D @tsdown/cssWhen @tsdown/css is installed, CSS processing is automatically enabled. Without it, encountering CSS files will result in an error.
CSS Import
Import .css files from TypeScript/JavaScript — CSS is extracted into separate .css assets:
// src/index.ts
import './style.css'
export function greet() {
return 'Hello'
}Output: index.mjs + index.css
@import Inlining
CSS @import statements are resolved and inlined automatically. No separate output files produced.
Inline CSS (?inline)
Append ?inline to return processed CSS as a JS string instead of emitting a .css file:
import './style.css' // → .css file
import css from './theme.css?inline' // → JS stringWorks with preprocessors too (./foo.scss?inline). Goes through full pipeline (preprocessors, @import inlining, lowering, minification). Tree-shakeable (moduleSideEffects: false).
CSS Pre-processors
Built-in support for Sass, Less, and Stylus. Install the preprocessor:
# Sass (either one)
npm install -D sass-embedded # recommended, faster
npm install -D sass
# Less
npm install -D less
# Stylus
npm install -D stylusThen import directly:
import './style.scss'
import './theme.less'
import './global.styl'Preprocessor Options
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `$brand-color: #ff7e17;`,
},
less: {
math: 'always',
},
stylus: {
define: {'$brand-color': '#ff7e17'},
},
},
},
})additionalData
Inject code at the beginning of every preprocessor file:
// String form
scss: {
additionalData: `@use "src/styles/variables" as *;`,
}
// Function form
scss: {
additionalData: (source, filename) => {
if (filename.includes('theme')) return source
return `@use "src/styles/variables" as *;\n${source}`
},
}CSS Minification
export default defineConfig({
css: {
minify: true,
},
})Powered by Lightning CSS.
CSS Target
Override the top-level target specifically for CSS:
export default defineConfig({
target: 'node18',
css: {
target: 'chrome90', // CSS-specific target
},
})Set css.target: false to disable CSS syntax lowering entirely.
CSS Transformer
css.transformer controls mutually exclusive CSS processing paths:
'lightningcss'(default):@importvia Lightning CSSbundleAsync(), no PostCSS.'postcss':@importviapostcss-import, PostCSS plugins applied, Lightning CSS for final transform only.
export default defineConfig({
css: {
transformer: 'postcss',
},
})PostCSS Options
export default defineConfig({
css: {
transformer: 'postcss',
postcss: {
plugins: [require('autoprefixer')],
},
// Or: postcss: './config' — path to search for postcss.config.js
},
})Auto-detects PostCSS config from project root when transformer is 'postcss' and css.postcss is omitted.
Lightning CSS (Syntax Lowering)
Install lightningcss to enable CSS syntax lowering based on your target:
npm install -D lightningcssWhen target is set (e.g., target: 'chrome108'), modern CSS features are automatically downleveled:
/* Input */
.foo {
& .bar {
color: red;
}
}
/* Output (chrome108) */
.foo .bar {
color: red;
}Custom Lightning CSS Options
import {Features} from 'lightningcss'
export default defineConfig({
css: {
lightningcss: {
targets: {chrome: 100 << 16},
include: Features.Nesting,
},
},
})css.lightningcss.targets takes precedence over both target and css.target for CSS.
Code Splitting
Merged (Default)
All CSS merged into a single file (default: style.css).
export default defineConfig({
css: {
fileName: 'my-library.css', // Custom name (default: 'style.css')
},
})Per-Chunk Splitting
export default defineConfig({
css: {
splitting: true, // Each JS chunk gets a corresponding .css file
},
})Preserving CSS Imports (css.inject)
When enabled, JS output preserves import statements pointing to emitted CSS files. Consumers auto-import CSS alongside JS:
export default defineConfig({
css: {
inject: true,
},
})Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
css.transformer | `'postcss' \ | 'lightningcss'` | 'lightningcss' |
css.splitting | boolean | false | Per-chunk CSS splitting |
css.fileName | string | 'style.css' | Merged CSS file name |
css.minify | boolean | false | CSS minification |
css.inject | boolean | false | Preserve CSS imports in JS output |
css.target | `string \ | string[] \ | false` |
css.postcss | `string \ | object` | — |
css.preprocessorOptions | object | — | Preprocessor options |
css.lightningcss | object | — | Lightning CSS options |
Related
- Target - Configure syntax lowering targets
- Output Format - Module output formats
Dependencies
Control how dependencies are bundled or externalized.
Overview
tsdown intelligently handles dependencies to keep your library lightweight while ensuring all necessary code is included.
Default Behavior
Auto-Externalized
These are NOT bundled by default:
- `dependencies` - Installed automatically with your package
- `peerDependencies` - User must install manually
Conditionally Bundled
These are bundled ONLY if imported:
- `devDependencies` - Only if actually used in source code
- Phantom dependencies - In node_modules but not in package.json
Configuration Options
All dependency options are grouped under the deps field:
export default defineConfig({
deps: {
neverBundle: ['react', /^@myorg\//],
alwaysBundle: ['some-package'],
onlyBundle: ['cac', 'bumpp'],
skipNodeModulesBundle: true,
},
})deps.neverBundle
Mark dependencies as external (not bundled):
export default defineConfig({
entry: ['src/index.ts'],
deps: {
neverBundle: [
'react', // Single package
'react-dom',
/^@myorg\//, // Regex pattern (all @myorg/* packages)
/^lodash/, // All lodash packages
],
},
})deps.alwaysBundle
Force dependencies to be bundled:
export default defineConfig({
entry: ['src/index.ts'],
deps: {
alwaysBundle: [
'some-package', // Bundle this even if in dependencies
'vendor-lib',
],
},
})deps.onlyBundle
Whitelist of dependencies allowed to be bundled from node_modules. Throws an error if any unlisted dependency is bundled:
export default defineConfig({
entry: ['src/index.ts'],
deps: {
onlyBundle: [
'cac', // Allow bundling cac
'bumpp', // Allow bundling bumpp
/^my-utils/, // Regex patterns supported
],
},
})Behavior:
- Array (
['cac', /^my-/]): Only matching dependencies can be bundled. Error for others. - `false`: Suppress all warnings about bundled dependencies.
- Not set (default): Warns if any node_modules dependencies are bundled.
Note: Include all sub-dependencies in the list, not just top-level imports.
deps.skipNodeModulesBundle
Skip resolving and bundling ALL node_modules:
export default defineConfig({
entry: ['src/index.ts'],
deps: {
skipNodeModulesBundle: true,
},
})Result: No dependencies from node_modules are parsed or bundled.
Note: Cannot be used together with alwaysBundle.
Common Patterns
React Component Library
export default defineConfig({
entry: ['src/index.tsx'],
format: ['esm', 'cjs'],
deps: {
neverBundle: [
'react',
'react-dom',
/^react\//, // react/jsx-runtime, etc.
],
},
dts: true,
})Utility Library with Shared Deps
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
deps: {
alwaysBundle: ['lodash-es'],
},
dts: true,
})Monorepo Package
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
deps: {
neverBundle: [
/^@mycompany\//, // Don't bundle other workspace packages
],
},
dts: true,
})CLI Tool (Bundle Everything)
export default defineConfig({
entry: ['src/cli.ts'],
format: ['esm'],
platform: 'node',
deps: {
alwaysBundle: [/.*/],
},
shims: true,
})Library with Specific Externals
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
deps: {
neverBundle: ['vue', '@vue/runtime-core', '@vue/reactivity'],
},
dts: true,
})Declaration Files
Dependency handling for .d.ts files follows the same rules as JavaScript.
Complex Type Resolution
Use TypeScript resolver for complex third-party types:
export default defineConfig({
entry: ['src/index.ts'],
dts: {
resolver: 'tsc', // Use TypeScript resolver instead of Oxc
},
})When to use `tsc` resolver:
- Types in
@types/*packages with non-standard naming (e.g.,@types/babel__generator) - Complex type dependencies
- Issues with default Oxc resolver
Trade-off: tsc is slower but more compatible.
CLI Usage
Never Bundle
tsdown --deps.never-bundle react --deps.never-bundle react-dom
tsdown --deps.never-bundle '/^@myorg\/.*/'Skip Node Modules
tsdown --deps.skip-node-modules-bundleMigration from Deprecated Options
| Deprecated Option | New Option |
|---|---|
external | deps.neverBundle |
noExternal | deps.alwaysBundle |
inlineOnly | deps.onlyBundle |
deps.onlyAllowBundle | deps.onlyBundle |
skipNodeModulesBundle | deps.skipNodeModulesBundle |
Examples by Use Case
Framework Component
// Don't bundle framework
export default defineConfig({
deps: {
neverBundle: ['vue', 'react', 'solid-js', 'svelte'],
},
})Standalone App
// Bundle everything
export default defineConfig({
deps: {
alwaysBundle: [/.*/],
},
})Shared Library
// Bundle only specific utils
export default defineConfig({
deps: {
neverBundle: [/.*/], // External by default
alwaysBundle: ['tiny-utils'], // Except this one
},
})Monorepo Package
// External workspace packages, bundle utilities
export default defineConfig({
deps: {
neverBundle: [
/^@workspace\//, // Other workspace packages
'react',
'react-dom',
],
alwaysBundle: [
'lodash-es', // Bundle utility libraries
],
},
})Troubleshooting
Dependency Bundled Unexpectedly
Check if it's in devDependencies and imported. Move to dependencies:
{
"dependencies": {
"should-be-external": "^1.0.0"
}
}Or explicitly externalize:
export default defineConfig({
deps: {
neverBundle: ['should-be-external'],
},
})Missing Dependency at Runtime
Ensure it's in dependencies or peerDependencies:
{
"dependencies": {
"needed-package": "^1.0.0"
}
}Or bundle it:
export default defineConfig({
deps: {
alwaysBundle: ['needed-package'],
},
})Type Resolution Errors
Use TypeScript resolver for complex types:
export default defineConfig({
dts: {
resolver: 'tsc',
},
})Summary
Default behavior:
dependencies&peerDependencies→ ExternaldevDependencies& phantom deps → Bundled if imported
Override (under `deps`):
neverBundle→ Force externalalwaysBundle→ Force bundledonlyBundle→ Whitelist bundled depsskipNodeModulesBundle→ Skip all node_modules
Declaration files:
- Same bundling logic as JavaScript
- Use
resolver: 'tsc'for complex types
Tips
1. Keep dependencies external for libraries 2. Bundle everything for standalone CLIs 3. Use regex patterns for namespaced packages 4. Check bundle size to verify external/bundled split 5. Test with fresh install to catch missing dependencies 6. Use tsc resolver only when needed (slower)
Related Options
- External - This page
- Platform - Runtime environment
- Output Format - Module formats
- DTS - Type declarations
TypeScript Declaration Files
Generate .d.ts type declaration files for your library.
Overview
tsdown uses rolldown-plugin-dts to generate and bundle TypeScript declaration files.
Requirements:
- TypeScript must be installed in your project
Enabling DTS Generation
Auto-Enabled
DTS generation is automatically enabled if package.json contains:
typesfield, ortypingsfield
Manual Enable
CLI
tsdown --dtsConfig File
export default defineConfig({
dts: true,
})Performance
With isolatedDeclarations (Recommended)
Extremely fast - uses oxc-transform for generation.
// tsconfig.json
{
"compilerOptions": {
"isolatedDeclarations": true
}
}Without isolatedDeclarations
Falls back to TypeScript compiler. Reliable but slower.
Declaration Maps
Map .d.ts files back to original .ts sources (useful for monorepos).
Enable in tsconfig.json
{
"compilerOptions": {
"declarationMap": true
}
}Enable in tsdown Config
export default defineConfig({
dts: {
sourcemap: true,
},
})Advanced Options
Custom Compiler Options
Override TypeScript compiler options:
export default defineConfig({
dts: {
compilerOptions: {
removeComments: false,
},
},
})Build Process
- ESM format:
.jsand.d.tsfiles generated in same build - CJS format: Separate build process for
.d.tsfiles
Common Patterns
Basic Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})Output:
dist/index.mjsdist/index.cjsdist/index.d.ts
Multiple Entry Points
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils.ts',
},
format: ['esm', 'cjs'],
dts: true,
})Output:
dist/index.mjs,dist/index.cjs,dist/index.d.tsdist/utils.mjs,dist/utils.cjs,dist/utils.d.ts
With Monorepo Support
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: {
sourcemap: true, // Enable declaration maps
},
})Fast Build (Isolated Declarations)
// tsconfig.json
{
"compilerOptions": {
"isolatedDeclarations": true,
"declaration": true,
"declarationMap": true
}
}// tsdown.config.ts
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true, // Will use fast oxc-transform
})Troubleshooting
Missing Types
Ensure TypeScript is installed:
pnpm add -D typescriptSlow Generation
Enable isolatedDeclarations in tsconfig.json for faster builds.
Declaration Errors
Check that all exports have explicit types (required for isolatedDeclarations).
Report Issues
For DTS-specific issues, report to rolldown-plugin-dts.
Vue Support
Enable Vue component type generation (requires vue-tsc):
export default defineConfig({
dts: {
vue: true,
},
})Oxc Transform
Control Oxc usage for declaration generation:
export default defineConfig({
dts: {
oxc: true, // Use oxc-transform (fast, requires isolatedDeclarations)
},
})Custom TSConfig
Specify a different tsconfig for DTS generation:
export default defineConfig({
dts: {
tsconfig: './tsconfig.build.json',
},
})Available DTS Options
| Option | Type | Description |
|---|---|---|
sourcemap | boolean | Generate declaration source maps |
compilerOptions | object | Override TypeScript compiler options |
vue | boolean | Enable Vue type generation (requires vue-tsc) |
oxc | boolean | Use oxc-transform for fast generation |
tsconfig | string | Path to tsconfig file |
resolver | `'oxc' \ | 'tsc'` |
cjsDefault | boolean | CJS default export handling |
sideEffects | boolean | Preserve side effects in declarations |
Tips
1. Always enable DTS for TypeScript libraries 2. Use isolatedDeclarations for fast builds 3. Enable declaration maps in monorepos 4. Ensure explicit types for all exports 5. Install TypeScript as dev dependency
Related Options
- Entry - Configure entry points
- Output Format - Multiple output formats
- Target - JavaScript version
Entry Points
Configure which files to bundle as entry points.
Overview
Entry points are the starting files for the bundling process. Each entry point generates a separate bundle.
Usage Patterns
CLI
# Single entry
tsdown src/index.ts
# Multiple entries
tsdown src/index.ts src/cli.ts
# Glob patterns
tsdown 'src/*.ts'Config File
Single Entry
export default defineConfig({
entry: 'src/index.ts',
})Multiple Entries (Array)
export default defineConfig({
entry: ['src/entry1.ts', 'src/entry2.ts'],
})Named Entries (Object)
export default defineConfig({
entry: {
main: 'src/index.ts',
utils: 'src/utils.ts',
cli: 'src/cli.ts',
},
})Output files will match the keys:
dist/main.mjsdist/utils.mjsdist/cli.mjs
Glob Patterns
Match multiple files dynamically using glob patterns:
All TypeScript Files
export default defineConfig({
entry: 'src/**/*.ts',
})Exclude Test Files
export default defineConfig({
entry: ['src/*.ts', '!src/*.test.ts'],
})Object Entries with Glob Patterns
Use glob wildcards (*) in both keys and values. The * in the key acts as a placeholder replaced with the matched file name (without extension):
export default defineConfig({
entry: {
// Maps src/foo.ts → dist/lib/foo.js, src/bar.ts → dist/lib/bar.js
'lib/*': 'src/*.ts',
},
})Negation Patterns in Object Entries
Values can be an array with negation patterns (!):
export default defineConfig({
entry: {
'hooks/*': ['src/hooks/*.ts', '!src/hooks/index.ts'],
},
})Multiple positive and negation patterns:
export default defineConfig({
entry: {
'utils/*': [
'src/utils/*.ts',
'src/utils/*.tsx',
'!src/utils/index.ts',
'!src/utils/internal.ts',
],
},
})Warning: Multiple positive patterns in an array value must share the same base directory.
Mixed Entries
Mix strings, glob patterns, and object entries in an array:
export default defineConfig({
entry: ['src/*', '!src/foo.ts', {main: 'index.ts'}, {'lib/*': ['src/*.ts', '!src/bar.ts']}],
})Object entries take precedence when output names conflict.
Windows Compatibility
Use forward slashes / instead of backslashes \ on Windows:
// ✅ Correct
entry: 'src/utils/*.ts'
// ❌ Wrong on Windows
entry: 'src\\utils\\*.ts'Common Patterns
Library with Main Export
export default defineConfig({
entry: 'src/index.ts',
format: ['esm', 'cjs'],
dts: true,
})Library with Multiple Exports
export default defineConfig({
entry: {
index: 'src/index.ts',
client: 'src/client.ts',
server: 'src/server.ts',
},
format: ['esm', 'cjs'],
dts: true,
})CLI Tool
export default defineConfig({
entry: {
cli: 'src/cli.ts',
},
format: ['esm'],
platform: 'node',
})Preserve Directory Structure
Use with unbundle: true to keep file structure:
export default defineConfig({
entry: ['src/**/*.ts', '!**/*.test.ts'],
unbundle: true,
format: ['esm'],
dts: true,
})This will output files matching the source structure:
src/index.ts→dist/index.mjssrc/utils/helper.ts→dist/utils/helper.mjs
Tips
1. Use glob patterns for multiple related files 2. Use object syntax for custom output names 3. Exclude test files with negation patterns !**/*.test.ts 4. Combine with unbundle to preserve directory structure 5. Use named entries for better control over output filenames
Executable - exe
[experimental] Bundle as a standalone executable using Node.js Single Executable Applications.
Requirements
- Node.js >= 25.5.0 (ESM support requires >= 25.7.0)
- Not supported in Bun or Deno
Basic Usage
export default defineConfig({
entry: ['src/cli.ts'],
exe: true,
})Behavior When Enabled
- Default output format changes from
esmtocjs(unless Node.js >= 25.7.0) - Declaration file generation (
dts) is disabled by default - Code splitting is disabled
- Only single entry points are supported
- Legacy CJS warnings are suppressed
Advanced Configuration
export default defineConfig({
entry: ['src/cli.ts'],
exe: {
fileName: 'my-tool',
seaConfig: {
disableExperimentalSEAWarning: true,
useCodeCache: true,
useSnapshot: false,
},
},
})ExeOptions
| Option | Type | Description |
|---|---|---|
seaConfig | `Omit<SeaConfig, 'main' \ | 'output' \ |
fileName | `string \ | ((chunk) => string)` |
targets | ExeTarget[] | Cross-platform build targets (requires @tsdown/exe) |
SeaConfig
See Node.js Single Executable Applications documentation.
| Option | Type | Default | Description |
|---|---|---|---|
disableExperimentalSEAWarning | boolean | true | Disable the experimental warning |
useSnapshot | boolean | false | Use V8 snapshot |
useCodeCache | boolean | false | Use V8 code cache |
execArgv | string[] | - | Extra Node.js arguments |
execArgvExtension | `'none' \ | 'env' \ | 'cli'` |
assets | Record<string, string> | - | Assets to embed |
Cross-Platform Builds
Install @tsdown/exe to build executables for multiple platforms from a single machine:
pnpm add -D @tsdown/exeexport default defineConfig({
entry: ['src/cli.ts'],
exe: {
targets: [
{platform: 'linux', arch: 'x64', nodeVersion: '25.7.0'},
{platform: 'darwin', arch: 'arm64', nodeVersion: '25.7.0'},
{platform: 'win', arch: 'x64', nodeVersion: '25.7.0'},
],
},
})This downloads the target platform's Node.js binary, caches it locally, and produces platform-suffixed output:
dist/
cli-linux-x64
cli-darwin-arm64
cli-win-x64.exeExeTarget
| Field | Type | Description |
|---|---|---|
platform | `'win' \ | 'darwin' \ |
arch | `'x64' \ | 'arm64'` |
nodeVersion | string | Node.js version (must be >=25.7.0) |
Caching
Downloaded Node.js binaries are cached in system cache directories:
- macOS:
~/Library/Caches/tsdown/node/ - Linux:
~/.cache/tsdown/node/ - Windows:
%LOCALAPPDATA%/tsdown/Caches/node/
Platform Notes
- On macOS, the executable is automatically codesigned (ad-hoc) for Gatekeeper compatibility
- On Windows, the
.exeextension is automatically appended - When
targetsis specified,seaConfig.executableis ignored
CLI
tsdown --exe
tsdown src/cli.ts --exePackage Validation (publint & attw)
Validate your package configuration and type declarations before publishing.
Overview
tsdown integrates with publint and Are the types wrong? (attw) to catch common packaging issues. Both are optional dependencies.
Installation
# publint only
npm install -D publint
# attw only
npm install -D @arethetypeswrong/core
# both
npm install -D publint @arethetypeswrong/corepublint
Checks that package.json fields (exports, main, module, types) match your actual output files.
Enable
export default defineConfig({
publint: true,
})Configuration
export default defineConfig({
publint: {
level: 'error', // 'warning' | 'error' | 'suggestion'
},
})CLI
tsdown --publintattw (Are the types wrong?)
Verifies TypeScript declarations are correct across different module resolution strategies (node10, node16, bundler).
Enable
export default defineConfig({
attw: true,
})Configuration
export default defineConfig({
attw: {
profile: 'node16', // 'strict' | 'node16' | 'esm-only'
level: 'error', // 'warn' | 'error'
ignoreRules: ['false-cjs', 'cjs-resolves-to-esm'],
},
})Profiles
| Profile | Description |
|---|---|
strict | Requires all resolutions to pass (default) |
node16 | Ignores node10 resolution failures |
esm-only | Ignores node10 and node16-cjs resolution failures |
Ignore Rules
Suppress specific problem types with ignoreRules:
| Rule | Description |
|---|---|
no-resolution | Module could not be resolved |
untyped-resolution | Resolution succeeded but has no types |
false-cjs | Types indicate CJS but implementation is ESM |
false-esm | Types indicate ESM but implementation is CJS |
cjs-resolves-to-esm | CJS resolution points to an ESM module |
fallback-condition | A fallback/wildcard condition was used |
cjs-only-exports-default | CJS module only exports a default |
named-exports | Named exports mismatch between types and implementation |
false-export-default | Types declare a default export that doesn't exist |
missing-export-equals | Types are missing export = for CJS |
unexpected-module-syntax | File uses unexpected module syntax |
internal-resolution-error | Internal resolution error in type checking |
CLI
tsdown --attwCI Integration
Both tools support CI-aware options:
export default defineConfig({
publint: 'ci-only',
attw: {
enabled: 'ci-only',
profile: 'node16',
level: 'error',
},
})Both tools require a package.json in your project directory.
Related Options
- CI Environment - CI-aware option details
- Package Exports - Auto-generate exports field
Log Level
Control the verbosity of build output.
Overview
The logLevel option controls how much information tsdown displays during the build process.
Type
logLevel?: 'silent' | 'error' | 'warn' | 'info'Default: 'info'
Basic Usage
CLI
# Suppress all output
tsdown --log-level silent
# Only show errors
tsdown --log-level error
# Show warnings and errors
tsdown --log-level warn
# Show all info (default)
tsdown --log-level infoConfig File
export default defineConfig({
entry: ['src/index.ts'],
logLevel: 'error',
})Available Levels
| Level | Shows | Use Case |
|---|---|---|
silent | Nothing | CI/CD pipelines, scripting |
error | Errors only | Minimal output |
warn | Warnings + errors | Standard CI/CD |
info | All messages | Development (default) |
Common Patterns
CI/CD Pipeline
export default defineConfig({
entry: ['src/index.ts'],
logLevel: 'error', // Only show errors in CI
})Scripting
export default defineConfig({
entry: ['src/index.ts'],
logLevel: 'silent', // No output for automation
})Fail on Warnings
The failOnWarn option controls whether warnings cause the build to exit with a non-zero code. Defaults to false — warnings never fail the build.
export default defineConfig({
failOnWarn: false, // Default: never fail on warnings
// failOnWarn: true, // Always fail on warnings
// failOnWarn: 'ci-only', // Fail on warnings only in CI
})See CI Environment for more about CI-aware options.
Related Options
- CI Environment - CI-aware option details
- CLI Reference - All CLI options
- Config File - Configuration setup
Minification
Compress code to reduce bundle size.
Overview
Minification removes unnecessary characters (whitespace, comments) and optimizes code for production, reducing bundle size and improving load times.
Note: Uses Oxc minifier internally. The minifier is currently in alpha.
Type
minify?: boolean | 'dce-only' | MinifyOptionstrue— Enable full minification (whitespace removal, mangling, compression)false— Disable minification (default)'dce-only'— Only perform dead code elimination without full minificationMinifyOptions— Pass detailed options to the Oxc minifier
Basic Usage
CLI
# Enable minification
tsdown --minify
# Disable minification
tsdown --no-minifyNote: The CLI --minify flag is a boolean toggle. For 'dce-only' mode or advanced options, use the config file.
Config File
export default defineConfig({
entry: ['src/index.ts'],
minify: true,
})DCE-Only Mode
Remove dead code without full minification (keeps readable output):
export default defineConfig({
entry: ['src/index.ts'],
minify: 'dce-only',
})Example Output
Without Minification
// dist/index.mjs
const x = 1
function hello(x$1) {
console.log('Hello World')
console.log(x$1)
}
hello(x)With Minification
// dist/index.mjs
const e = 1
function t(e) {
;(console.log(`Hello World`), console.log(e))
}
t(e)Common Patterns
Production Build
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: true,
clean: true,
})Conditional Minification
export default defineConfig((options) => ({
entry: ['src/index.ts'],
format: ['esm'],
minify: !options.watch, // Only minify in production
}))Browser Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'],
platform: 'browser',
globalName: 'MyLib',
minify: true,
})Multiple Builds
export default defineConfig([
// Development build
{
entry: ['src/index.ts'],
format: ['esm'],
minify: false,
outDir: 'dist/dev',
},
// Production build
{
entry: ['src/index.ts'],
format: ['esm'],
minify: true,
outDir: 'dist/prod',
},
])CLI Examples
# Production build with minification
tsdown --minify --clean
# Multiple formats with minification
tsdown --format esm --format cjs --minify
# Conditional minification (only when not watching)
tsdown --minify # Or omit --watchTips
1. Use `minify: true` for production builds 2. Use `'dce-only'` to remove dead code while keeping output readable 3. Skip minification during development for faster rebuilds 4. Combine with tree shaking for best results 5. Test minified output thoroughly (Oxc minifier is in alpha)
Troubleshooting
Minified Code Has Bugs
Oxc minifier is in alpha and may have issues:
1. Use DCE-only mode: minify: 'dce-only' 2. Report bug to Oxc project 3. Disable minification: minify: false
Unexpected Output
- Test unminified first to isolate issue
- Check source maps for debugging
- Verify target compatibility
Related Options
- Tree Shaking - Remove unused code
- Target - Syntax transformations
- Output Format - Module formats
- Sourcemap - Debug information
Output Directory
Configure the output directory for bundled files.
Overview
By default, tsdown outputs bundled files to the dist directory. You can customize this location using the outDir option.
Basic Usage
CLI
# Default output to dist/
tsdown
# Custom output directory
tsdown --out-dir build
tsdown -d libConfig File
export default defineConfig({
entry: ['src/index.ts'],
outDir: 'build',
})Common Patterns
Standard Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist', // Default
dts: true,
})Output:
dist/
├── index.mjs
├── index.cjs
└── index.d.tsSeparate Directories by Format
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm'],
outDir: 'dist/esm',
},
{
entry: ['src/index.ts'],
format: ['cjs'],
outDir: 'dist/cjs',
},
])Output:
dist/
├── esm/
│ └── index.js
└── cjs/
└── index.jsMonorepo Package
export default defineConfig({
entry: ['src/index.ts'],
outDir: 'lib', // Custom directory
clean: true,
})Build to Root
export default defineConfig({
entry: ['src/index.ts'],
outDir: '.', // Output to project root (not recommended)
clean: false, // Don't clean root!
})Warning: Be careful when outputting to root to avoid deleting important files.
Output Extensions
Custom Extensions
Use outExtensions to control file extensions:
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist',
outExtensions({format}) {
return {
js: format === 'esm' ? '.mjs' : '.cjs',
}
},
})Default Extensions
| Format | Default Extension | With type: "module" |
|---|---|---|
esm | .mjs | .js |
cjs | .cjs | .js |
iife | .global.js | .global.js |
umd | .umd.js | .umd.js |
ESM with .js Extension
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({js: '.js'}),
})Requires "type": "module" in package.json.
File Naming
Entry Names
Control output filenames based on entry names:
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils.ts',
},
outDir: 'dist',
})Output:
dist/
├── index.mjs
└── utils.mjsGlob Entry
export default defineConfig({
entry: ['src/**/*.ts', '!**/*.test.ts'],
outDir: 'dist',
unbundle: true, // Preserve structure
})Output:
dist/
├── index.mjs
├── utils/
│ └── helper.mjs
└── components/
└── button.mjsMultiple Builds
Same Output Directory
export default defineConfig([
{
entry: ['src/index.ts'],
outDir: 'dist',
clean: true, // Clean first
},
{
entry: ['src/cli.ts'],
outDir: 'dist',
clean: false, // Don't clean again
},
])Different Output Directories
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist/lib',
},
{
entry: ['src/cli.ts'],
format: ['esm'],
outDir: 'dist/bin',
},
])CLI Examples
# Default
tsdown
# Custom directory
tsdown --out-dir build
tsdown -d lib
# Nested directory
tsdown --out-dir dist/lib
# With other options
tsdown --out-dir build --format esm,cjs --dtsTips
1. Use default `dist` for standard projects 2. Be careful with root - avoid outDir: '.' 3. Clean before build - use clean: true 4. Consistent naming - match your project conventions 5. Separate by format if needed for clarity 6. Check .gitignore - ensure output dir is ignored
Troubleshooting
Files Not in Expected Location
- Check
outDirconfig - Verify build completed successfully
- Look for typos in path
Files Deleted Unexpectedly
- Check if
clean: true - Ensure outDir doesn't overlap with source
- Don't use root as outDir
Permission Errors
- Check write permissions
- Ensure directory isn't locked
- Try different location
Related Options
- Cleaning - Clean output directory
- Entry - Entry points
- Output Format - Module formats
- Unbundle - Preserve structure
Output Format
Configure the module format(s) for generated bundles.
Overview
tsdown can generate bundles in multiple formats. Default is ESM.
Available Formats
| Format | Description | Use Case |
|---|---|---|
esm | ECMAScript Module (default) | Modern Node.js, browsers, Deno |
cjs | CommonJS | Legacy Node.js, require() |
iife | Immediately Invoked Function Expression | Browser <script> tags |
umd | Universal Module Definition | AMD, CommonJS, and globals |
Usage
CLI
# Single format
tsdown --format esm
# Multiple formats
tsdown --format esm --format cjs
# Or comma-separated
tsdown --format esm,cjsConfig File
Single Format
export default defineConfig({
entry: ['src/index.ts'],
format: 'esm',
})Multiple Formats
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
})Per-Format Configuration
Override options for specific formats:
export default defineConfig({
entry: ['src/index.ts'],
format: {
esm: {
target: ['es2015'],
},
cjs: {
target: ['node20'],
},
},
})This allows different targets, platforms, or other settings per format.
Common Patterns
Modern Library (ESM + CJS)
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
})Output:
dist/index.mjs(ESM)dist/index.cjs(CJS)dist/index.d.ts(Types)
Browser Library (IIFE)
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'],
globalName: 'MyLib',
platform: 'browser',
minify: true,
})Output: dist/index.global.js (IIFE with global MyLib)
Universal Library (UMD)
export default defineConfig({
entry: ['src/index.ts'],
format: ['umd'],
globalName: 'MyLib',
platform: 'neutral',
})Works with AMD, CommonJS, and browser globals.
Node.js Package (CJS + ESM)
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
platform: 'node',
dts: true,
shims: true, // Add __dirname, __filename for CJS compat
})Framework Component Library
export default defineConfig({
entry: ['src/index.tsx'],
format: ['esm', 'cjs'],
deps: {
neverBundle: ['react', 'react-dom'], // Don't bundle dependencies
},
dts: true,
})Format-Specific Outputs
File Extensions
| Format | Extension |
|---|---|
| ESM | .mjs or .js (with "type": "module") |
| CJS | .cjs or .js (without "type": "module") |
| IIFE | .global.js |
| UMD | .umd.js |
Customize Extensions
Use outExtensions to override:
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outExtensions: ({format}) => ({
js: format === 'esm' ? '.js' : '.cjs',
}),
})Tips
1. Use ESM + CJS for maximum compatibility 2. Use IIFE for browser-only libraries 3. Use UMD for universal compatibility (less common now) 4. Externalize dependencies to avoid bundling framework code 5. Add shims for CJS compatibility when using Node.js APIs 6. Set globalName for IIFE/UMD formats
Related Options
- Target - Set JavaScript version
- Platform - Set platform (node, browser, neutral)
- Shims - Add ESM/CJS compatibility
- Output Directory - Customize output paths
Auto-Generate Package Exports
Automatically generate package.json exports field from build output.
Overview
tsdown can automatically infer and generate the exports, main, module, and types fields in your package.json based on your build outputs.
Status: Experimental - review before publishing.
Basic Usage
CLI
tsdown --exportsConfig File
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
exports: true,
})What Gets Generated
Single Entry
Config:
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
exports: true,
})Generated in package.json:
{
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}Multiple Entries
Config:
export default defineConfig({
entry: {
index: 'src/index.ts',
utils: 'src/utils.ts',
},
format: ['esm', 'cjs'],
dts: true,
exports: true,
})Generated in package.json:
{
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}Export All Files
Include all output files, not just entry points:
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
exports: {
all: true,
},
})Result: All .mjs, .cjs, and .d.ts files will be added to exports.
Dev-Time Source Linking
Dev Exports
Link to source files during development:
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
exports: {
devExports: true,
},
})Generated:
{
"exports": {
".": "./src/index.ts" // Points to source
},
"publishConfig": {
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
}Note: Supported by pnpm/yarn, not npm.
Conditional Dev Exports
Use specific condition for dev exports:
export default defineConfig({
exports: {
devExports: 'development',
},
})Generated:
{
"exports": {
".": {
"development": "./src/index.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}Use with TypeScript customConditions:
// tsconfig.json
{
"compilerOptions": {
"customConditions": ["development"]
}
}Custom Exports
Add custom export mappings:
export default defineConfig({
entry: ['src/index.ts'],
exports: {
customExports(pkg, context) {
// Add custom export
pkg['./foo'] = './dist/foo.js'
// Add package.json export
pkg['./package.json'] = './package.json'
return pkg
},
},
})Common Patterns
Complete Library Setup
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
exports: true,
clean: true,
})Multiple Exports with Dev Mode
export default defineConfig({
entry: {
index: 'src/index.ts',
client: 'src/client.ts',
server: 'src/server.ts',
},
format: ['esm', 'cjs'],
dts: true,
exports: {
all: false, // Only entries
devExports: 'development',
},
})Monorepo Package
export default defineConfig({
workspace: 'packages/*',
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
exports: true, // Generate for each package
})Validation
Enable Publint
Validate generated exports:
tsdown --exports --publintOr in config:
export default defineConfig({
exports: true,
publint: true, // Validate exports
})Tips
1. Review before publishing - Check generated fields 2. Use with publint - Validate exports field 3. Enable for libraries - Especially with multiple exports 4. Use devExports - Better DX during development 5. Test exports - Verify imports work correctly
Troubleshooting
Exports Not Generated
- Ensure
exports: trueis set - Check build completed successfully
- Verify output files exist
Wrong Export Paths
- Check
outDirconfiguration - Verify entry names match expectations
- Review
formatsettings
Dev Exports Not Working
- Only supported by pnpm/yarn
- Check package manager
- Use
publishConfigfor publishing
Types Not Exported
- Enable
dts: true - Ensure TypeScript is installed
- Check
.d.tsfiles are generated
CLI Examples
# Generate exports
tsdown --exports
# With publint validation
tsdown --exports --publint
# Export all files
tsdown --exports
# With dev exports
tsdown --exportsRelated Options
- Entry - Configure entry points
- Output Format - Module formats
- DTS - Type declarations
Platform
Target runtime environment for bundled code.
Overview
Platform determines the runtime environment and affects module resolution, built-in handling, and optimizations.
Available Platforms
| Platform | Runtime | Built-ins | Use Case |
|---|---|---|---|
node | Node.js (default) | Resolved automatically | Server-side, CLIs, tooling |
browser | Web browsers | Warning if used | Front-end applications |
neutral | Platform-agnostic | No assumptions | Universal libraries |
Usage
CLI
tsdown --platform node # Default
tsdown --platform browser
tsdown --platform neutralConfig File
export default defineConfig({
entry: ['src/index.ts'],
platform: 'browser',
})Platform Details
Node Platform
Default platform for server-side and tooling.
export default defineConfig({
entry: ['src/index.ts'],
platform: 'node',
})Characteristics:
- Node.js built-ins (fs, path, etc.) resolved automatically
- Optimized for Node.js runtime
- Compatible with Deno and Bun
- Default mainFields:
['main', 'module']
Browser Platform
For web applications running in browsers.
export default defineConfig({
entry: ['src/index.ts'],
platform: 'browser',
format: ['esm'],
})Characteristics:
- Warnings if Node.js built-ins are used
- May require polyfills for Node APIs
- Optimized for browser environments
- Default mainFields:
['browser', 'module', 'main']
Neutral Platform
Platform-agnostic for universal libraries.
export default defineConfig({
entry: ['src/index.ts'],
platform: 'neutral',
format: ['esm'],
})Characteristics:
- No runtime assumptions
- No automatic built-in resolution
- Relies on
exportsfield only - Default mainFields:
[] - Full control over runtime behavior
CJS Format Limitation
CJS format always uses `node` platform and cannot be changed.
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs'],
platform: 'browser', // Ignored for CJS
})See rolldown PR #4693 for details.
Module Resolution
Main Fields
Different platforms check different package.json fields:
| Platform | mainFields | Priority |
|---|---|---|
node | ['main', 'module'] | main → module |
browser | ['browser', 'module', 'main'] | browser → module → main |
neutral | [] | Only exports field |
Neutral Platform Resolution
When using neutral, packages without exports field may fail to resolve:
Help: The "main" field here was ignored. Main fields must be configured
explicitly when using the "neutral" platform.Solution: Configure mainFields explicitly:
export default defineConfig({
platform: 'neutral',
inputOptions: {
resolve: {
mainFields: ['module', 'main'],
},
},
})Common Patterns
Node.js CLI Tool
export default defineConfig({
entry: ['src/cli.ts'],
format: ['esm'],
platform: 'node',
shims: true,
})Browser Library (IIFE)
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'],
platform: 'browser',
globalName: 'MyLib',
minify: true,
})Universal Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
platform: 'neutral',
inputOptions: {
resolve: {
mainFields: ['module', 'main'],
},
},
})React Component Library
export default defineConfig({
entry: ['src/index.tsx'],
format: ['esm', 'cjs'],
platform: 'browser',
deps: {
neverBundle: ['react', 'react-dom'],
},
})Node.js + Browser Builds
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
platform: 'node',
},
{
entry: ['src/browser.ts'],
format: ['esm'],
platform: 'browser',
},
])Troubleshooting
Node Built-in Warnings (Browser)
When using Node.js APIs in browser builds:
Warning: Module "fs" has been externalized for browser compatibilitySolutions:
1. Use platform: 'node' if not browser-only 2. Add polyfills for Node APIs 3. Avoid Node.js built-ins in browser code 4. Use platform: 'neutral' with careful dependency management
Module Resolution Issues (Neutral)
When packages don't resolve with neutral:
export default defineConfig({
platform: 'neutral',
inputOptions: {
resolve: {
mainFields: ['module', 'browser', 'main'],
conditions: ['import', 'require'],
},
},
})Tips
1. Use `node` for server-side and CLIs (default) 2. Use `browser` for front-end applications 3. Use `neutral` for universal libraries 4. Configure mainFields when using neutral platform 5. CJS is always node - use ESM for other platforms 6. Test in target environment to verify compatibility
Related Options
- Output Format - Module formats
- Target - JavaScript version
- Shims - ESM/CJS compatibility
- Dependencies - External packages
Root Directory
Specify the root directory of input files for output structure mapping.
Overview
The root option is similar to TypeScript's rootDir. It determines how entry file paths map to output paths. By default, tsdown computes the root as the common base directory of all entry files. Setting root explicitly lets you override this behavior.
Basic Usage
CLI
tsdown --root srcConfig File
export default defineConfig({
entry: ['src/index.ts', 'src/utils/helper.ts'],
root: 'src',
})How It Works
Default
Given entries src/index.ts and src/utils/helper.ts, the common base directory is src/:
dist/
├── index.js
└── utils/
└── helper.jsWith root: '.'
Setting root to the project directory preserves the src/ prefix:
dist/
└── src/
├── index.js
└── utils/
└── helper.jsWhat It Affects
1. Entry name resolution — Array entry paths are computed relative to root for output filenames 2. Unbundle mode — Used as preserveModulesRoot, controlling output structure when unbundle: true
When to Use
- Auto-computed common base directory doesn't produce desired output structure
- Need to include or exclude directory prefixes in output paths
- Unbundle mode needs specific directory mapping
Common Patterns
Library with src/ Prefix Preserved
export default defineConfig({
entry: ['src/**/*.ts', '!**/*.test.ts'],
root: '.',
unbundle: true,
})Monorepo Package
export default defineConfig({
entry: ['src/index.ts'],
root: 'src',
unbundle: true,
})Related Options
- Unbundle - Preserve directory structure
- Entry - Entry point configuration
- Output Directory - Output location
Shims
Add compatibility between ESM and CommonJS module systems.
Overview
Shims provide small pieces of code that bridge the gap between CommonJS (CJS) and ECMAScript Modules (ESM), enabling cross-module-system compatibility.
What Shims Provide
ESM Output (when enabled)
With shims: true, adds CommonJS variables to ESM:
__dirname- Current directory path__filename- Current file path
ESM Output (automatic)
Always added when using require in ESM on Node.js:
requirefunction viacreateRequire(import.meta.url)
CJS Output (automatic)
Always added to CommonJS output:
import.meta.urlimport.meta.dirnameimport.meta.filename
Usage
CLI
tsdown --shimsConfig File
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
shims: true,
})Generated Code
ESM with Shims
Source:
console.log(__dirname)
console.log(__filename)Output (shims: true):
import {fileURLToPath} from 'node:url'
import {dirname} from 'node:path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__dirname)
console.log(__filename)ESM with require
Source:
const mod = require('some-module')Output (automatic on Node.js):
import {createRequire} from 'node:module'
const require = createRequire(import.meta.url)
const mod = require('some-module')CJS with import.meta
Source:
console.log(import.meta.url)
console.log(import.meta.dirname)Output (automatic):
const import_meta = {
url: require('url').pathToFileURL(__filename).toString(),
dirname: __dirname,
filename: __filename,
}
console.log(import_meta.url)
console.log(import_meta.dirname)Common Patterns
Node.js CLI Tool
export default defineConfig({
entry: ['src/cli.ts'],
format: ['esm'],
platform: 'node',
shims: true, // Add __dirname, __filename
})Dual Format Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
platform: 'node',
shims: true, // ESM gets __dirname/__filename
// CJS gets import.meta.* (automatic)
})Server-Side Code
export default defineConfig({
entry: ['src/server.ts'],
format: ['esm'],
platform: 'node',
shims: true,
deps: {
neverBundle: [/.*/], // External all deps
},
})File System Operations
// Source code
import {readFileSync} from 'fs'
import {join} from 'path'
// Read file relative to current module
const content = readFileSync(join(__dirname, 'data.json'), 'utf-8')// tsdown config
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
shims: true, // Enables __dirname
})When to Use Shims
Use shims: true when:
- ✅ Building Node.js tools/CLIs
- ✅ Code uses
__dirnameor__filename - ✅ Need file system operations relative to module
- ✅ Migrating from CommonJS to ESM
- ✅ Need cross-format compatibility
Don't need shims when:
- ❌ Browser-only code
- ❌ No file system operations
- ❌ Using only
import.meta.url - ❌ Pure ESM without CJS variables
Performance Impact
Runtime Overhead
Shims add minimal runtime overhead:
// Added to output when shims enabled
import {fileURLToPath} from 'node:url'
import {dirname} from 'node:path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)Tree Shaking
If __dirname or __filename are not used, they're automatically removed during bundling (no overhead).
Platform Considerations
Node.js Platform
export default defineConfig({
platform: 'node',
format: ['esm'],
shims: true, // Recommended for Node.js
})requireshim added automatically__dirnameand__filenameavailable withshims: true
Browser Platform
export default defineConfig({
platform: 'browser',
format: ['esm'],
shims: false, // Not needed for browser
})- Shims not needed (no Node.js variables)
- Will cause warnings if Node.js APIs used
Neutral Platform
export default defineConfig({
platform: 'neutral',
format: ['esm'],
shims: false, // Avoid platform-specific code
})- Avoid shims for maximum portability
CLI Examples
# Enable shims
tsdown --shims
# ESM with shims for Node.js
tsdown --format esm --platform node --shims
# Dual format with shims
tsdown --format esm --format cjs --shimsTroubleshooting
__dirname is not defined
Enable shims:
export default defineConfig({
shims: true,
})require is not defined in ESM
Automatic on Node.js platform. If not working:
export default defineConfig({
platform: 'node', // Ensure Node.js platform
})Import.meta not working in CJS
Automatic - no configuration needed. If still failing, check output format:
export default defineConfig({
format: ['cjs'], // Shims added automatically
})Tips
1. Enable for Node.js tools - Use shims: true for CLIs and servers 2. Skip for browsers - Not needed for browser code 3. No overhead if unused - Automatically tree-shaken 4. Automatic require shim - No config needed for require in ESM 5. CJS shims automatic - import.meta.* always available in CJS
Related Options
- Platform - Runtime environment
- Output Format - Module formats
- Target - Syntax transformations
Target Environment
Configure JavaScript syntax transformations for target environments.
Overview
The target option controls which JavaScript features are downleveled (transformed to older syntax) for compatibility.
Important: Only affects syntax transformations, not runtime polyfills.
Default Behavior
tsdown auto-reads from package.json:
// package.json
{
"engines": {
"node": ">=18.0.0"
}
}Automatically sets target to node18.0.0.
If no engines.node field exists, behaves as if target: false (no transformations).
Disabling Transformations
Set to false to preserve modern syntax:
export default defineConfig({
target: false,
})Result:
- No JavaScript downleveling
- Modern features preserved (optional chaining
?., nullish coalescing??, etc.)
Use when:
- Targeting modern environments
- Handling transformations elsewhere
- Building libraries for further processing
Setting Target
CLI
# Single target
tsdown --target es2020
tsdown --target node20
# Multiple targets
tsdown --target chrome100 --target node20.18
# Disable
tsdown --no-targetConfig File
export default defineConfig({
entry: ['src/index.ts'],
target: 'es2020',
})Multiple Targets
export default defineConfig({
entry: ['src/index.ts'],
target: ['chrome100', 'safari15', 'node18'],
})Supported Targets
ECMAScript Versions
es2015,es2016,es2017,es2018,es2019,es2020,es2021,es2022,es2023,esnext
Browser Versions
chrome100,safari18,firefox110,edge100, etc.
Node.js Versions
node16,node18,node20,node20.18, etc.
Examples
Modern Browsers
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
target: ['chrome100', 'safari15', 'firefox100'],
})Node.js Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
target: 'node18',
})Legacy Support
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
target: 'es2015', // Maximum compatibility
})Per-Format Targets
export default defineConfig({
entry: ['src/index.ts'],
format: {
esm: {
target: 'es2020',
},
cjs: {
target: 'node16',
},
},
})Decorators
Legacy Decorators (Stage 2)
Enable in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true
}
}Stage 3 Decorators
Not currently supported by tsdown/Rolldown/Oxc.
See oxc issue #9170.
Common Patterns
Universal Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
target: 'es2020', // Wide compatibility
})Modern-Only Library
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
target: false, // No transformations
})Browser Component
export default defineConfig({
entry: ['src/index.tsx'],
format: ['esm'],
target: ['chrome100', 'safari15', 'firefox100'],
platform: 'browser',
})CSS Targeting
When @tsdown/css is installed and a browser target is set, CSS syntax is also lowered automatically:
export default defineConfig({
target: 'chrome108', // CSS nesting will be flattened
})See CSS for full CSS configuration options.
Tips
1. Let tsdown auto-detect from package.json when possible 2. Use `false` for modern-only builds 3. Specify multiple targets for broader compatibility 4. Use legacy decorators with experimentalDecorators 5. Install `@tsdown/css` for CSS support and syntax lowering 6. Test output in target environments
Related Options
- Platform - Runtime environment
- Output Format - Module formats
- Minification - Code optimization
- CSS - CSS handling and preprocessors
Solid Support
Build Solid component libraries with tsdown using rolldown-plugin-solid or unplugin-solid.
Quick Start
npx create-tsdown@latest -t solidConfiguration
import solid from 'rolldown-plugin-solid' // or 'unplugin-solid/rolldown'
import {defineConfig} from 'tsdown'
export default defineConfig({
entry: ['./src/index.ts'],
platform: 'neutral',
dts: true,
plugins: [solid()],
})Dependencies
Install one of:
# Option 1: rolldown-plugin-solid
npm install -D rolldown-plugin-solid
# Option 2: unplugin-solid
npm install -D unplugin-solidKey Points
- Use
platform: 'neutral'for framework-agnostic output dts: truegenerates TypeScript declarations- The Solid plugin handles JSX compilation for Solid's reactive system
Related
- Plugins - Plugin configuration
- Platform - Platform options
Sync Info
- Source:
vendor/tsdown/skills/tsdown - Git SHA:
61514338f92ea5d9f3b8fc3c45d920de75ed1b14 - Synced: 2026-03-16