Cross-file `defineConsts` is never inlined — `experimental_crossFileParsing` is unreachable (3 stacked defects)
- Dominant language
- JavaScript
- Stars
- 10.3k
- Forks
- 481
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 13
Description
### 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/babel-plugin@0.19.0`, `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**
```sh
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](https://stylexjs.com/docs/api/javascript/defineConsts), 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`):
```js
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`):
```js
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.
Contributor guide
Assessment
This issue has not been assessed yet.