调试 StyleX + Vite:'Invalid Empty Selector' 之谜

发布: (2026年1月2日 GMT+8 05:36)
10 min read
原文: Dev.to

Source: Dev.to

一次系统化的探索:在错误信息毫无提示时调试 CSS‑in‑JS 的竞态条件

TL;DR – 如果你在使用 Vite 的 StyleX 时遇到 Invalid empty selector 错误,并且使用了像 [breakpoints.tablet]: '1rem' 这样的导入常量作为计算属性键,那么问题就出在这里。请将其替换为内联字符串,例如 "@media (max-width: 768px)": '1rem'。下面将解释为何会出现这种情况以及我们是如何发现的。

常见错误信息(便于搜索)

如果你是通过搜索来到这里的,可能已经看到以下错误之一:

Error: Invalid empty selector
Invalid empty selector
unknown file:528:1
    at lightningTransform (node_modules/@stylexjs/unplugin/lib/es/index.mjs)
LightningCSS error: Invalid empty selector
@stylexjs/unplugin: Invalid empty selector
vite stylex Invalid empty selector

或者你可能在生成的 CSS 中看到 @media var(--xxx),这才是错误的实际原因。


告诉我们一无所知的错误

它始于一个几乎没有提供任何有用信息的错误:

Error: Invalid empty selector
unknown file:528:1
    at lightningTransform (node_modules/@stylexjs/unplugin/lib/es/index.mjs)
    at processCollectedRulesToCSS (node_modules/@stylexjs/unplugin/lib/es/index.mjs)
    at collectCss (node_modules/@stylexjs/unplugin/lib/es/index.mjs)

没有源文件,没有我们代码中的行号,也没有哪个样式出错的指示——只有 “unknown file” 和一个我们看不到的生成的 CSS 中的行号。

这就是我们追踪根本原因的过程——更重要的是,帮助我们找到原因的 调试方法论

为什么这种模式应该有效

在深入调试之前,先要明白:根据 StyleX 文档,我们并没有做错任何事

StyleX 提供了 stylex.defineConsts() 用于定义可复用的常量,例如媒体查询。官方文档正是展示了这种模式:

// This is the documented, recommended approach
import * as stylex from '@stylexjs/stylex';

export const breakpoints = stylex.defineConsts({
  tablet: "@media (max-width: 768px)",
  mobile: "@media (max-width: 640px)",
});

在 JavaScript 中,将这些常量用作计算属性键是标准写法:

import { breakpoints } from './breakpoints.stylex';

const styles = stylex.create({
  container: {
    padding: {
      default: '2rem',
      [breakpoints.tablet]: '1rem', // Standard JS computed property
    },
  },
});

这种模式在以下环境中都能完美运行:

  • 生产构建
  • Webpack 开发服务器
  • Next.js

在 Vite 开发模式下会出现问题。问题的关键是:为什么会这样?

调试之旅

第 1 步 – 显而易见的首次尝试

# 清除所有缓存
rm -rf node_modules/.vite
rm -rf node_modules/.cache
npm run dev
# 极端方案 – 重新安装所有依赖
rm -rf node_modules
npm install
npm run dev

两次尝试都没有消除错误,说明缓存并不是问题所在。

第 2 步 – 理解错误来源

堆栈跟踪指向 @stylexjs/unplugin 中的 lightningTransform。LightningCSS 是 StyleX 使用的 CSS 解析/转换器。错误信息 “Invalid empty selector”(无效的空选择器)意味着 LightningCSS 收到了格式错误的 CSS。

关键洞察: 错误并不在我们的源代码中,而是在 生成的 CSS 中,而我们看不到它。

第 3 步 – 为构建管道添加仪表

由于无法查看中间 CSS,我们在 node_modules/@stylexjs/unplugin/lib/es/index.mjs 中直接加入了调试钩子:

function processCollectedRulesToCSS(rules, options) {
  if (!rules || rules.length === 0) return '';

  const collectedCSS = stylexBabelPlugin.processStylexRules(rules, {
    useCSSLayers: options.useCSSLayers ?? false,
    classNamePrefix: options.classNamePrefix ?? 'x',
  });

  // DEBUG: 始终写入 CSS,以便查看生成内容
  const fs = require('fs');
  const lines = collectedCSS.split('\n');
  console.log('[StyleX DEBUG] CSS lines:', lines.length);
  fs.writeFileSync(`stylex-debug-${lines.length}.css`, collectedCSS);

  let code;
  try {
    const result = lightningTransform({
      filename: 'styles.css',
      code: Buffer.from(collectedCSS),
      minify: options.minify ?? false,
    });
    code = result.code;
  } catch (error) {
    // CRITICAL: 捕获导致失败的 CSS
    fs.writeFileSync('stylex-debug-FAILED.css', collectedCSS);
    console.log('[StyleX DEBUG] FAILED – check stylex-debug-FAILED.css');
    throw error;
  }

  return code.toString();
}

为什么重要: 在调试构建工具时,往往看不到中间产物。给它们加上捕获代码是必不可少的。

第 4 步 – 第一个线索

在加入仪表后运行开发服务器,会生成 stylex-debug-FAILED.css。打开它可以看到:

@media var(--xgageza) {
  .x1abc123 {
    padding-left: 1rem;
  }
}

@media var(--xgageza) 并不是合法的 CSS——这正是 LightningCSS 报错的地方。

第 5 步 – 追踪 var(--xgageza) 的来源

在生成的 CSS 中搜索发现,StyleX 生成的每个媒体查询都被替换成了 CSS 变量引用。罪魁祸首是 Vite 开发服务器处理 引用导入常量的计算属性键 的方式。常量在 StyleX 的 Babel 插件已经收集规则之后才被求值,导致出现了一个永远不会被解析的占位变量 var(--xgageza)

第 6 步 – 验证假设

我们创建了一个最小复现案例:

// breakpoints.stylex
import * as stylex from '@stylexjs/stylex';
export const bp = stylex.defineConsts({
  tablet: '@media (max-width: 768px)',
});

// component.jsx
import { bp } from './breakpoints.stylex';
export const styles = stylex.create({
  box: {
    [bp.tablet]: { padding: '1rem' },
  },
});
  • Webpack → 构建成功。
  • Vite(开发模式) → 抛出 Invalid empty selector

将其改为内联字符串后错误消失:

const styles = stylex.create({
  box: {
    '@media (max-width: 768px)': { padding: '1rem' },
  },
});

因此问题出在 Vite 开发模式下 运行时导入常量

第 7 步 – 变通方案 / 修复

将计算属性的使用改为字面量字符串,或将常量移到普通对象(不使用 stylex.defineConsts),直接引用:

// breakpoints.js – 普通对象,不使用 StyleX API
export const breakpoints = {
  tablet: '@media (max-width: 768px)',
  mobile: '@media (max-width: 640px)',
};

// component.jsx
import { breakpoints } from './breakpoints';
export const styles = stylex.create({
  box: {
    [breakpoints.tablet]: { padding: '1rem' },
  },
});
styles = stylex.create({
  container: {
    [breakpoints.tablet]: { padding: '1rem' },
  },
});

或者,升级到更新的 @stylexjs/unplugin 版本(≥ 0.5.2),该版本已修复此问题,或通过 optimizeDeps 配置 Vite 预先打包这些常量。


要点与调试方法论

  1. 从显而易见的开始 – 清除缓存,重新安装,重启。
  2. 阅读堆栈跟踪 – 找到抛出错误的工具(LightningCSS)。
  3. 捕获中间产物 – 在构建流水线中加入仪器,以输出生成的 CSS。
  4. 搜索模式 – 错误的 @media var(--…) 指向了一个转换 bug。
  5. 隔离最小复现 – 简化到能够复现错误的最小示例。
  6. 验证假设 – 将常量替换为字面字符串;错误消失。
  7. 应用修复或变通方案 – 在 Vite 开发模式下避免该问题模式,或升级插件。

通过遵循这些步骤,你可以将模糊的 “Invalid empty selector” 转化为具体、可复现的 bug,并快速解决它。祝调试愉快!

CSS 变量引用,而不是媒体查询!

CSS 应该是:

@media (max-width: 768px) {
  .x1abc123 {
    padding-left: 1rem;
  }
}

The Smoking Gun

有东西在生成 var(--xgageza),而本该是 @media (max-width: 768px) 的地方出现了它。

Step 5 – The Wrong Hypothesis

我们的第一个假设:“也许 stylex.defineConsts() 出了问题。我们改用普通的 JavaScript 对象试试看。”

// Changed from stylex.defineConsts() to plain object
export const breakpoints = {
  tablet: "@media (max-width: 768px)",
  mobile: "@media (max-width: 640px)",
};

我们清除了缓存,重新启动…… 仍然出错

Step 6 – Following the Evidence

我们回到出错的 CSS 文件,搜索所有 var(-- 的实例。出现了很多,而且它们都遵循一种模式——出现在应该是媒体查询字符串的地方。

随后我们查看这些值是如何 使用 的,而不仅仅是它们是如何 定义 的:

// In roles.stylex.js
const styles = stylex.create({
  heading: {
    paddingBlock: {
      default: '1.5rem',
      [breakpoints.tablet]: '1.25rem', //  **Have you encountered similar CSS‑in‑JS race conditions?**  
      // > What’s your approach to debugging build‑tool issues? Share your stories in the comments.
    },
  },
});

标签

stylex, vite, css-in-js, debugging, javascript, react, build-tools, lightningcss

Keywords (for search engines)

  • StyleX 无效的空选择器
  • StyleX Vite 错误
  • @stylexjs/unplugin 错误
  • LightningCSS 无效的空选择器
  • StyleX stylex.create 媒体查询错误
  • StyleX 断点不起作用
  • StyleX defineConsts Vite
  • StyleX 计算属性键
  • Vite CSS‑in‑JS 竞争条件
  • StyleX var(--…) 在媒体查询中
  • @media var CSS 错误
  • StyleX unplugin lightningTransform 错误
  • StyleX Vite 开发模式崩溃
  • processCollectedRulesToCSS 错误
Back to Blog

相关文章

阅读更多 »