Repository navigation
react({ compiler: true }) ignores tsconfig jsxImportSource #1448
Description
Describe the bug
Enabling the native React Compiler with react({ compiler: true }) changes the JSX runtime selected from each file's nearest tsconfig.json. With no explicit plugin-level jsxImportSource, a file configured to use @emotion/react instead imports react/jsx-runtime.
Expected: enabling the compiler should preserve the documented per-file tsconfig inference.
Two identical TSX files produce these imports:
| Plugin options | styled/component.tsx (jsxImportSource: "@emotion/react") |
plain/component.tsx (default React) |
|---|---|---|
{ compiler: false } |
@emotion/react/jsx-runtime |
react/jsx-runtime |
{ compiler: true } |
react/jsx-runtime — incorrect |
react/jsx-runtime |
{ compiler: true, jsxImportSource: "@emotion/react" } |
@emotion/react/jsx-runtime |
@emotion/react/jsx-runtime |
The last row is expected global-override behavior, not another bug: setting one global source cannot preserve both per-file configurations.
Reproduction
Self-contained files below. No pragmas, Babel, CSS plugins, or workspace tooling are involved. Runtime imports are externalized so the test can inspect the emitted specifiers without installing or executing React/Emotion.
Create the files in an empty directory, then run:
npm install --ignore-scripts
npm run reproThe script checks the compiler-disabled baseline, prints all three configurations, and exits with code 1 for the compiler-enabled regression. The final global-override demonstration does not affect the exit code.
Complete reproduction files
package.json
{
"name": "vite-jsx-import-source-repro",
"private": true,
"type": "module",
"scripts": {
"repro": "node repro.mjs"
},
"devDependencies": {
"@vitejs/plugin-react": "6.1.1",
"oxc-transform-react": "0.145.0",
"vite": "8.2.2"
}
}styled/tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}
}plain/tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx"
}
}Both styled/component.tsx and plain/component.tsx:
export function Component() {
return <div>Hello</div>;
}repro.mjs
import assert from "node:assert/strict";
import { fileURLToPath } from "node:url";
import react from "@vitejs/plugin-react";
import { build } from "vite";
const root = fileURLToPath(new URL(".", import.meta.url));
const expected = {
styled: "@emotion/react/jsx-runtime",
plain: "react/jsx-runtime",
};
let failures = 0;
for (const options of [
{ compiler: false },
{ compiler: true },
{ compiler: true, jsxImportSource: "@emotion/react" },
]) {
const buildOutput = await build({
root,
configFile: false,
logLevel: "silent",
plugins: [react(options)],
build: {
write: false,
minify: false,
lib: {
entry: {
styled: `${root}styled/component.tsx`,
plain: `${root}plain/component.tsx`,
},
formats: ["es"],
},
rolldownOptions: {
external: /^(react|@emotion\/react)(\/|$)/,
output: { preserveModules: true },
},
},
});
console.log(JSON.stringify(options));
const chunks = [buildOutput].flat().flatMap((output) => output.output);
for (const [entry, runtime] of Object.entries(expected)) {
const chunk = chunks.find(
(output) =>
output.type === "chunk" &&
output.facadeModuleId === `${root}${entry}/component.tsx`,
);
assert.ok(chunk, `Missing entry: ${entry}`);
const imports = chunk.imports.filter((specifier) =>
specifier.endsWith("/jsx-runtime"),
);
const matches = imports.length === 1 && imports[0] === runtime;
console.log(
` ${entry}: ${imports.join(", ")} (${matches ? "PASS" : "FAIL"})`,
);
if (!options.compiler) assert.deepEqual(imports, [runtime]);
if (!matches && !options.jsxImportSource) failures++;
}
}
process.exitCode = failures ? 1 : 0;System Info
macOS 26.4.1 arm64
Node 22.14.0
npm 10.9.2
vite 8.2.2
@vitejs/plugin-react 6.1.1
oxc-transform-react 0.145.0
Also reproduced after a clean npm ci --ignore-scripts using the generated lockfile.
Related context
- fix(react): respect tsconfig jsxImportSource by default #726 restored tsconfig inference in the regular JSX transform path. It predates the native compiler integration from feat(react): add native React Compiler support #1419 and does not cover this compiler-enabled path.
- Enabling React Compiler breaks JSX pragma handling #1224 concerns file-level JSX pragma comments being moved below imports. This reproduction contains no pragmas, so moving comments cannot address this case.
- In the native compiler transform,
jsx.importSourceis supplied fromreactOptions.jsxImportSource, and JSX is already lowered before Vite's regular transform can apply the file's tsconfig.
Activity
doctor8296 commentedon Sep 11, 2026
I have exactly the same problem!
ArnaudBarre commentedon Oct 4, 2026
@undefined1e4 @doctor8296 @undefined1e4 Does using the
jsxImportSourceoption have downside for you apart from duplicating the config value?The current suggestion to have the react plugin track the tsconfig seems like an heavy addition
swandir commentedon Oct 4, 2026
Files in the same app can belong to different tsconfigs with different
jsxImportSourceconnected via project references. For example, in a monorepo setup only certain packages may use a custom JSX factory.fix(react): respect tsconfig jsxImportSource (fix vitejs#1448)