Skip to content

fix(react): respect tsconfig jsxImportSource (fix #1448) - #1450

Open
cpruijsen wants to merge 2 commits into
vitejs:mainfrom
cpruijsen:fix/issue-1448
Open

cpruijsen wants to merge 2 commits into
vitejs:mainfrom
cpruijsen:fix/issue-1448

Conversation

@cpruijsen

Copy link
Copy Markdown

Description

Fixes #1448.

react({ compiler: true }) lowers JSX in vite:react-compiler with oxc-transform-react. That transform defaults jsx.importSource to "react" when the option is unset. Vite's oxc transform never sees the original JSX, so the per-file tsconfig inference restored in #726 does not apply.

With no plugin-level jsxImportSource, the compiler transform now reads compilerOptions.jsxImportSource from the file's tsconfig using the same resolveTsconfig Vite uses for oxc JSX. An explicit jsxImportSource option still overrides every file.

Reproduction from the issue (two TSX files, no pragmas):

Plugin options file with jsxImportSource: "@emotion/react" default React file
{ compiler: false } @emotion/react/jsx-runtime react/jsx-runtime
{ compiler: true } (before) react/jsx-runtime react/jsx-runtime
{ compiler: true } (after) @emotion/react/jsx-runtime react/jsx-runtime
{ compiler: true, jsxImportSource: "@emotion/react" } @emotion/react/jsx-runtime @emotion/react/jsx-runtime

What I chose: fill jsx.importSource from the file's tsconfig inside the compiler plugin.

Alternative: set jsx: "preserve" in the compiler plugin so Vite's oxc transform keeps owning JSX, Fast Refresh, and tsconfig inference.

Why: #1419 put JSX and Fast Refresh in the compiler plugin so the compiler sees the original AST. This change only supplies the missing import source and stays reversible. Happy to switch to preserve-JSX if that is preferred.

resolveTsconfig is a hidden rolldown export currently used by Vite. It is loaded through Vite's installation so this package does not gain a new runtime dependency. If you would rather have a public Vite helper, this call site is the consumer.

Checklist

  • Read the Contributing Guidelines.
  • Check that there isn't already a PR that solves the problem the same way.
  • Documentation already states that jsxImportSource is inferred from tsconfig; no extra docs.
  • Tests fail without this change and pass with it (packages/plugin-react unit tests).

The native compiler lowered JSX before Vite could apply per-file
tsconfig inference. Use the same tsconfig resolver as Vite's oxc
transform so compiler: true preserves jsxImportSource.
@doctor8296

doctor8296 commented Sep 11, 2026

Copy link
Copy Markdown

The initial per-file jsxImportSource case works, but the new TsconfigCache becomes stale in a running dev server.
Tested 3beee4f with Vite 8.2.2, Rolldown 1.2.6 and oxc-transform-react 0.147.0:

  1. Start Vite with react({ compiler: true }), no global override, and tsconfig pointing to runtime A.
  2. Change jsxImportSource to runtime B. Vite detects the change and sends full-reload, but subsequent HTTP responses still import A.
  3. Edit the component: the response contains the updated code, but still imports A. Removing jsxImportSource also leaves A instead of reverting to React.

With compiler: false, the same test correctly switches A → B → React. Adding only tsconfigCache?.clear() before the new resolveTsconfig call also makes the native-compiler test pass. This isolates the problem to the cache introduced here, rather than stale HTTP/module responses.
Could you invalidate this cache on relevant tsconfig changes and add a dev-server regression test? Clearing it on every transform is a diagnostic control, but not sure if it solves bug correctly.

The TsconfigCache added in this PR is created once per plugin instance and never
invalidated, so editing a tsconfig during dev keeps resolving the old
jsxImportSource: Vite sends a full-reload, but later responses still import the
previous source. Reported by @doctor8296 on Vite 8.2.2 / Rolldown 1.2.6.

Clearing the cache before every resolveTsconfig call fixes the staleness but
leaves the cache doing nothing. The cache parameter is optional and upstream
resolves without one, so this removes it instead.

Adds a regression test that reuses a single plugin instance across a tsconfig
change. It fails with the cache present, resolving @emotion/react where react is
expected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

react({ compiler: true }) ignores tsconfig jsxImportSource

2 participants