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:
- When the babel plugin processes
defineConsts files, it generates "constant rules" with constKey/constVal metadata
- When it processes consumer files (using computed keys like
[mediaQueries.tablet]), it generates rules with var(--hash) as CSS selectors
- 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
- Create a
defineConsts file:
import * as stylex from '@stylexjs/stylex';
export const mediaQueries = stylex.defineConsts({
tablet: '@media (min-width: 48rem)',
desktop: '@media (min-width: 64rem)',
});
- Use computed keys in a consumer file:
import * as stylex from '@stylexjs/stylex';
import { mediaQueries } from './mediaQueries.stylex.js';
const styles = stylex.create({
container: {
display: {
[mediaQueries.desktop]: 'block',
default: 'none',
},
},
});
- 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:
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
Description
When using
defineConstswith computed property keys instylex.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
defineConstsmechanism works via a two-phase system:defineConstsfiles, it generates "constant rules" withconstKey/constValmetadata[mediaQueries.tablet]), it generates rules withvar(--hash)as CSS selectorsprocessStylexRulesreplacesvar(--hash)references with actual values from the constant rulesIn production builds, all files are transformed before
processCollectedRulesToCSSruns, so constant rules are always available and the replacement works correctly.In the Vite dev server, CSS can be requested/regenerated before all
defineConstsfiles have been processed. When this happens:processStylexRulesdoesn't find the constant values inconstsMapvar(--hash)references remain unresolved in the CSS output (e.g.var(--xs8wxp5){.xv8rlbn{display:block}})lightningTransforminprocessCollectedRulesToCSSrejects this as invalid CSS, throwing "Invalid empty selector"The HMR mechanism (
stylex:css-update) would eventually re-trigger CSS generation once all files are processed, but thelightningTransformerror prevents the dev server from serving any CSS in the meantime.Reproduction
defineConstsfile:Proposed Fix
Wrap the
lightningTransformcall inprocessCollectedRulesToCSS(packages/@stylexjs/unplugin/src/core.js) in a try-catch, falling back to the raw CSS output:This is safe because:
var(--hash)selectors is ignored by the browser (invalid selectors are skipped)defineConstsfiles are processed, the next HMR-triggered CSS update resolves all references and produces valid CSSEnvironment
@stylexjs/unplugin: 0.17.5@stylexjs/babel-plugin: 0.17.5