Subchapter 8.13
references/option-css.mdMarkdown7 KBView on GitHub
Status: Experimental — API and behavior may change.
Configure CSS handling including preprocessors, syntax lowering, minification, and code splitting.
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.
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
CSS @import statements are resolved and inlined automatically. No separate output files produced.
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).
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'export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `$brand-color: #ff7e17;`,
},
less: {
math: 'always',
},
stylus: {
define: { '$brand-color': '#ff7e17' },
},
},
},
})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}`
},
}export default defineConfig({
css: {
minify: true,
},
})Powered by Lightning CSS.
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 controls mutually exclusive CSS processing paths:
'lightningcss' (default): @import via Lightning CSS bundleAsync(), no PostCSS.'postcss': @import via postcss-import, PostCSS plugins applied, Lightning CSS for final transform only.export default defineConfig({
css: {
transformer: 'postcss',
},
})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.
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 }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.
Files with .module.css (and .module.scss, .module.less, etc.) are treated as CSS modules — class names are scoped and exported as JS:
import styles from './app.module.css'
console.log(styles.title) // "scoped_title_hash"export default defineConfig({
css: {
modules: {
scopeBehaviour: 'local', // 'local' (default) | 'global'
generateScopedName: '[hash]_[local]', // Lightning CSS pattern string
localsConvention: 'camelCase', // 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly'
},
},
})Set css.modules: false to disable. Function-form generateScopedName requires transformer: 'postcss'.
npm install -D postcss postcss-modulesAll CSS merged into a single file (default: style.css).
export default defineConfig({
css: {
fileName: 'my-library.css', // Custom name (default: 'style.css')
},
})export default defineConfig({
css: {
splitting: true, // Each JS chunk gets a corresponding .css file
},
})When enabled, JS output preserves import statements pointing to emitted CSS files. Consumers auto-import CSS alongside JS:
export default defineConfig({
css: {
inject: true,
},
})When using transformer: 'postcss', install these as needed:
| Package | Purpose | Required When |
|---|---|---|
postcss | Core PostCSS engine | Always (with transformer: 'postcss') |
postcss-import | Resolve/inline @import | CSS uses @import |
postcss-modules | CSS modules (scoped classes) | Using .module.css files |
npm install -D postcss postcss-import postcss-modulesAll declared as optional peer dependencies of @tsdown/css.
| Option | Type | Default | Description |
|---|---|---|---|
css.transformer | 'postcss' | 'lightningcss' | 'lightningcss' | CSS processing pipeline |
css.splitting | boolean | false | Per-chunk CSS splitting |
css.fileName | string | 'style.css' | Merged CSS file name |
css.minify | boolean | false | CSS minification |
css.modules | object | false | {} | CSS modules config, or false to disable |
css.inject | boolean | false | Preserve CSS imports in JS output |
css.target | string | string[] | false | from target | CSS-specific lowering target |
css.postcss | string | object | — | PostCSS config path or inline options |
css.preprocessorOptions | object | — | Preprocessor options |
css.lightningcss | object | — | Lightning CSS options |