npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sanity/tsdown-config

v0.19.1

Published

Shared configuration for tsdown

Readme

Shared config for tsdown

pnpm add --save-dev @sanity/tsdown-config tsdown

Create a tsdown.config.ts file with:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({tsconfig: 'tsconfig.dist.json'})

React Compiler

The same React Compiler feature as @sanity/pkg-utils is available. It runs babel-plugin-react-compiler on the source files before they are bundled, so published components are memoized automatically. The plugin needs to be installed separately:

pnpm add --save-dev babel-plugin-react-compiler

Then enable it:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  reactCompiler: true,
})

Pass an object to configure the compiler, using the same options as babel-plugin-react-compiler:

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  reactCompiler: {target: '18'},
})

styled-components

If your package uses styled-components, enable the same styledComponents transform that @sanity/pkg-utils has:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  styledComponents: true,
})

It adds displayName (better debugging) and componentId (avoids SSR hydration mismatches) to your styled components, and minifies the CSS in tagged template literals. Unlike @sanity/pkg-utils it doesn't require installing babel-plugin-styled-components, as it uses oxc's native port of the babel plugin.

Pass an object to customize the transform, using the same options as babel-plugin-styled-components:

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  styledComponents: {namespace: 'my-package'},
})

vanilla-extract

The same vanillaExtract feature as @sanity/pkg-utils is available. It extracts the CSS from .css.ts files into a separate file (dist/bundle.css by default), minified and lowered with lightningcss for the @sanity/browserslist-config browsers by default. Under the hood it uses @sanity/vanilla-extract-tsdown-plugin, a tsdown-native port of @vanilla-extract/rollup-plugin, so enabling it doesn't pull rollup into your project. Start by installing @vanilla-extract/css for authoring the .css.ts files:

pnpm add --save-dev @vanilla-extract/css
import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  vanillaExtract: true,
})

By default (inject: {nodeCompat: true}) the conditional CSS export pattern is wired up automatically:

  • injects the self-referential import "<pkg>/bundle.css" into the entry chunks that use vanilla-extract styles,
  • emits a no-op bundle-css.js shim (plus bundle.css.d.ts / bundle-css.d.ts) for runtimes that cannot import .css files, and
  • writes the conditional "./bundle.css" export to package.json (types → the shim's .d.ts, browser/style → the real CSS, node/default → the shim).

The result is that import "<pkg>/bundle.css" resolves to the real CSS in bundlers/browsers and to the no-op shim in Node and similar runtimes. Make sure the extracted CSS survives tree-shaking in consumers by adding it to sideEffects in package.json:

{
  "sideEffects": ["*.css"]
}

Pass an options object instead of true to customize - the options are modeled after the css options of @tsdown/css (e.g. fileName, minify, target, lightningcss). Set inject: true for a plain relative import "./bundle.css" instead of the conditional export pattern, or inject: false to wire up the import, shim, and export yourself.

Two Sanity-flavored defaults diverge from the bare plugins (which match @tsdown/css exactly):

  • minify defaults to true - published Sanity libraries ship minified CSS. Set minify: false for readable output.
  • The CSS syntax lowering target defaults to tsdown's top-level target, and when the effective target is undefined or names no browsers (e.g. 'node20', also what tsdown derives from engines.node - it speaks to the JS runtime, not the browsers the extracted CSS runs in), the lowering targets are resolved from @sanity/browserslist-config and passed through lightningcss.targets. The bare plugins (like @tsdown/css) would skip lowering in that case. target: false disables lowering entirely, and a user-provided lightningcss.targets wins over the fallback.

vanillaExtract can be combined with the css option below when a package also uses CSS modules (or other @tsdown/css features) — the two pipelines write to different files by default (bundle.css vs style.css) and do not interfere with each other.

css

tsdown's experimental css option is passed through as-is. It enables the @tsdown/css pipeline for CSS modules, preprocessors, Lightning CSS / PostCSS, inject, and related features. Install @tsdown/css in the project first — @sanity/tsdown-config only forwards the option and does not depend on @tsdown/css itself:

pnpm add --save-dev @tsdown/css
import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  css: {
    inject: true,
    modules: {localsConvention: 'camelCase'},
  },
})

Both css and vanillaExtract can be enabled in the same config. Use that when a package authors styles with vanilla-extract and CSS modules (.module.css):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  vanillaExtract: true,
  css: {
    inject: true,
    modules: {localsConvention: 'camelCase'},
  },
})

vanilla-extract extracts into dist/bundle.css by default (with the conditional "./bundle.css" export when inject stays at its Sanity default), while @tsdown/css merges other CSS — including scoped CSS modules — into dist/style.css. See the InlineConfig.css API reference for the full option surface.

dts

tsdown's dts option is passed through as-is. By default tsdown auto-detects it from package.json (it's enabled when a types field or a types condition in exports is present). Pass an object to customize how the .d.ts files are generated, for example to use tsgo (the same feature as the tsgo option in @sanity/pkg-utils, requires either typescript v7 or @typescript/native-preview to be installed — with typescript v7 it's enabled automatically):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  dts: {tsgo: true},
})

outDir

tsdown's outDir option is passed through as-is. When left undefined, tsdown writes to dist (the same default as @sanity/pkg-utils):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  outDir: 'lib',
})

clean

tsdown's clean option is passed through as-is. When left undefined, tsdown defaults to true and removes outDir (dist by default) before each build.

Prefer an array of folders over a separate "clean" script in package.json. That way tsdown / pnpm build clears the directories itself — packages don't need rimraf, a clean script, or prebuild / run-s clean build wiring:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  // Instead of `"clean": "rimraf dist coverage"` (and running it before build):
  clean: ['dist', 'coverage'],
})

A string[] replaces tsdown's default (true → clean outDir), so include outDir (usually 'dist') in the array when you still want it cleaned alongside other folders. Pass false to skip cleaning entirely.

Isolated declarations

If you're using the @sanity/tsconfig/isolated-declarations preset — which makes tsdown generate the .d.ts files with oxc's fast isolated declarations transform — annotate the default export of tsdown.config.ts with satisfies Promise<UserConfig>:

import {defineConfig} from '@sanity/tsdown-config'
import type {UserConfig} from 'tsdown'

export default defineConfig() satisfies Promise<UserConfig>

Without the annotation, type-checking tsdown.config.ts with the @sanity/tsconfig presets (they enable declaration) fails with TS2883 in pnpm projects: the inferred type of the default export can only be named through @sanity/tsdown-config's own copy of tsdown, which isn't portable. satisfies Promise<UserConfig> names the type through your own tsdown dependency instead.

Keep the isolated-declarations preset scoped to the tsconfig that tsdown builds with (e.g. a tsconfig.dist.json that only includes ./src). If isolatedDeclarations covers tsdown.config.ts itself, the default export can't be inferred at all (TS9037), and the config has to move into an explicitly annotated variable instead:

import {defineConfig} from '@sanity/tsdown-config'
import type {UserConfig} from 'tsdown'

const config: Promise<UserConfig> = defineConfig()

export default config

define

tsdown's define option is also passed through as-is. It replaces global identifiers with constant expressions at build time (the same feature as the define option in @sanity/pkg-utils):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  define: {'process.env.NODE_ENV': JSON.stringify('production')},
})

sourcemap

tsdown's sourcemap option is forwarded with a true default (the same as @sanity/pkg-utils). tsdown itself defaults to false and does not read sourceMap from the tsconfig:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  sourcemap: false,
})

deps

tsdown's deps option is forwarded. When platform is 'neutral' (the default), neverBundle always includes /^node:/ so node built-ins stay external, and userland neverBundle entries are appended rather than replacing that default (tsdown's mergeConfig would replace the array):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  // Resulting neverBundle: [/^node:/, /^my-package(\/|$)/]
  deps: {neverBundle: [/^my-package(\/|$)/]},
})

'neutral' also restores inputOptions.resolve.mainFields: ['module', 'main'] for inlined dependencies that ship no exports map. Prefer it over 'node' for packages that also run in the browser - 'node' makes CommonJS-interop emit a module-scope createRequire(import.meta.url) for inlined CJS deps, which crashes browser-bundled consumers.

target

tsdown's target option is also passed through as-is. It downlevels JS syntax for the given runtimes (esbuild-style target strings), and doubles as the default CSS syntax lowering target when vanillaExtract is enabled (browserless targets like node20 don't affect the CSS - it falls back to @sanity/browserslist-config):

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  target: ['chrome90', 'safari16'],
})

exports

tsdown's exports option is forwarded with different defaults, suited for publishing Sanity libraries:

  • enabled: 'local-only' - the exports map in package.json is generated during local builds and skipped in CI, where the committed package.json is already up to date, and
  • devExports: true when pnpm is detected - the local exports map points at the source files (so monorepo siblings and editors resolve them directly), while publishConfig.exports receives the built files. This default is omitted for other or unknown package managers because they do not all reliably apply publishConfig.exports when publishing.

Userland values apply with tsdown's mergeConfig semantics: an object deep-merges over the defaults (so individual fields can be overridden), while any other value - false to disable exports generation, or a bare CI condition ('ci-only'/'local-only') - replaces them entirely:

import {defineConfig} from '@sanity/tsdown-config'

export default defineConfig({
  tsconfig: 'tsconfig.dist.json',
  exports: {all: true},
})

checks

Rolldown's checks.circularDependency warning is enabled by default (Rolldown itself defaults it to false). Circular imports inflate bundle size and can cause execution-order issues, so library builds surface them as warnings.

To turn the warning off, merge over the returned config:

import {defineConfig} from '@sanity/tsdown-config'
import {mergeConfig} from 'tsdown'

export default mergeConfig(await defineConfig({tsconfig: 'tsconfig.dist.json'}), {
  checks: {circularDependency: false},
})

Everything else: mergeConfig

defineConfig deliberately only exposes options you're likely to change. For anything else, merge tsdown options over the returned config with tsdown's own mergeConfig - defineConfig returns a promise, so await it first:

import {defineConfig} from '@sanity/tsdown-config'
import {mergeConfig} from 'tsdown'

export default mergeConfig(await defineConfig({tsconfig: 'tsconfig.dist.json'}), {
  // Any tsdown option, e.g. opting out of hashed chunk filenames:
  hash: false,
})