Skip to content

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

@marcellegane

Description

@marcellegane

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:
import * as stylex from '@stylexjs/stylex';

export const mediaQueries = stylex.defineConsts({
    tablet: '@media (min-width: 48rem)',
    desktop: '@media (min-width: 64rem)',
});
  1. 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',
        },
    },
});
  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions