Skip to content

allow CSS nesting #1211

@awkale

Description

@awkale

Problem Statement

When building a library with tsdx v2, component-level CSS files (those imported directly in .tsx files
like import './drawer.css') are processed by bunchee's inlineCss rollup plugin, which runs clean-css
v5.3.3 with default options (new CleanCSS({})).

clean-css v5.x has no support for native CSS nesting. Its tokenizer only handles { at Level.RULE
for CSS custom property values and @page margin boxes — there is no handler for the native nesting case
(e.g. .foo { &:hover { color: red } }). When it encounters nested { in a rule context, it falls
through to a generic buffer push, corrupting the token stream and silently mangling or dropping the nested
styles in the output.

This means any component-level CSS file that uses native CSS nesting syntax will produce broken output
after the tsdx build, with no error or warning.

Proposed Solution

Replace clean-css with a CSS minifier that supports native CSS nesting, such as Lightning
CSS
or cssnano (via PostCSS).
Alternatively, make the CSS minifier configurable so consumers can provide their own.

In bunchee's dist/index.js, the relevant code is:

const cleanCssInstance = new CleanCSS__default.default({});

function minify(code) {
  return cleanCssInstance.minify(code).styles;
}

This could be swapped to Lightning CSS:

import { transform } from 'lightningcss';

function minify(code) {
  const { code: minified } = transform({
    filename: 'style.css',
    code: Buffer.from(code),
    minify: true,
  });
  return minified.toString();
}

Lightning CSS has full support for native CSS nesting and is significantly faster than clean-css.

Alternatives Considered

  • Pre-flattening nesting with PostCSS/Tailwind before the build: This works for styles that go through a
    dedicated CSS compilation step (e.g. Tailwind's --minify flattens nesting), but doesn't help
    component-level CSS files that are imported directly in components and processed by bunchee's inlineCss
    plugin.
  • Disabling CSS minification entirely in bunchee: Avoids the corruption but sacrifices minification for
    all CSS.
  • Avoiding native CSS nesting in component CSS files: A workaround, but native CSS nesting is a stable
    browser feature (supported in all major browsers since Dec 2023) and developers should be able to use it.

Who Benefits?

Any library author using tsdx/bunchee who wants to use native CSS nesting in component-level CSS files. As
CSS nesting adoption grows (it's now baseline across all major browsers), this will affect an increasing
number of users.

Additional Context

  • clean-css v5.x is effectively in maintenance mode and has no plans to add nesting support — the
    tokenizer's Level system would require a significant architectural rewrite.
  • bunchee v6.9.4 pins clean-css: ^5.3.3 as a direct runtime dependency.
  • tsdx v2.0.0 delegates entirely to bunchee via execa("bunchee").
  • The issue is silent — clean-css produces corrupted output without any errors or warnings, making it
    difficult to diagnose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions