Cross-file `defineConsts` is never inlined — `experimental_crossFileParsing` is unreachable (3 stacked defects)
Summary
An imported defineConsts value is never inlined. unstable_moduleResolution: { type: "experimental_crossFileParsing" } always throws IMPORT_FILE_PARSING_ERROR, and the commonJS fallback silently treats the constant as a variable, emitting a hashed var() that is never declared plus a block of invalid CSS.
There are three stacked defects in evaluateImportedFile (@stylexjs/[email protected], lib/index.js). Each is only reachable after the previous is fixed, so the cross-file path appears never to have executed successfully.
Repro
https://github.com/inf1nite-lo0p/stylex-defineconsts-crossfile-repro
git clone https://github.com/inf1nite-lo0p/stylex-defineconsts-crossfile-repro
cd stylex-defineconsts-crossfile-repro
npm install && npm testNo bundler, four source files. 6 failing, 1 passing — each failure message names the defect it proves:
✖ experimental_crossFileParsing inlines an imported defineConsts value
compiling threw instead of inlining. StyleX reported:
"There was error when attempting to parse the imported file." This is defect 1.
✖ commonJS does not silently hash a const into an undeclared variable
background-color compiled to var(--xk4kf7g), a variable no stylesheet declares.
The const's literal value var(--brand-bg) never reached the output.
✖ commonJS does not emit a hashed variable as an at-rule prelude
emitted "var(--xx2mxa6){ … }" as a block prelude, which is not valid CSS.
A hashed variable was substituted for the @media string.
✖ defect 1: the deopt guard tests the array itself, not its length
✖ defect 2: traverse is invoked as a function, but the import is a namespace
✖ defect 3: with defects 1 and 2 patched, the evaluator rejects the defineConsts call
with defects 1 and 2 fixed, compiling still fails: "Unsupported expression: CallExpression".
✔ [characterisation] defineVars declares its alias only at :rootDefects 1 and 2 are asserted against the shipped bundle's source, so they go green the moment a fix lands. Defect 3 is proven by loading a copy of the plugin with fixes 1 and 2 applied and compiling again — the chain is demonstrated, not assumed, and the harness throws if either fix stops applying so the repro can't silently rot.
Expected
color.bg inlines to var(--brand-bg) and media.md becomes a real @media rule — matching the docs, which state that constants are inlined at build time.
Actual
# experimental_crossFileParsing
SyntaxError: app.js: There was error when attempting to parse the imported file.
# commonJS — no error, but wrong output
.xfawy5m{padding:4px}
.x6c4j75:not(#\#){background-color:var(--xk4kf7g)}
var(--xx2mxa6){.x1jjukta.x1jjukta:not(#\#):not(#\#){padding:8px}}The commonJS path is the more dangerous of the two, because nothing raises:
--xk4kf7gis never declared anywhere — the const was hashed as if it were adefineVarsentry, and the literalvar(--brand-bg)never reaches the output.var(--xx2mxa6){ … }is not valid CSS. The media constant was hashed into a variable and then used as an at-rule prelude.
Root cause
Defect 1 — the deopt guard is unconditional (lib/index.js:6110):
const ast = core.parseSync(fileContents, { babelrc: true });
if (!ast || ast.errors || !t__namespace.isNode(ast)) {
deopt(bindingPath, state, IMPORT_FILE_PARSING_ERROR);
return;
}Babel sets errors to an empty array on a successful parse, and [] is truthy — so this deopts for every imported file, always.
Fix: ast.errors.length > 0.
Defect 2 — traverse is the module namespace, not the function (lib/index.js:6121):
var traverse = require('@babel/traverse'); // line 11 -> { default, NodePath, Scope, … }
...
traverse(astNode, { … }); // TypeError: traverse is not a functionEvery other use in the bundle is path.traverse(…), a method — line 6121 is the only module-level call, which is consistent with this branch never having run.
Fix: traverse.default(astNode, …) (or import the default).
Defect 3 — the evaluator rejects the defineConsts call itself:
SyntaxError: app.js: Unsupported expression: CallExpressionPast defects 1 and 2, evaluateImportedFile locates export const color = stylex.defineConsts({ … }) and hands the initializer to the evaluator, which only handles a bare object literal. It needs to unwrap the defineConsts(...) call first.
Why this matters
This blocks publishing a design system's token layer as an npm package. defineConsts is the only primitive that preserves per-element custom-property resolution: because the value is inlined, backgroundColor: color.bg compiles to background-color: var(--brand-bg) and re-resolves at whatever element it lands on — so a themed subtree ([data-theme], a nested scope, an elevation context) re-derives correctly.
defineVars cannot substitute for it. The passing characterisation test in the repro records what it emits:
:root, .x6mqh16{--x1bdcq69:var(--brand-bg);}var() is substituted at computed-value time on the element that declares the property, and descendants inherit the substituted value — so redefining --brand-bg inside a subtree never reaches the alias. Every scoped theme silently resolves to the root value, with no error. For a shared, scope-aware token package defineConsts is the only correct mechanism, and it currently cannot cross a module boundary.
Related, but distinct from this parsing bug: #1174, #1497, #1646.
Environment
@stylexjs/babel-plugin0.19.0,@stylexjs/stylex0.19.0@babel/core8.0.1,@babel/traverse8.0.4- Node 26.3.0, Linux
Defect 2 is Babel-version-sensitive in principle, but require('@babel/traverse') has resolved to a namespace object with .default in both Babel 7 and 8, so the call site looks wrong for either.
Happy to open a PR for defects 1 and 2 — they are one-liners. Defect 3 needs a maintainer's call on where to unwrap the defineConsts call.
Source: facebook/stylex