#1825·stylex

Cross-file `defineConsts` is never inlined — `experimental_crossFileParsing` is unreachable (3 stacked defects)

Author: inf1nite-lo0pCreated Aug 27, 2026Updated Sep 13, 2026

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

bash
git clone https://github.com/inf1nite-lo0p/stylex-defineconsts-crossfile-repro
cd stylex-defineconsts-crossfile-repro
npm install && npm test

No 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 :root

Defects 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:

  1. --xk4kf7g is never declared anywhere — the const was hashed as if it were a defineVars entry, and the literal var(--brand-bg) never reaches the output.
  2. 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):

javascript
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):

javascript
var traverse = require('@babel/traverse');   // line 11 -> { default, NodePath, Scope, … }
...
traverse(astNode, { … });                    // TypeError: traverse is not a function

Every 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: CallExpression

Past 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:

css
: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-plugin 0.19.0, @stylexjs/stylex 0.19.0
  • @babel/core 8.0.1, @babel/traverse 8.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.