调试 StyleX + Vite:'Invalid Empty Selector' 之谜
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 预先打包这些常量。
要点与调试方法论
- 从显而易见的开始 – 清除缓存,重新安装,重启。
- 阅读堆栈跟踪 – 找到抛出错误的工具(LightningCSS)。
- 捕获中间产物 – 在构建流水线中加入仪器,以输出生成的 CSS。
- 搜索模式 – 错误的
@media var(--…)指向了一个转换 bug。 - 隔离最小复现 – 简化到能够复现错误的最小示例。
- 验证假设 – 将常量替换为字面字符串;错误消失。
- 应用修复或变通方案 – 在 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
defineConstsVite - StyleX 计算属性键
- Vite CSS‑in‑JS 竞争条件
- StyleX
var(--…)在媒体查询中 - @media
varCSS 错误 - StyleX unplugin
lightningTransform错误 - StyleX Vite 开发模式崩溃
processCollectedRulesToCSS错误