
Tsdown
- 1.1k installs
- 696 repo stars
- Updated July 27, 2026
- onmax/nuxt-skills
tsdown is a Nuxt ecosystem skill for bundling TypeScript libraries with Rolldown, Unplugin, and Rollup plugin support plus lifecycle hooks for developers who publish typed npm packages with advanced build customization.
About
tsdown is a Nuxt-skills entry for bundling TypeScript libraries with advanced plugin support and lifecycle hooks using the Rolldown engine. It documents supported plugin types: native Rolldown, Unplugin (most unplugin-* packages), Rollup (often with type cast), and some Vite plugins. Examples show defineConfig with UnpluginVue for Rolldown and custom transform plugins that export raw .txt files as modules. Developers reach for tsdown when publishing TS libraries that need Rolldown-speed bundling with hooks beyond a minimal tsc emit. The skill covers plugin composition patterns rather than application routing or SSR setup.
- Native Rolldown support with Unplugin, Rollup, and Vite plugin compatibility
- Custom plugin authoring with transform hooks and type casting for Rollup plugins
- Build lifecycle hooks including build:prepare, build:before, and build:done
- Programmatic API for multiple builds with ESM/CJS, DTS, and exports options
- Function and object syntax for hook registration
Tsdown by the numbers
- 1,114 all-time installs (skills.sh)
- +22 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #171 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/onmax/nuxt-skills --skill tsdownAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 696 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | onmax/nuxt-skills ↗ |
How do you bundle TypeScript libraries with Rolldown plugins?
Bundle TypeScript libraries with advanced plugin support and lifecycle hooks using the Rolldown engine.
Who is it for?
TypeScript library authors who need Rolldown-based bundling with Unplugin or Rollup plugin compatibility and custom transform hooks.
Skip if: Developers building full Nuxt applications, server-only backends, or projects that only need plain tsc compilation without bundling.
When should I use this skill?
A developer asks to bundle a TypeScript library with tsdown, Rolldown plugins, or custom transform lifecycle hooks.
What you get
tsdown config files, bundled library output, and plugin-enabled transform pipelines for TypeScript packages.
- tsdown config
- Bundled library artifacts
By the numbers
- Documents 4 supported plugin categories: Rolldown, Unplugin, Rollup, and Vite
Files
tsdown
Rolldown + Oxc powered TypeScript bundler. Drop-in tsup replacement.
When to Use
- Building TypeScript libraries
- Generating .d.ts declarations
- Publishing npm packages
- Dual ESM/CJS output
- Vue/React component libraries
Quick Start
npm i -D tsdown typescript// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: 'src/index.ts',
format: 'esm',
dts: true,
exports: true,
})tsdown # Build
tsdown --watch # Watch modeReference Files
| Task | File |
|---|---|
| Config file, CLI, entry points | config.md |
| Format, target, dts, exports, validation | output.md |
| Shims, unbundle, watch, frameworks, WASM | features.md |
| Plugins, hooks, lint, programmatic, migration | advanced.md |
Loading Files
Consider loading these reference files based on your task:
- [ ] references/config.md - if setting up tsdown.config.ts, CLI, or entry points
- [ ] references/output.md - if configuring output format, target, .d.ts, exports, or validation
- [ ] references/features.md - if using shims, unbundle, watch mode, framework integrations, or WebAssembly
- [ ] references/advanced.md - if writing plugins, using linting/validation, programmatic API, or migrating from tsup
DO NOT load all files at once. Load only what's relevant to your current task.
Cross-Skill References
- Library patterns → Use
ts-libraryskill - Vue component libs → Use
vueskill - Package management → Use
pnpmskill
Advanced
Plugins
Supported Types
- Rolldown - Native support
- Unplugin - Most
unplugin-*work - Rollup - Most work (may need type cast)
- Vite - May work if not using Vite internals
import UnpluginVue from 'unplugin-vue/rolldown'
import SomeRollupPlugin from 'some-rollup-plugin'
defineConfig({
plugins: [
UnpluginVue({ isProduction: true }),
SomeRollupPlugin() as any, // Type cast for Rollup plugins
],
})Custom Plugin
defineConfig({
plugins: [
{
name: 'my-plugin',
transform(code, id) {
if (id.endsWith('.txt')) {
return `export default ${JSON.stringify(code)}`
}
},
},
],
})See Rolldown Plugin API.
Hooks
Build lifecycle:
// Object syntax
defineConfig({
hooks: {
'build:prepare': (context) => {
console.log('Preparing build...')
},
'build:before': (context, format) => {
console.log(`Building ${format}...`)
},
'build:done': async (context) => {
await generateDocs()
console.log('Build complete!')
},
},
})
// Function syntax
defineConfig({
hooks(hooks) {
hooks.hook('build:prepare', () => {
console.log('Starting...')
})
},
})Programmatic API
import { build } from 'tsdown'
await build({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
outDir: 'dist',
dts: true,
exports: true,
})Multiple builds:
await build([
{ entry: ['src/node.ts'], platform: 'node', outDir: 'dist/node' },
{ entry: ['src/browser.ts'], platform: 'browser', outDir: 'dist/browser' },
])Rolldown Options
inputOptions
defineConfig({
inputOptions: {
cwd: './custom-directory',
resolve: {
mainFields: ['module', 'main'],
alias: { '@': './src' },
},
transform: {
jsx: 'react',
},
},
})Function syntax for format-specific:
defineConfig({
inputOptions(options, format) {
if (format === 'cjs') {
options.cwd = './cjs-specific'
}
return options
},
})outputOptions
defineConfig({
outputOptions: {
legalComments: 'inline', // Preserve license headers
},
})Format-specific:
defineConfig({
outputOptions(options, format) {
if (format === 'esm') {
options.legalComments = 'inline'
}
return options
},
})Migration from tsup
Automatic
npx tsdown-migrate
npx tsdown-migrate packages/* # Monorepo
npx tsdown-migrate --dry-run # PreviewChanged Defaults
| Option | tsup | tsdown |
|---|---|---|
format | - | esm |
clean | false | true |
dts | false | Auto-enabled if types field |
target | - | Reads engines.node |
Breaking Changes (v0.19+)
- Removed:
dts.resolveoption (v0.20+) - Removed:
silentoption - use log levels instead - Renamed:
debugLogs→debug - Renamed:
debug.devtools→devtools.ui - Exports:
exports.legacycontrolsmain/modulefield generation - Exports:
exports.excludenow excludes extension names
No Stub Mode
tsdown doesn't support stub mode. Alternatives:
1. Watch mode: tsdown --watch 2. Dev exports: exports: { devExports: true } 3. TypeScript runners: vite-node, tsx, jiti, Node.js v22.18+
Performance Tips
1. Enable isolatedDeclarations in tsconfig for fast .d.ts 2. Use skipNodeModulesBundle: true if not bundling deps 3. Disable sourcemaps in production if not needed
Package Validation
publint
Validates package.json exports configuration:
defineConfig({
publint: true, // Enable
publint: 'warning', // Set severity: 'warning' | 'error' | 'suggestion'
publint: 'ci-only', // Run only in CI
})tsdown --publintattw (Are The Types Wrong?)
Validates TypeScript declarations across module resolutions:
defineConfig({
attw: {
profile: 'strict', // 'strict' | 'node16' | 'esm-only'
level: 'error', // 'warn' | 'error'
ignoreRules: ['false-cjs', 'named-exports'],
},
attw: 'ci-only', // Run only in CI
})tsdown --attwDebugging
# Verbose output
DEBUG=tsdown:* tsdown
# Dry run (migration)
npx tsdown-migrate --dry-runDevtools
Rolldown devtools for debugging:
defineConfig({
devtools: {
ui: true, // Enable UI (renamed from debug.devtools)
},
})Configuration & CLI
Config File
Supported files (searched in order):
tsdown.config.ts/.mts/.ctstsdown.config.js/.mjs/.cjstsdown.config.jsontsdownfield inpackage.json
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: 'src/index.ts',
dts: true,
exports: true,
})Multiple Configurations
import { defineConfig } from 'tsdown'
export default defineConfig([
{
entry: 'src/node.ts',
platform: 'node',
},
{
entry: 'src/browser.ts',
platform: 'browser',
},
])Entry Points
// Single entry
defineConfig({
entry: 'src/index.ts',
})
// Multiple entries
defineConfig({
entry: ['src/entry1.ts', 'src/entry2.ts'],
})
// Entry aliases → dist/main.js, dist/utils.js
defineConfig({
entry: {
main: 'src/index.ts',
utils: 'src/utils.ts',
},
})
// Glob patterns
defineConfig({
entry: 'src/**/*.ts',
})
// Mixed array and object entries
defineConfig({
entry: [
'src/index.ts',
{ cli: 'src/cli.ts' },
],
})CLI Commands
tsdown # Build
tsdown src/index.ts # Specify entry
tsdown --watch # Watch mode
tsdown -w
tsdown --config ./path/to/config
tsdown --no-config # Skip config fileCLI Options
# Output
--format esm|cjs|iife|umd
--format esm --format cjs # Multiple formats
-d ./build # Output dir
--out-dir ./build
# Declaration files
--dts
# Target
--target es2020
--target node20
--no-target # Preserve modern syntax
# Platform
--platform node|browser|neutral
# Optimization
--minify
--sourcemap
--treeshake
--no-treeshake
# Cleaning
--clean # Default: true
--no-clean
# External
--external lodash
--external "@my-scope/*"
# Watch
--watch [path]
--ignore-watch node_modules
# Env
--env.NODE_ENV=production
--env-file .env.production
--env-prefix APP_
# Post-build
--on-success "echo Done!"
# Copy assets
--copy public
# Package exports
--exports
# Validation
--publint
--attwCLI Flag Patterns
--foo # foo: true
--no-foo # foo: false
--foo.bar # foo: { bar: true }
--format esm --format cjs # format: ['esm', 'cjs']Config Loaders
tsdown --config-loader auto # Default
tsdown --config-loader native # Node.js TypeScript
tsdown --config-loader unrun # More powerfulExtend Vite/Vitest Config
Reuse resolve and plugins:
tsdown --from-vite # Load vite.config.*
tsdown --from-vite vitest # Load vitest.config.*Common Configurations
Pure ESM Library
defineConfig({
entry: 'src/index.ts',
format: 'esm',
dts: true,
exports: true,
target: 'es2020',
})Dual ESM/CJS
defineConfig({
entry: 'src/index.ts',
format: ['esm', 'cjs'],
dts: true,
exports: true,
})Node.js CLI
defineConfig({
entry: 'src/cli.ts',
format: 'esm',
platform: 'node',
dts: true,
shims: true, // __dirname, __filename in ESM
})Browser Library
defineConfig({
entry: 'src/index.ts',
format: ['esm', 'iife'],
platform: 'browser',
minify: true,
})Features
Tree Shaking
Enabled by default - removes unused code.
defineConfig({
treeshake: true, // Default
treeshake: false, // Disable
})Minification
defineConfig({
minify: true,
})Uses Oxc (fast, currently alpha).
Source Maps
defineConfig({
sourcemap: true,
})Auto-enabled if declarationMap: true in tsconfig.json.
Shims
CJS in ESM
__dirname and __filename not available in ESM:
defineConfig({
shims: true, // Provides __dirname, __filename
})require in ESM
Auto-injected when platform: 'node':
// Generated:
const require = createRequire(import.meta.url)ESM in CJS
Always enabled - import.meta.url, import.meta.dirname, import.meta.filename work in CJS.
CJS Default Export
When CJS output has only default export:
// Source
export default function greet() {}
// CJS Output
module.exports = greet
// Declaration
declare function greet(): void
export = greetUnbundle Mode
Transpile-only - preserves file structure:
defineConfig({
entry: ['src/index.ts'],
unbundle: true,
})Input:
src/
index.ts
mod.tsOutput:
dist/
index.js
mod.jsWatch Mode
defineConfig({
watch: true,
// Or specific paths
watch: ['./src', './lib'],
})tsdown --watch
tsdown --watch ./src
tsdown --ignore-watch node_modulesPost-Build Command
tsdown --watch --on-success "node dist/index.mjs"Copy Assets
tsdown --copy publicEnvironment Variables
tsdown --env.NODE_ENV=production
tsdown --env-file .env.production
tsdown --env-prefix APP_Node Protocol
Control Node.js built-in imports:
defineConfig({
nodeProtocol: true, // fs → node:fs
nodeProtocol: 'strip', // node:fs → fs
nodeProtocol: false, // Keep as-is (default)
})CSS
defineConfig({
css: {
splitting: false, // Single CSS file
fileName: 'styles.css',
},
})CSS targeting (requires unplugin-lightningcss):
pnpm add -D unplugin-lightningcssJSX
Built-in support via Rolldown:
defineConfig({
inputOptions: {
transform: {
jsx: 'react', // Classic transform
},
},
})Framework Support
React
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['./src/index.ts'],
platform: 'neutral',
dts: true,
})React Compiler:
pnpm add -D @rollup/plugin-babel babel-plugin-react-compilerimport pluginBabel from '@rollup/plugin-babel'
defineConfig({
plugins: [
pluginBabel({
babelHelpers: 'bundled',
parserOpts: {
sourceType: 'module',
plugins: ['jsx', 'typescript'],
},
plugins: ['babel-plugin-react-compiler'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
],
})Vue
pnpm add -D unplugin-vue vue-tscimport { defineConfig } from 'tsdown'
import Vue from 'unplugin-vue/rolldown'
export default defineConfig({
entry: ['./src/index.ts'],
platform: 'neutral',
plugins: [Vue({ isProduction: true })],
dts: { vue: true },
})WebAssembly
pnpm add -D rolldown-plugin-wasmimport { defineConfig } from 'tsdown'
import wasm from 'rolldown-plugin-wasm'
export default defineConfig({
plugins: [
wasm({
maxFileSize: 14000, // Inline if < 14KB
targetEnv: 'auto', // 'auto' | 'node' | 'browser'
}),
],
})Import methods:
import { add } from './add.wasm' // Direct
import init from './module.wasm?init' // Async
import initSync from './module.wasm?init&sync' // SyncSupports wasm-bindgen for bundler and web targets.
Quick Start Templates
npx create-tsdown@latest -t vue
npx create-tsdown@latest -t react
npx create-tsdown@latest -t react-compilerOutput Options
Format
Default: esm
defineConfig({
format: 'esm', // ECMAScript Modules
format: 'cjs', // CommonJS
format: 'iife', // Browser script
format: 'umd', // Universal
format: ['esm', 'cjs'], // Multiple
})Format-specific config:
defineConfig({
format: {
esm: { target: ['es2015'] },
cjs: { target: ['node20'] },
},
})Output Directory
defineConfig({
outDir: 'dist', // Default
})tsdown -d ./build
tsdown --out-dir ./buildTarget
Syntax downleveling (no polyfills):
defineConfig({
target: 'es2020',
target: 'node20',
target: ['chrome100', 'firefox90'],
})Default: reads from engines.node in package.json.
Disable transformations:
defineConfig({
target: false, // Preserve modern syntax
})Platform
defineConfig({
platform: 'node', // Default, Node.js built-ins external
platform: 'browser', // Web browsers
platform: 'neutral', // Platform-agnostic
})Module resolution:
- node:
mainFields: ['main', 'module'] - browser:
mainFields: ['browser', 'module', 'main'] - neutral: relies on
exportsfield only
Declaration Files (.d.ts)
defineConfig({
dts: true, // Enable
})Auto-enabled if types or typings in package.json.
Performance with isolatedDeclarations
For fastest .d.ts generation:
// tsconfig.json
{
"compilerOptions": {
"isolatedDeclarations": true
}
}Uses oxc-transform instead of slower TypeScript.
Declaration Maps
defineConfig({
dts: {
sourcemap: true, // .d.ts.map files
},
})Or via tsconfig.json:
{
"compilerOptions": {
"declarationMap": true
}
}Vue Support
defineConfig({
dts: { vue: true }, // Requires vue-tsc
})Dependencies
Default Behavior
| Type | Bundled? |
|---|---|
dependencies | No (external) |
peerDependencies | No (external) |
devDependencies | Yes (if imported) |
| Phantom deps | Yes (if imported) |
Skip All node_modules
defineConfig({
skipNodeModulesBundle: true,
})Inline-Only Mode
Warn if dependencies are bundled (useful for libraries):
defineConfig({
inlineOnly: true, // Shows warnings for bundled deps
})Custom External
defineConfig({
external: ['lodash', /^@my-scope\//],
})Force Bundle
defineConfig({
noExternal: ['some-package'], // Bundle despite being in deps
})Package Exports
Auto-generate exports, main, module, types:
defineConfig({
exports: true,
})Export All Files
defineConfig({
exports: {
all: true, // Not just entry files
},
})Legacy Support
Control main and module field generation:
defineConfig({
exports: {
legacy: true, // Default, generates main/module fields
legacy: false, // Pure ESM, omits main/module if only ESM format
},
})Auto-fills types field only when legacy: true.
Dev Exports
Point to source during development:
defineConfig({
exports: {
devExports: true,
},
})exports → source, publishConfig.exports → built.
CSS Exports
defineConfig({
css: {
splitting: false,
fileName: 'my-library.css',
},
exports: true,
})Custom Exports
defineConfig({
exports: {
customExports(pkg, context) {
pkg['./foo'] = './foo.js'
return pkg
},
},
})Cleaning
Output directory cleaned by default:
defineConfig({
clean: true, // Default
clean: false, // Keep existing
})Example package.json
{
"name": "my-lib",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": ["dist"]
}Related skills
How it compares
Choose tsdown when publishing TypeScript libraries that need Rolldown-speed bundling with Unplugin compatibility instead of Vite app-centric builds.
FAQ
Which plugin types does tsdown support?
tsdown supports native Rolldown plugins, most Unplugin packages, most Rollup plugins (sometimes with a type cast), and some Vite plugins that do not rely on Vite internals.
How do custom tsdown plugins transform files?
tsdown custom plugins implement transform hooks inside defineConfig. For example, a plugin can detect .txt files and emit export default JSON-stringified module code.
Is Tsdown safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.