[unplugin] Internal Babel pass discovers root babel.config.* — no opt-out
- Dominant language
- JavaScript
- Stars
- 10.3k
- Forks
- 481
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 13
Description
## Describe the issue
`@stylexjs/unplugin` runs its own internal Babel pass over every StyleX file (to apply the StyleX Babel plugin) by calling `@babel/core`'s `transformAsync`. That call hardcodes `babelrc: false`, but **not** `configFile: false` — so Babel still discovers and applies any root `babel.config.js` / `.cjs` / `.mjs` / `.ts` present in the project.
For any project with a root Babel config (Vite templates, Next.js apps, monorepos with a shared Babel config, most non-toy setups), this silently causes every plugin and preset in that root config — `react-compiler`, `relay`, `module-resolver`, Flow/TS stripping, etc. — to run against every StyleX file **inside the StyleX pass**, in addition to running normally in the consumer's main Vite/Rollup/webpack pipeline.
Symptoms range from wasted CPU (we measured ~15-30% of a Vite dev warm build going to this double-transform) to real correctness bugs (React Compiler running twice, `module-resolver` producing subtly different rewrites depending on which pass runs first).
There is currently no way to opt the StyleX Babel pass out of this — the `transformAsync` options aren't exposed on the plugin's user options at all.
## Expected behavior
Consumers should be able to opt the plugin's internal Babel pass out of root-config discovery — for example via `configFile: false` — without having to fork or `pnpm patch` the plugin. Ideally the plugin would also default to `configFile: false` (since the plugin's caller shouldn't need the consumer's root Babel config to do its work), but that's a bigger behavioural change; the immediate fix is just to make it configurable.
## Steps to reproduce
Package versions: `@stylexjs/unplugin@0.19.0`, `@babel/core@7.x`.
1. Create a Vite project that uses `@stylexjs/unplugin/vite`.
2. Add a root `babel.config.cjs` (typical for a React-Compiler / Relay project):
```js
module.exports = {
plugins: ['babel-plugin-react-compiler', 'relay'],
};
```
3. Add any file that imports `@stylexjs/stylex` and calls `stylex.create(...)`.
4. Instrument either plugin (a `console.log` in the plugin's `visitor` or wrapping the plugin function) to log when it runs, or profile the build.
**Observed:** `react-compiler` and `relay` run once during the StyleX unplugin's internal Babel pass, and again in the main Vite React pipeline. Same for any other plugin/preset in the root config.
**Expected:** the StyleX unplugin's internal Babel pass should not apply plugins/presets the caller didn't ask for — or at minimum should expose a way to opt out.
## Test case
Any Vite + React-Compiler + StyleX project reproduces it. The direct code path is [`packages/@stylexjs/unplugin/src/core.js`](https://github.com/facebook/stylex/blob/main/packages/%40stylexjs/unplugin/src/core.js) in `runBabelTransform`:
```js
const result = await transformAsync(inputCode, {
babelrc: false, // only disables .babelrc* files; root babel.config.* still discovered
filename,
presets,
plugins: [...],
caller: {...},
});
```
## Additional comments
I've opened #1791 with a proposed fix: a `babel` option on the plugin's user options that is spread into `transformAsync` before the plugin's mandatory options (so consumers can extend but not override the plugin's internals). The primary use case is `babel: { configFile: false }`.
Happy to iterate on API naming or approach — an alternative would be to default `configFile: false` inside the plugin and have consumers opt back **in** if they want the root config applied. That's more aggressive but arguably more correct. Flagging both for maintainer preference.
We've been running the patched version at Mixcloud for a few weeks and it cleanly resolves both the perf and correctness issues.
Contributor guide
Assessment
This issue has not been assessed yet.