--- url: /options/package-exports.md --- # Auto-Generating Package Exports `tsdown` can automatically infer and generate the `exports` field in your `package.json`. This helps ensure your package exports are always up-to-date and correctly reflect your build outputs. Top-level `main`, `module`, and `types` fields are controlled by the `exports.legacy` option, which defaults to `false` for ESM-only builds and `true` otherwise. ## Enabling Auto Exports You can enable this feature by setting the `exports: true` option in your `tsdown` configuration file: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ exports: true, }) ``` This will automatically analyze your entry points and output files, and update the `exports` field in your `package.json` accordingly. > \[!WARNING] > Please review the generated exports before publishing your package, or enable publint for validation. ## Exporting All Files By default, only entry files are exported. If you want to export all files (including those not listed as entry points), you can enable the `exports.all` option: ```ts export default defineConfig({ exports: { all: true, }, }) ``` This will include all relevant files in the generated `exports` field. ## Legacy Package Fields The `exports.legacy` option controls whether top-level `main`, `module`, and `types` fields are also generated for older tools. It defaults to `false` when only ESM output is built, and `true` when other formats (such as CJS) are included. Set it explicitly to override: ```ts export default defineConfig({ exports: { legacy: true, }, }) ``` ## Dev-Time Source Linking ### Dev Exports {#dev-exports} During development, you may want your `exports` to point directly to your source files for better debugging and editor support. You can enable this by setting `exports.devExports` to `true`: ```ts export default defineConfig({ exports: { devExports: true, }, }) ``` With this setting, the generated `exports` in your `package.json` will link to your source code. The exports for the built output will be written to `publishConfig`, which will override the top-level `exports` field when using `yarn` or `pnpm`'s `pack`/`publish` commands (note: this is **not supported by npm**). ### Conditional Dev Exports You can also set `exports.devExports` to a string to only link to source code under a specific [condition](https://nodejs.org/api/packages.html#conditional-exports): ```ts export default defineConfig({ exports: { devExports: '@my-org/source', }, }) ``` This is especially useful when combined with TypeScript's [`customConditions`](https://www.typescriptlang.org/tsconfig/#customConditions) option, allowing you to control which conditions use the source code. ## CSS Exports When `css.splitting` is `false`, the bundled CSS file is automatically added to `exports`: ```ts export default defineConfig({ css: { splitting: false, }, exports: true, }) ``` The CSS filename defaults to `style.css` and can be customized via `css.fileName`. ## Customizing Exports If you need more control over the generated exports, you can provide an object or a custom function via `exports.customExports`: ```ts export default defineConfig({ exports: { customExports: { './foo': './foo.js', }, }, }) ``` ```ts export default defineConfig({ exports: { customExports(exports, context) { exports['./foo'] = './foo.js' return exports }, }, }) ``` --- --- url: /advanced/benchmark.md --- # Benchmark `tsdown` delivers exceptional performance compared to other popular bundlers. In most cases, it is approximately **2 times faster** than `tsup` for standard builds, and up to **8 times faster** when generating TypeScript declaration files. For detailed comparisons and real-world results, see [bundler-benchmark](https://gugustinette.github.io/bundler-benchmark/). --- --- url: /advanced/ci.md --- # CI Environment Support tsdown automatically detects CI environments and allows you to enable or disable specific features depending on whether the build runs locally or in CI. ## CI Detection tsdown detects CI from the `CI` environment variable. CI mode is enabled when `process.env.CI` is set to a value other than `0` or `false` (case-insensitive). ## CI-Aware Options Several options support CI-aware behavior through the `'ci-only'` and `'local-only'` 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 The following options accept CI-aware values: * [`dts`](/options/dts) — TypeScript declaration file generation * [`publint`](/options/lint) — Package lint validation * [`attw`](/options/lint) — "Are the types wrong" validation * `report` — Bundle size reporting * [`exports`](/options/package-exports) — Auto-generate `package.json` exports * `unused` — Unused dependency check * `devtools` — DevTools integration * [`exe`](/options/exe) — Standalone executable generation * `failOnWarn` — Fail on warnings (defaults to `false`) ### Basic Usage Pass a CI option string directly: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ // Only generate declaration files locally (skip in CI for faster builds) dts: 'local-only', // Only run publint in CI publint: 'ci-only', // Fail on warnings in CI only failOnWarn: 'ci-only', }) ``` ### Object Form When an option takes a configuration object, you can set the `enabled` property to a CI-aware value: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' 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, allowing dynamic configuration: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig((_, { ci }) => ({ minify: ci, sourcemap: !ci, })) ``` ## Example: CI Pipeline A typical CI-optimized configuration: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ entry: 'src/index.ts', format: ['esm', 'cjs'], dts: true, // Fail on warnings in CI (opt-in) failOnWarn: 'ci-only', // Run package validators in CI publint: 'ci-only', attw: 'ci-only', }) ``` --- --- url: /options/cjs-default.md --- # CJS Default Export The `cjsDefault` option helps improve compatibility when generating CommonJS (CJS) entry modules. This option is **enabled by default**. ## How It Works When an explicit entry module has **only a single default export** and the output format is set to CJS, `tsdown` will automatically transform: * `export default ...` into `module.exports = ...` in the generated JavaScript file. For TypeScript declaration files (`.d.ts`), it will transform: * `export default ...` into `export = ...` This ensures that consumers using CommonJS require syntax (`require('your-module')`) will receive the default export directly, improving interoperability with tools and environments that expect this behavior. > \[!NOTE] > `cjsDefault` only applies to explicit entry modules. In [unbundle mode](./unbundle.md), imported modules that are emitted as non-entry chunks keep named CJS exports such as `exports.default`. CJS is considered legacy and is supported in maintenance-only mode, so this behavior will not be extended to non-entry chunks. If every source module is intended to be consumed independently, include all of them as entries: ```ts import { defineConfig } from 'tsdown' export default defineConfig({ entry: ['src/**/*.ts'], root: 'src', format: 'cjs', unbundle: true, }) ``` ## Example **Source Module:** ```ts // src/index.ts export default function greet() { console.log('Hello, world!') } ``` **Generated CJS Output:** ```js // dist/index.cjs function greet() { console.log('Hello, world!') } module.exports = greet ``` **Generated Declaration File:** ```ts // dist/index.d.cts declare function greet(): void export = greet ``` --- --- url: /options/cleaning.md --- # Cleaning By default, `tsdown` will **clean the output directory** (`outDir`) before each build. This ensures that any files from previous builds are removed, preventing outdated or unused files from remaining in your output. If you want to disable this behavior and keep existing files in the output directory, you can use the `--no-clean` option: ```bash tsdown --no-clean ``` > \[!NOTE] > By default, all files in the output directory will be removed before the build process begins. Make sure this behavior aligns with your project requirements to avoid accidentally deleting important files. --- --- url: /reference/cli.md --- # Command Line Interface All CLI flags can also be set in the configuration file, which improves reusability and maintainability for complex projects. Conversely, any option can be overridden by CLI flags, even if not explicitly listed on this page. For more details, see the [Config File](../options/config-file.md) documentation. ## CLI Flag Patterns The mapping between CLI flags and configuration options follows these rules: * `--foo` sets `foo: true` * `--no-foo` sets `foo: false` * `--foo.bar` sets `foo: { bar: true }` * `--format esm --format cjs` sets `format: ['esm', 'cjs']` CLI flags support both camelCase and kebab-case. For example, `--outDir` and `--out-dir` are equivalent. Nested keys follow the same rule, so `--deps.never-bundle` and `--deps.neverBundle` are equivalent as well. Options whose keys are defined by you — `--env.*`, `--define.*`, `--alias.*`, `--entry.*` and `--loader.*` — are the exception: their keys are used exactly as written. This flexible pattern allows you to easily control and override configuration options directly from the command line. ## `[...files]` Specify entry files as command arguments. This is equivalent to setting the `entry` option in the configuration file. For example: ```bash tsdown src/index.ts src/util.ts ``` This will bundle `src/index.ts` and `src/util.ts` as separate entry points. See the [Entry](../options/entry.md) documentation for more details. ## `-c, --config ` Specify a custom configuration file. Use this option to define the path to the configuration file you want to use. See also [Config File](../options/config-file.md). ## `--config-loader ` Specifies which config loader to use: `auto` (default), `native`, `tsx`, or `unrun`. See also [Config File](../options/config-file.md). ## `--no-config` Disable loading a configuration file. This is useful if you want to rely solely on command-line options or default settings. See also [Disabling the Config File](../options/config-file.md#disable-config-file). ## `--tsconfig ` Specify the path or filename of your `tsconfig` file. `tsdown` will search upwards from the current directory to find the specified file. By default, it uses `tsconfig.json`. ```bash tsdown --tsconfig tsconfig.build.json ``` ## `-f, --format ` Define the bundle format. Supported formats include: * `esm` (ECMAScript Modules) * `cjs` (CommonJS) * `iife` (Immediately Invoked Function Expression) * `umd` (Universal Module Definition) See also [Output Format](../options/output-format.md). ## `--clean` Clean the output directory before building. This removes all files in the output directory to ensure a fresh build. Enabled by default; use `--no-clean` to disable. See also [Cleaning](../options/cleaning.md). ## `--deps.never-bundle ` Mark a module as external. This prevents the specified module from being included in the bundle. See also [Dependencies](../options/dependencies.md). ## `--external ` {#external} ::: warning Deprecated Use `--deps.never-bundle` instead. ::: Alias for `--deps.never-bundle`. ## `--minify` Enable minification of the output bundle to reduce file size. Minification removes unnecessary characters and optimizes the code for production. See also [Minification](../options/minification.md). ## `--target ` Specify the JavaScript target version for the bundle. Examples include: * `es2015` * `esnext` * `chrome100` * `node18` You can also disable all syntax transformations by using `--no-target` or by setting the target to `false` in your configuration file. See also [Target](../options/target.md). ## `-l, --log-level ` Set the log level to control the verbosity of logs during the build process. Available levels: `info`, `warn`, `error`, `silent`. See also [Log Level](../options/log-level.md). ## `-d, --out-dir ` Specify the output directory for the bundled files. Use this option to customize where the output files are written. See also [Output Directory](../options/output-directory.md). ## `--root ` Specify the root directory of input files. See also [Root Directory](../options/root.md). ## `--treeshake`, `--no-treeshake` Enable or disable tree shaking. Tree shaking removes unused code from the final bundle, reducing its size and improving performance. See also [Tree Shaking](../options/tree-shaking.md). ## `--sourcemap` Generate source maps for the bundled files. Source maps help with debugging by mapping the output code back to the original source files. See also [Source Maps](../options/sourcemap.md). ## `--shims` Enable CommonJS (CJS) and ECMAScript Module (ESM) shims. This ensures compatibility between different module systems. See also [Shims](../options/shims.md). ## `--platform ` Specify the target platform for the bundle. Supported platforms include: * `node` (Node.js) * `browser` (Web browsers) * `neutral` (Platform-agnostic) See also [Platform](../options/platform.md). ## `--dts` Generate TypeScript declaration (`.d.ts`) files for the bundled code. This is useful for libraries that need to provide type definitions. See also [Declaration Files](../options/dts.md). ## `--publint` Enable `publint` to validate your package for publishing. This checks for common issues in your package configuration, ensuring it meets best practices. See also [Package Validation](../options/lint.md). ## `--attw` Enable [Are the types wrong?](https://github.com/arethetypeswrong/arethetypeswrong.github.io) integration to check your package's TypeScript types for compatibility issues. See also [Package Validation](../options/lint.md). ## `--unused` Enable unused dependencies checking. This helps identify dependencies in your project that are not being used, allowing you to clean up your `package.json`. ## `-w, --watch [path]` Enable watch mode to automatically rebuild your project when files change. Optionally, specify a path to watch for changes. See also [Watch Mode](../options/watch-mode.md). ## `--ignore-watch ` Ignore custom paths in watch mode. ## `--from-vite [vitest]` Reuse configuration from Vite or Vitest. This allows you to extend or integrate with existing Vite or Vitest configurations seamlessly. See also [Extending Vite or Vitest Config](../options/config-file.md#extending-vite-or-vitest-config-experimental). ## `--report`, `--no-report` Enable or disable the generation of a build report. By default, the report is enabled and outputs the list of build artifacts along with their sizes to the console. This provides a quick overview of the build results, helping you analyze the output and identify potential optimizations. Disabling the report can be useful in scenarios where minimal console output is desired. ## `--devtools` Enable Vite DevTools integration for bundle analysis. ## `--env.* ` Define compile-time environment variables, for example: ```bash tsdown --env.NODE_ENV=production ``` Note that environment variables defined with `--env.VAR_NAME` can only be accessed as `import.meta.env.VAR_NAME` or `process.env.VAR_NAME`. ## `--env-file ` Load environment variables from a file. When used together with `--env`, variables in `--env` take precedence. :::tip To prevent accidental exposure of sensitive information, only environment variables prefixed with `TSDOWN_` are injected by default. You can customize this behavior using the [`--env-prefix`](#env-prefix) flag. ::: ```bash tsdown --env-file .env.production ``` ## `--env-prefix ` {#env-prefix} When loading environment variables from a file via `--env-file`, only include variables that start with these prefixes. * **Default:** `TSDOWN_` ```bash tsdown --env-file .env --env-prefix APP_ --env-prefix TSDOWN_ ``` ## `--debug [feat]` Show debug logs. ## `--on-success ` Specify a command to run after a successful build. This is especially useful in watch mode to trigger additional scripts or actions automatically after each build completes. ```bash tsdown --on-success "echo Build finished!" ``` ## `--copy ` Copies all files from the specified directory to the output directory. This is useful for including static assets such as images, stylesheets, or other resources in your build output. ```bash tsdown --copy public ``` All contents of the `public` directory will be copied to your output directory (e.g., `dist`). ## `--exe` **\[experimental]** Bundle as a standalone executable using [Node.js Single Executable Applications](https://nodejs.org/api/single-executable-applications.html). This will bundle the output into a single executable file. Requires Node.js 25.7.0 or later (in practice Node.js 26+, since tsdown itself does not support Node.js 25), and is not supported in Bun or Deno. Cross-platform builds are supported via the `@tsdown/exe` package. When `exe` is enabled: * Declaration file generation (`dts`) is disabled by default. * Code splitting is disabled. * Only single entry points are supported. See also [Executable](../options/exe.md). ## `-W, --workspace [dir]` Enable workspace mode for building multiple packages in a monorepo. Optionally specify the workspace root directory. ## `--concurrency ` Maximum number of Rolldown builds to run in parallel. Defaults to unlimited. Not supported in watch mode, where it is ignored. ## `-F, --filter ` Filter configs by working directory or name. Supports string matching and regex patterns (e.g., `/pkg-name$/` or `pkg-name`). ## `--unbundle` Enable unbundle (bundleless) mode. Each source file is compiled individually, preserving the source directory structure in the output. See also [Unbundle](../options/unbundle.md). ## `--fail-on-warn` Fail the build when warnings are encountered. Enabled by default. See also [CI Environment](../advanced/ci.md). ## `--no-write` Disable writing output files to disk. Incompatible with watch mode. ## `--exports` Generate the `exports` field in your `package.json`. See also [Package Exports](../options/package-exports.md). --- --- url: /options/config-file.md --- # Config File By default, `tsdown` will search for a configuration file by looking in the current working directory and traversing upward through parent directories until it finds one. It supports the following file names: * `tsdown.config.ts` * `tsdown.config.mts` * `tsdown.config.cts` * `tsdown.config.js` * `tsdown.config.mjs` * `tsdown.config.cjs` * `tsdown.config.json` Additionally, you can define your configuration directly in the `tsdown` field of your `package.json` file. ## Writing a Config File The configuration file allows you to define and customize your build settings in a centralized and reusable way. Below is a simple example of a `tsdown` configuration file: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ entry: 'src/index.ts', }) ``` ### Building Multiple Outputs `tsdown` also supports returning an **array of configurations** from the config file. This allows you to build multiple outputs with different settings in a single run. For example: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig([ { entry: 'src/entry1.ts', platform: 'node', }, { entry: 'src/entry2.ts', platform: 'browser', }, ]) ``` ## Specifying a Custom Config File If your configuration file is located elsewhere or has a different name, you can specify its path using the `--config` (or `-c`) option: ```bash tsdown --config ./path/to/config ``` ## Disabling the Config File {#disable-config-file} To disable loading a configuration file entirely, use the `--no-config` option: ```bash tsdown --no-config ``` This is useful if you want to rely solely on command-line options or default settings. ## Config Loaders `tsdown` supports multiple config loaders to accommodate various file formats. You can select a config loader using the `--config-loader` option. The available loaders are: * `auto` (default): Utilizes native runtime loading for TypeScript if supported; otherwise, defaults to `unrun`. * `native`: Loads TypeScript configuration files using native runtime support. Requires a compatible environment, such as Node.js 22.18.0+, Deno, or Bun. * `tsx`: Loads configuration files using the [`tsx`](https://tsx.is/) library via its [tsImport API](https://tsx.is/dev-api/ts-import). Note that `tsx` is an optional peer dependency — you need to install it manually if you want to use this loader. * `unrun`: Loads configuration files using the [`unrun`](https://gugustinette.github.io/unrun/) library. It provides more powerful and flexible loading capabilities. Note that `unrun` is an optional peer dependency — you need to install it manually if you want to use this loader. > \[!TIP] > Node.js does not natively support importing TypeScript files without specifying the file extension. If you are using Node.js and want to load a TypeScript config file without including the `.ts` extension, consider installing and using the `tsx` or `unrun` loader for seamless compatibility. ## Extending Vite or Vitest Config (Experimental) `tsdown` provides an **experimental** feature to extend your existing Vite or Vitest configuration files. This allows you to reuse specific configuration options, such as `resolve` and `plugins`, while ignoring others that are not relevant to `tsdown`. To enable this feature, use the `--from-vite` option: ```bash tsdown --from-vite # Load vite.config.* tsdown --from-vite vitest # Load vitest.config.* ``` > \[!WARNING] > This feature is **experimental** and may not support all Vite or Vitest configuration options. Only specific options, such as `resolve` and `plugins`, are reused. Use with caution and test thoroughly in your project. > \[!TIP] > Extending Vite or Vitest configurations can save time and effort if your project already uses these tools, allowing you to build upon your existing setup without duplicating configuration. ## Reference For a full list of available configuration options, refer to the [Config Options Reference](../reference/api/Interface.UserConfig.md). This includes detailed explanations of all supported fields and their usage. --- --- url: /options/copy.md --- # Copy Files The `copy` option copies static files and directories into your build output. It is useful for assets that should be distributed without being processed by the bundler, such as images, fonts, and license files. ## Using the CLI Pass a directory to `--copy` to copy it into the output directory: ```bash tsdown --copy public ``` With the default `outDir` of `dist`, a file at `public/favicon.svg` is copied to `dist/public/favicon.svg`. ## Using the Config File The simplest configuration is a path or glob pattern: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ copy: 'public', }) ``` You can provide multiple paths, glob patterns, or object entries in an array: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ copy: [ 'LICENSE', { from: ['public/**/*', '!public/**/*.map'], to: 'dist/assets', flatten: false, }, ], }) ``` Glob patterns support negation with a leading `!`. In this example, files below `public` are copied to `dist/assets` with their relative directory structure preserved, while source map files are excluded. > \[!NOTE] > Relative source and destination paths are resolved from the project root > (`cwd`), not from `outDir`. ## Object Options Object entries support the following properties: | Property | Type | Default | Description | | --------- | --------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `from` | `string \| string[]` | Required | A source path or glob pattern. An array can include negated patterns. | | `to` | `string` | `outDir` | The destination path, resolved from the project root. | | `flatten` | `boolean` | `true` | Places matched files directly in `to`. Set to `false` to preserve the directory structure below the first segment. | | `rename` | `string \| ((name, extension, fullPath) => string)` | — | Changes the destination name. The callback receives the extension without a leading dot and the absolute path. | | `verbose` | `boolean` | `false` | Logs each copied source and destination. | ### Preserve Directory Structure Glob matches are flattened into the destination by default. Set `flatten: false` to keep their relative structure: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ copy: { from: 'assets/**/*', to: 'dist/public', flatten: false, }, }) ``` For example, `assets/fonts/inter.woff2` is copied to `dist/public/fonts/inter.woff2`. ### Rename Copied Items Use a string or a callback to rename copied items: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ copy: [ { from: 'src/file.txt', to: 'dist', rename: 'file.md', }, { from: 'src/file.txt', to: 'dist', rename: (name, extension) => `${name}-renamed.${extension}`, }, ], }) ``` This creates `dist/file.md` and `dist/file-renamed.txt`. ## Dynamic Configuration `copy` can also be a synchronous or asynchronous callback. It receives the resolved tsdown configuration and returns the same string, object, or array forms: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ copy: ({ outDir }) => ({ from: ['assets/**/*', '!assets/**/*.map'], to: `${outDir}/assets`, flatten: false, }), }) ``` ## Build and Watch Behavior Copying runs after the bundle output is produced. Static files are copied without passing through Rolldown transforms. A top-level `copy` option runs only once for a multi-format build; a format-specific override can define `copy` again for that format. In watch mode, matched source files are watched as build dependencies. Changing one triggers a rebuild and copies the current files again. --- --- url: /options/css.md --- # CSS Support CSS support in `tsdown` is still an experimental feature. While it covers the core use cases, the API and behavior may change in future releases. > \[!WARNING] Experimental Feature > CSS support is experimental. Please test thoroughly and report any issues you encounter. The API and behavior may change as the feature matures. ## Getting Started All CSS support in `tsdown` is provided by the `@tsdown/css` package. Install it to enable CSS handling: ```bash npm install -D @tsdown/css ``` When `@tsdown/css` is installed, CSS processing is automatically enabled. ## CSS Import Importing `.css` files from your TypeScript or JavaScript entry points is supported. The CSS content is extracted and emitted as a separate `.css` asset file: ```ts // src/index.ts import './style.css' export function greet() { return 'Hello' } ``` This produces both `index.mjs` and `index.css` in the output directory. ### `@import` Inlining CSS `@import` statements are automatically resolved and inlined into the output. This means you can use `@import` to organize your CSS across multiple files without producing separate output files: ```css /* style.css */ @import './reset.css'; @import './theme.css'; .main { color: red; } ``` All imported CSS is bundled into a single output file with `@import` statements removed. ### Inline CSS (`?inline`) Appending `?inline` to a CSS import returns the fully processed CSS as a JavaScript string instead of emitting a separate `.css` file. This aligns with [Vite's `?inline` behavior](https://vite.dev/guide/features#disabling-css-injection-into-the-page): ```ts import css from './theme.css?inline' // Returns processed CSS as a string import './style.css' // Extracted to a .css file console.log(css) // ".theme { color: red; }\n" ``` The `?inline` CSS goes through the full processing pipeline — preprocessors, `@import` inlining, syntax lowering, and minification — just like regular CSS. The only difference is the output format: a JavaScript string export instead of a CSS asset file. This also works with preprocessors: ```ts import css from './theme.scss?inline' ``` When `?inline` is used, the CSS is not included in the emitted `.css` files and the import is tree-shakeable (`moduleSideEffects: false`). ## CSS Pre-processors `tsdown` provides built-in support for `.scss`, `.sass`, `.less`, `.styl`, and `.stylus` files. The corresponding pre-processor must be installed as a dev dependency: ::: code-group ```sh [Sass] # Either sass-embedded (recommended, faster) or sass npm install -D sass-embedded # or npm install -D sass ``` ```sh [Less] npm install -D less ``` ```sh [Stylus] npm install -D stylus ``` ::: Once installed, you can import preprocessor files directly: ```ts import './style.scss' import './theme.less' import './global.styl' ``` ### Preprocessor Options You can pass options to each preprocessor via `css.preprocessorOptions`: ```ts export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: `$brand-color: #ff7e17;`, }, less: { math: 'always', }, }, }, }) ``` #### `additionalData` Each preprocessor supports an `additionalData` option to inject extra code at the beginning of every processed file. This is useful for global variables or mixins: ```ts export default defineConfig({ css: { preprocessorOptions: { scss: { // String — prepended to every .scss file additionalData: `@use "src/styles/variables" as *;`, }, }, }, }) ``` You can also use a function for dynamic injection: ```ts export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: (source, filename) => { if (filename.includes('theme')) return source return `@use "src/styles/variables" as *;\n${source}` }, }, }, }, }) ``` ## CSS Minification Enable CSS minification via `css.minify`: ```ts export default defineConfig({ css: { minify: true, }, }) ``` Minification is powered by [Lightning CSS](https://lightningcss.dev/). ## CSS Target By default, CSS syntax lowering uses the top-level [`target`](/options/target) option. You can override this specifically for CSS with `css.target`: ```ts export default defineConfig({ target: 'node18', css: { target: 'chrome90', // CSS-specific target }, }) ``` Set `css.target: false` to disable CSS syntax lowering entirely, even when a top-level `target` is set: ```ts export default defineConfig({ target: 'chrome90', css: { target: false, // Preserve modern CSS syntax }, }) ``` ## CSS Transformer The `css.transformer` option controls how CSS is processed. PostCSS and Lightning CSS are **mutually exclusive** processing paths: * **`'lightningcss'`** (default): `@import` is resolved by Lightning CSS's `bundleAsync()`, and PostCSS is **not used at all**. * **`'postcss'`**: `@import` is resolved by [`postcss-import`](https://github.com/postcss/postcss-import), PostCSS plugins are applied, then Lightning CSS is used only for final syntax lowering and minification. ```ts export default defineConfig({ css: { transformer: 'postcss', // Use PostCSS for @import and plugins }, }) ``` When using the `'postcss'` transformer, install `postcss` and optionally `postcss-import` for `@import` resolution: ```bash npm install -D postcss postcss-import ``` ### PostCSS Options Configure PostCSS inline or point to a config file: ```ts export default defineConfig({ css: { transformer: 'postcss', postcss: { plugins: [require('autoprefixer')], }, }, }) ``` Or specify a directory path to search for a PostCSS config file (`postcss.config.js`, etc.): ```ts export default defineConfig({ css: { transformer: 'postcss', postcss: './config', // Search for postcss.config.js in ./config/ }, }) ``` When `css.postcss` is omitted and `transformer` is `'postcss'`, tsdown auto-detects PostCSS config from the project root. ## Lightning CSS `tsdown` uses [Lightning CSS](https://lightningcss.dev/) for CSS syntax lowering — transforming modern CSS features into syntax compatible with older browsers based on your `target` setting. To enable CSS syntax lowering, install `lightningcss`: ::: code-group ```sh [npm] npm install -D lightningcss ``` ```sh [pnpm] pnpm add -D lightningcss ``` ```sh [yarn] yarn add -D lightningcss ``` ```sh [bun] bun add -D lightningcss ``` ::: Once installed, CSS lowering is enabled automatically when a `target` is set. For example, with `target: 'chrome108'`, CSS nesting `&` selectors will be flattened: ```css /* Input */ .foo { & .bar { color: red; } } /* Output (chrome108) */ .foo .bar { color: red; } ``` ### Lightning CSS Options You can pass additional options to Lightning CSS via `css.lightningcss`: ```ts import { Features } from 'lightningcss' export default defineConfig({ css: { lightningcss: { // Override browser targets directly (instead of using `target`) targets: { chrome: 100 << 16 }, // Include/exclude specific features include: Features.Nesting, }, }, }) ``` > \[!TIP] > When `css.lightningcss.targets` is set, it takes precedence over both the top-level `target` and `css.target` options for CSS transformations. For more information on available options, refer to the [Lightning CSS documentation](https://lightningcss.dev/). ## Preserving CSS Imports (`css.inject`) {#css-inject} By default, CSS import statements are removed from JS output after extracting the CSS into separate files. When `css.inject` is enabled, the JS output preserves `import` statements pointing to the emitted CSS files, so consumers of your library will automatically import the CSS alongside the JS: ```ts export default defineConfig({ css: { inject: true, }, }) ``` With `css.inject: true`, the output JS will contain: ```js // dist/index.mjs import './style.css' export function greet() { return 'Hello' } ``` This is useful for component libraries where you want CSS to be automatically included when users import your components. ## CSS Modules Files with the `.module.css` extension (and preprocessor variants like `.module.scss`, `.module.less`, etc.) are treated as [CSS modules](https://github.com/css-modules/css-modules). Class names are automatically scoped and exported as a JavaScript object: ```ts // src/index.ts import styles from './app.module.css' console.log(styles.title) // "scoped_title_hash" ``` ```css /* app.module.css */ .title { color: red; } .content { font-size: 14px; } ``` The CSS is emitted with scoped class names, and the JS output exports the mapping from original to scoped names. ### Configuration Configure CSS modules behavior via `css.modules`: ```ts export default defineConfig({ css: { modules: { // Scoping behavior: 'local' (default) or 'global' scopeBehaviour: 'local', // Pattern for scoped class names (Lightning CSS pattern syntax) generateScopedName: '[hash]_[local]', // Transform class name convention in JS exports localsConvention: 'camelCase', }, }, }) ``` Set `css.modules: false` to disable CSS modules entirely — `.module.css` files will be treated as regular CSS. ### `localsConvention` Controls how class names are exported in JavaScript: | Value | Input | Exports | | ----------------- | --------- | ------------------- | | *(not set)* | `foo-bar` | `foo-bar` | | `'camelCase'` | `foo-bar` | `foo-bar`, `fooBar` | | `'camelCaseOnly'` | `foo-bar` | `fooBar` | | `'dashes'` | `foo-bar` | `foo-bar`, `fooBar` | | `'dashesOnly'` | `foo-bar` | `fooBar` | You can also provide a function to generate a custom export name. It receives the original class name, generated scoped class name, and input file: ```ts export default defineConfig({ css: { modules: { localsConvention: (originalClassName, generatedClassName, inputFile) => { return originalClassName.replaceAll(/-([a-z0-9])/g, (_, character) => character.toUpperCase(), ) }, }, }, }) ``` The function form works with both CSS transformers. It changes the exported JavaScript key only; `generatedClassName` remains the value of that export. ### `generateScopedName` When using `transformer: 'lightningcss'` (default), this accepts a Lightning CSS [pattern string](https://lightningcss.dev/css-modules.html#custom-naming-conventions) (e.g., `'[hash]_[local]'`). When using `transformer: 'postcss'`, this also accepts a function: ```ts export default defineConfig({ css: { transformer: 'postcss', modules: { generateScopedName: (name, filename, css) => { return `my-lib_${name}` }, }, }, }) ``` > \[!NOTE] > Function-form `generateScopedName` is only supported with `transformer: 'postcss'`. The Lightning CSS transformer only supports string patterns. ### Optional Dependencies When using `transformer: 'postcss'` with CSS modules, install [`postcss-modules`](https://github.com/css-modules/postcss-modules): ```bash npm install -D postcss postcss-modules ``` ## CSS Code Splitting ### Merged Mode (Default) By default, all CSS is merged into a single file (default: `style.css`). The exception is [unbundle mode](/options/unbundle), where `css.splitting` defaults to `true` to preserve the module structure: ``` dist/ index.mjs style.css ← all CSS merged ``` ### Custom File Name You can customize the merged CSS file name: ```ts export default defineConfig({ css: { fileName: 'my-library.css', }, }) ``` ### Splitting Mode To split CSS per chunk — so each JavaScript chunk that imports CSS has a corresponding `.css` file — enable splitting: ```ts export default defineConfig({ css: { splitting: true, }, }) ``` ``` dist/ index.mjs index.css ← CSS from index.ts async-abc123.mjs async-abc123.css ← CSS from async chunk ``` ## PostCSS Optional Peer Dependencies When using `transformer: 'postcss'`, the following packages may need to be installed depending on the features you use: | Package | Purpose | Required When | | ------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------- | | [`postcss`](https://github.com/postcss/postcss) | Core PostCSS engine | Always (with `transformer: 'postcss'`) | | [`postcss-import`](https://github.com/postcss/postcss-import) | Resolve and inline `@import` statements | CSS files use `@import` | | [`postcss-modules`](https://github.com/css-modules/postcss-modules) | CSS modules support (scoped class names) | Using `.module.css` files | ```bash npm install -D postcss postcss-import postcss-modules ``` All three are declared as optional peer dependencies of `@tsdown/css` and only loaded when needed. ## Options Reference | Option | Type | Default | Description | | ------------------------- | ----------------------------- | ------------------------------ | ----------------------------------------------------------- | | `css.transformer` | `'postcss' \| 'lightningcss'` | `'lightningcss'` | CSS processing pipeline | | `css.splitting` | `boolean` | `false` (`true` if `unbundle`) | Enable CSS code splitting per chunk | | `css.fileName` | `string` | `'style.css'` | File name for the merged CSS file (when `splitting: false`) | | `css.minify` | `boolean` | `false` | Enable CSS minification | | `css.modules` | `object \| false` | `{}` | CSS modules configuration, or `false` to disable | | `css.target` | `string \| string[] \| false` | *from `target`* | CSS-specific syntax lowering target | | `css.postcss` | `string \| object` | — | PostCSS config path or inline options | | `css.preprocessorOptions` | `object` | — | Options for CSS preprocessors | | `css.inject` | `boolean` | `false` | Preserve CSS import statements in JS output | | `css.lightningcss` | `object` | — | Options passed to Lightning CSS for syntax lowering | --- --- url: /advanced/rolldown-options.md --- # Customizing Rolldown Options `tsdown` uses [Rolldown](https://rolldown.rs) as its core bundling engine. This allows you to easily pass or override options directly to Rolldown, giving you fine-grained control over the bundling process. For a full list of available Rolldown options, refer to the [Rolldown Config Options](https://rolldown.rs/reference/InputOptions.input) documentation. > \[!WARNING] > You should be familiar with the behavior of the Rolldown options you are overriding and ensure you have read the Rolldown documentation. ## Overriding `inputOptions` You can override the `inputOptions` generated by `tsdown` to customize how Rolldown processes your input files. There are two ways to do this: ### Using an Object You can directly pass an object to override specific `inputOptions`: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ inputOptions: { cwd: './custom-directory', }, }) ``` In this example, the `cwd` (current working directory) option is set to `./custom-directory`. ### Using a Function Alternatively, you can use a function to dynamically modify the `inputOptions`. The function receives the generated `inputOptions` and the current `format` as arguments: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ inputOptions(inputOptions, format) { inputOptions.cwd = './custom-directory' return inputOptions }, }) ``` This approach is useful when you need to customize options based on the output format or other dynamic conditions. ## Overriding `outputOptions` The `outputOptions` can be customized in the same way as `inputOptions`. For example: ### Using an Object You can directly pass an object to override specific `outputOptions`: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ outputOptions: { comments: { legal: true }, }, }) ``` In this example, the `comments.legal` option ensures that legal comments (e.g., license headers) are preserved in the output files. ### Using a Function You can also use a function to dynamically modify the `outputOptions`. The function receives the generated `outputOptions` and the current `format` as arguments: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ outputOptions(outputOptions, format) { if (format === 'esm') { outputOptions.comments = { legal: true } } return outputOptions }, }) ``` This ensures that legal comments are preserved only for the `esm` format. ## When to Use Custom Options While `tsdown` exposes many common options directly, there may be cases where certain Rolldown options are not exposed. In such cases, you can use the `inputOptions` and `outputOptions` overrides to directly set these options in Rolldown. > \[!TIP] > Using `inputOptions` and `outputOptions` gives you full access to Rolldown's powerful configuration system, allowing you to customize your build process beyond what `tsdown` exposes directly. --- --- url: /options/dts.md --- # Declaration Files (dts) Declaration files (`.d.ts`) are an essential part of TypeScript libraries, providing type definitions that allow consumers of your library to benefit from TypeScript's type checking and IntelliSense. `tsdown` makes it easy to generate and bundle declaration files for your library, ensuring a seamless developer experience for your users. > \[!NOTE] > You must install `typescript` in your project for declaration file generation to work properly. ## How dts Works in tsdown `tsdown` uses [rolldown-plugin-dts](https://github.com/sxzz/rolldown-plugin-dts) internally to generate and bundle `.d.ts` files. This plugin is specifically designed to handle declaration file generation efficiently and integrates seamlessly with `tsdown`. If you encounter any issues related to `.d.ts` generation, please report them directly to the [rolldown-plugin-dts repository](https://github.com/sxzz/rolldown-plugin-dts/issues). ## Enabling dts Generation If your `package.json` contains a `types` or `typings` field, or its `exports` field contains a `types` entry, declaration file generation will be **enabled by default** in `tsdown`. You can also explicitly enable `.d.ts` generation using the `--dts` option in the CLI or by setting `dts: true` in your configuration file. ### CLI ```bash tsdown --dts ``` ### Config File ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ dts: true, }) ``` ## Declaration Map Declaration maps allow `.d.ts` files to be mapped back to their original `.ts` sources, which is especially useful in monorepo setups for improved navigation and debugging. Learn more in the [TypeScript documentation](https://www.typescriptlang.org/tsconfig/#declarationMap). You can enable declaration maps in either of the following ways (no need to set both): ### Enable in `tsconfig.json` Enable the `declarationMap` option under `compilerOptions`: ```json [tsconfig.json] { "compilerOptions": { "declarationMap": true } } ``` ### Enable in tsdown Config Set the `dts.sourcemap` option to `true` in your tsdown config file: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ dts: { sourcemap: true, }, }) ``` ## Performance Considerations The performance of `.d.ts` generation depends on your `tsconfig.json` configuration: ### With `isolatedDeclarations` If your `tsconfig.json` has the `isolatedDeclarations` option enabled, `tsdown` will use **oxc-transform** for `.d.ts` generation. This method is **extremely fast** and highly recommended for optimal performance. ```json [tsconfig.json] { "compilerOptions": { "isolatedDeclarations": true } } ``` ### Without `isolatedDeclarations` If `isolatedDeclarations` is not enabled, `tsdown` will fall back to using the TypeScript compiler for `.d.ts` generation. While this approach is reliable, it is relatively slower compared to `oxc-transform`. > \[!TIP] > If speed is critical for your workflow, consider enabling `isolatedDeclarations` in your `tsconfig.json`. ## Build Process for dts * **For ESM Output**: Both `.js` and `.d.ts` files are generated in the **same build process**. If you encounter compatibility issues, please report them. * **For CJS Output**: A **separate build process** is used exclusively for `.d.ts` generation to ensure compatibility. ## Advanced Options `rolldown-plugin-dts` provides several advanced options to customize `.d.ts` generation. For a detailed explanation of these options, refer to the [plugin's documentation](https://github.com/sxzz/rolldown-plugin-dts#options). --- --- url: /options/dependencies.md --- # Dependencies When bundling with `tsdown`, dependencies are handled intelligently to ensure your library remains lightweight and easy to consume. Here's how `tsdown` processes different types of dependencies and how you can customize this behavior. ## Default Behavior ### `dependencies`, `peerDependencies`, and `optionalDependencies` By default, `tsdown` **does not bundle dependencies** listed in your `package.json` under `dependencies`, `peerDependencies`, and `optionalDependencies`: * **`dependencies`**: These are treated as external and will not be included in the bundle. Instead, they will be installed automatically by npm (or other package managers) when your library is installed. * **`peerDependencies`**: These are also treated as external. Users of your library are expected to install these dependencies manually, although some package managers may handle this automatically. * **`optionalDependencies`**: These are also treated as external. They may or may not be installed depending on the user's platform and configuration. ### `devDependencies` and Phantom Dependencies * **`devDependencies`**: Dependencies listed under `devDependencies` in your `package.json` will **only be bundled if they are actually imported or required by your source code**. * **Phantom Dependencies**: Dependencies that exist in your `node_modules` folder but are not explicitly listed in your `package.json` will **only be bundled if they are actually used in your code**. In other words, only the `devDependencies` and phantom dependencies that are actually referenced in your project will be included in the bundle. ## The `deps` Option All dependency-related options are configured under the `deps` field: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { neverBundle: ['lodash', /^@my-scope\//], alwaysBundle: ['some-package'], onlyBundle: ['cac', 'bumpp'], onlyImport: ['cac'], resolveDepSubpath: true, }, }) ``` ### `deps.resolveDepSubpath` By default, tsdown preserves external dependency subpath imports as written. Enable `resolveDepSubpath` to resolve subpath imports to their actual package-relative paths when a package has no `exports` field. For example, `my-dep/functions/lt` may become `my-dep/functions/lt.js`, and `my-dep/folder` may become `my-dep/folder/index.js`. Set `resolveDepSubpath` to `true` to enable this behavior: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { resolveDepSubpath: true, }, }) ``` The default value is `false`. ### `deps.onlyBundle` The `onlyBundle` option acts as a whitelist for dependencies that are allowed to be bundled from `node_modules`. If any dependency not in the list is found in the bundle, tsdown will throw an error. This is useful for preventing unexpected dependencies from being silently inlined into your output, especially in large projects. ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { onlyBundle: ['cac', 'bumpp'], }, }) ``` In this example, only `cac` and `bumpp` are allowed to be bundled. If any other `node_modules` dependency is imported, tsdown will throw an error with a message indicating which dependency was unexpectedly bundled and which files imported it. #### Behavior * **`onlyBundle` is an array** (e.g., `['cac', /^my-/]`): Only dependencies matching the list are allowed to be bundled. An error is thrown for any others. Unused patterns in the list will also be reported. * **`onlyBundle` is `false`**: All warnings and checks about bundled dependencies are suppressed. * **`onlyBundle` is not set** (default): A hint is logged if any `node_modules` dependencies are bundled, suggesting you add the `onlyBundle` option or set it to `false` to disable the hint. ::: tip Make sure to include all required sub-dependencies in the `onlyBundle` list as well, not just the top-level packages you directly import. ::: ### `deps.onlyImport` While `onlyBundle` controls which dependencies are allowed to be **bundled**, the `onlyImport` option acts as a whitelist for dependencies that are allowed to be **imported** by your output at runtime. After each build, tsdown scans the emitted chunks and throws an error if any of them imports a package that is not in the list. This ensures your published code never depends on packages you haven't explicitly approved. ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { onlyImport: ['cac'], }, }) ``` In this example, the output is only allowed to import `cac`. If any chunk imports another package, tsdown will throw an error listing all offending imports, suggesting you either add them to `onlyImport` or bundle them via `alwaysBundle`. #### Behavior * Matching is based on the **package name**, so subpath imports like `cac/deno` are covered by listing `cac`. * Node.js built-in modules are always allowed when `platform` is `node`. * Imports between chunks emitted by code splitting are always allowed. * Type declaration output (`.d.ts`) is checked as well. ::: warning ES imports and dynamic `import()` expressions are checked. CJS `require()` calls are not detected. ::: ### `deps.neverBundle` The `neverBundle` option allows you to explicitly mark certain dependencies as external, ensuring they are not bundled into your library. For example: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { neverBundle: ['lodash', /^@my-scope\//], }, }) ``` In this example, `lodash` and all packages under the `@my-scope` namespace will be treated as external. #### Externalizing All Dependencies Set `neverBundle` to `true` to externalize **all** dependencies: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { neverBundle: true, }, }) ``` When enabled, every import that follows npm package naming conventions (e.g. `lodash`, `@scope/pkg/utils`) is marked as external **as written, without being resolved**. This is fast and even works when dependencies are not installed. Note the following behaviors: * Package specifiers are preserved exactly as written; subpaths like `my-dep/utils` are only rewritten when `resolveDepSubpath` is enabled. * Other non-relative imports — [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) starting with `#` and path aliases like `~/utils` — are still resolved: if they resolve into `node_modules`, they are kept external with the original specifier; otherwise the resolved local file is bundled. `neverBundle: true` can be combined with `alwaysBundle` to bundle a few selected dependencies while externalizing everything else: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { neverBundle: true, alwaysBundle: ['some-package'], }, }) ``` ### `deps.alwaysBundle` The `alwaysBundle` option allows you to force certain dependencies to be bundled, even if they are listed in `dependencies`, `peerDependencies`, or `optionalDependencies`. For example: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ deps: { alwaysBundle: ['some-package'], }, }) ``` Here, `some-package` will be bundled into your library. ## Handling Dependencies in Declaration Files The bundling logic for declaration files is consistent with JavaScript: dependencies are bundled or marked as external according to the same rules and options. ### Resolver Option When bundling complex third-party types, you may encounter cases where the default resolver (Oxc) cannot handle certain scenarios. For example, the types for `@babel/generator` are located in the `@types/babel__generator` package, which may not be resolved correctly by Oxc. To address this, you can set the `resolver` option to `tsc` in your configuration. This uses the native TypeScript resolver, which is slower but much more compatible with complex type setups: ```ts [tsdown.config.ts] import { defineConfig } from 'tsdown' export default defineConfig({ dts: { resolver: 'tsc', }, }) ``` ## Migration from Deprecated Options The following top-level options are deprecated. Please migrate to the `deps` namespace: | Deprecated Option | New Option | | ----------------- | ------------------- | | `external` | `deps.neverBundle` | | `noExternal` | `deps.alwaysBundle` | Note that a deprecated option cannot be mixed with its replacement: setting both `external` and `deps.neverBundle` (or both `noExternal` and `deps.alwaysBundle`) throws an error. ## Summary * **Default Behavior**: * `dependencies`, `peerDependencies`, and `optionalDependencies` are treated as external and not bundled. * `devDependencies` and phantom dependencies are only bundled if they are actually used in your code. * **Customization**: * Use `deps.onlyBundle` to whitelist dependencies allowed to be bundled, and throw an error for any others. * Use `deps.onlyImport` to whitelist packages the output is allowed to import at runtime. * Use `deps.neverBundle` to mark specific dependencies as external, or set it to `true` to externalize all dependencies. * Use `deps.alwaysBundle` to force specific dependencies to be bundled. * Enable `deps.resolveDepSubpath` to resolve external dependency subpath imports to their package-relative paths. * **Declaration Files**: * The bundling logic for declaration files is now the same as for JavaScript. * Use `resolver: 'tsc'` for better compatibility with complex third-party types. By understanding and customizing dependency handling, you can ensure your library is optimized for both size and usability. --- --- url: /recipes/ember-support.md --- # Ember Support `tsdown` can build Ember v2 addons (libraries) with [`@nullvoxpopuli/ember-rolldown`](https://github.com/NullVoxPopuli/ember.nvp/tree/main/packages/rolldown). This meta-plugin compiles `.gts` and `.gjs` files, including their `