facebook / facebook/stylex

processCollectedRulesToCSS crashes in Vite dev mode when defineConsts files haven't been processed yet

Open
#1,497 1 comment 4 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
10.3k
Forks
481
Avg merge
3d 8h
Merged PRs (30d)
13

Description

## Description

When using `defineConsts` with computed property keys in `stylex.create()`, the Vite dev server crashes with an "Invalid empty selector" error from lightningcss. This is a timing issue specific to dev mode — it does not affect production builds.

## Root Cause

StyleX's `defineConsts` mechanism works via a two-phase system:

1. When the babel plugin processes `defineConsts` files, it generates "constant rules" with `constKey`/`constVal` metadata
2. When it processes consumer files (using computed keys like `[mediaQueries.tablet]`), it generates rules with `var(--hash)` as CSS selectors
3. At CSS generation time, `processStylexRules` replaces `var(--hash)` references with actual values from the constant rules

In **production builds**, all files are transformed before `processCollectedRulesToCSS` runs, so constant rules are always available and the replacement works correctly.

In the **Vite dev server**, CSS can be requested/regenerated before all `defineConsts` files have been processed. When this happens:
- `processStylexRules` doesn't find the constant values in `constsMap`
- `var(--hash)` references remain unresolved in the CSS output (e.g. `var(--xs8wxp5){.xv8rlbn{display:block}}`)
- `lightningTransform` in `processCollectedRulesToCSS` rejects this as invalid CSS, throwing "Invalid empty selector"
- The dev CSS middleware crashes

The HMR mechanism (`stylex:css-update`) would eventually re-trigger CSS generation once all files are processed, but the `lightningTransform` error prevents the dev server from serving any CSS in the meantime.

## Reproduction

1. Create a `defineConsts` file:
```js
import * as stylex from '@stylexjs/stylex';

export const mediaQueries = stylex.defineConsts({
tablet: '@media (min-width: 48rem)',
desktop: '@media (min-width: 64rem)',
});
```

2. Use computed keys in a consumer file:
```js
import * as stylex from '@stylexjs/stylex';
import { mediaQueries } from './mediaQueries.stylex.js';

const styles = stylex.create({
container: {
display: {
[mediaQueries.desktop]: 'block',
default: 'none',
},
},
});
```

3. Run the Vite dev server — the terminal shows:
```
Error: Invalid empty selector
at processCollectedRulesToCSS (...)
```

## Proposed Fix

Wrap the `lightningTransform` call in `processCollectedRulesToCSS` (`packages/@stylexjs/unplugin/src/core.js`) in a try-catch, falling back to the raw CSS output:

```js
try {
const { code } = lightningTransform({
targets: browserslistToTargets(browserslist('>= 1%')),
...options.lightningcssOptions,
filename: 'stylex.css',
code: Buffer.from(collectedCSS),
});
return code.toString();
} catch (e) {
// In dev mode, defineConsts files may not have been processed yet,
// leaving unresolved var(--hash) references as CSS selectors.
// Return raw CSS and rely on HMR to re-generate once all rules are collected.
return collectedCSS;
}
```

This is safe because:
- The raw CSS with `var(--hash)` selectors is ignored by the browser (invalid selectors are skipped)
- Once all `defineConsts` files are processed, the next HMR-triggered CSS update resolves all references and produces valid CSS
- Production builds are unaffected (all files are always processed before CSS generation)

## Environment

- `@stylexjs/unplugin`: 0.17.5
- `@stylexjs/babel-plugin`: 0.17.5
- Vite: 8.0.0-beta.8
- Node: 22

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.