facebook / facebook/stylex

[unplugin] Internal Babel pass discovers root babel.config.* — no opt-out

Open
#1,792 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.