From f1249f8e94499a702b4279383e6daea7c93db401 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Fri, 21 Aug 2026 14:41:36 +0200 Subject: [PATCH 1/4] feat(react): make logging diagnostics an opt-in for React Compiler --- packages/plugin-react/src/index.ts | 30 ++++++++--- .../plugin-react/tests/reactCompiler.test.ts | 54 ++++++++++++++++--- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index b86d7c37b..f880f4531 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -20,6 +20,15 @@ import { defaultCodeFilter, reactCompilerPreset } from './reactCompilerPreset' const _dirname = dirname(fileURLToPath(import.meta.url)) const refreshRuntimePath = join(_dirname, 'refresh-runtime.js') +interface ReactCompilerPluginOptions extends ReactCompilerOptions { + /** + * Log recoverable React Compiler diagnostics through Vite. + * Fatal diagnostics are always logged and fail the transform. + * @default false + */ + logDiagnostics?: boolean +} + export interface Options { /** * Can be used to process extra files like `.mdx` @@ -59,7 +68,7 @@ export interface Options { * @default false * @experimental */ - compiler?: boolean | ReactCompilerOptions + compiler?: boolean | ReactCompilerPluginOptions } const defaultIncludeRE = /\.[tj]sx?$/ @@ -288,7 +297,7 @@ export default function viteReact(opts: Options = {}): Plugin[] { } function createReactCompilerPlugin( - options: ReactCompilerOptions, + options: ReactCompilerPluginOptions, include: NonNullable, exclude: NonNullable, reactOptions: Pick, @@ -351,6 +360,13 @@ function createReactCompilerPlugin( const { transform } = compiler ?? (await loadCompiler((message) => this.error(message))) + const cleanOptions = { ...options } + delete cleanOptions.logDiagnostics + + const reactCompiler: boolean | ReactCompilerOptions = shouldCompile + ? cleanOptions + : false + const result = await transform(id.split('?')[0]!, code, { jsx: { runtime: reactOptions.jsxRuntime, @@ -358,7 +374,7 @@ function createReactCompilerPlugin( importSource: reactOptions.jsxImportSource, refresh: isClient && isFastRefreshEnabled(), }, - reactCompiler: shouldCompile ? options : false, + reactCompiler, sourcemap, }) const diagnostics = result.errors.map( @@ -371,8 +387,10 @@ function createReactCompilerPlugin( diagnostics.join('\n\n') || 'React Compiler transform failed.', ) } - for (const diagnostic of diagnostics) { - this.warn(diagnostic) + if (options.logDiagnostics) { + for (const diagnostic of diagnostics) { + this.warn(diagnostic) + } } return { code: result.code, map: result.map } @@ -384,7 +402,7 @@ function createReactCompilerPlugin( viteReact.preambleCode = preambleCode export { reactCompilerPreset } -export type { ReactCompilerOptions } +export type { ReactCompilerPluginOptions as ReactCompilerOptions } // Compat for require function viteReactForCjs(this: unknown, options: Options): Plugin[] { diff --git a/packages/plugin-react/tests/reactCompiler.test.ts b/packages/plugin-react/tests/reactCompiler.test.ts index fd7abbd2b..8bd3decba 100644 --- a/packages/plugin-react/tests/reactCompiler.test.ts +++ b/packages/plugin-react/tests/reactCompiler.test.ts @@ -101,12 +101,56 @@ describe('compiler option', () => { expect(withoutSourcemap.map).toBeFalsy() expect((await transformWithBuildConfig({}, true)).map).toBeTruthy() }) + + test('logs recoverable diagnostics by default', async () => { + const diagnostics: unknown[] = [] + + await transformWithBuildConfig( + { logDiagnostics: true }, + false, + 'client', + ` + import { useState } from 'react' + + export function App({ condition }) { + if (condition) useState(0) + return
+ } + `, + (diagnostic) => diagnostics.push(diagnostic), + ) + + expect(diagnostics).toHaveLength(1) + expect(diagnostics[0]).toContain( + 'Hooks must always be called in a consistent order', + ) + }) + + test('fails on fatal diagnostics when logging is disabled', async () => { + await expect( + transformWithBuildConfig( + { logDiagnostics: false, panicThreshold: 'all_errors' }, + false, + 'client', + ` + import { useState } from 'react' + + export function App({ condition }) { + if (condition) useState(0) + return
+ } + `, + ), + ).rejects.toThrow('Hooks must always be called in a consistent order') + }) }) async function transformWithBuildConfig( compiler: ReactCompilerOptions, buildSourcemap: boolean, consumer: 'client' | 'server' = 'client', + code: string = `export function App({ name }) { return
{name}
}`, + onWarn?: (message: unknown) => void, ) { const plugin = pluginReact({ compiler }).find( (plugin) => plugin.name === 'vite:react-compiler', @@ -115,7 +159,9 @@ async function transformWithBuildConfig( error(message: unknown): never { throw new Error(String(message)) }, - warn() {}, + warn(message: unknown) { + onWarn?.(message) + }, environment: { config: { consumer } }, } @@ -142,11 +188,7 @@ async function transformWithBuildConfig( if (typeof plugin.transform !== 'object') { throw new Error('Missing transform hook') } - return plugin.transform.handler.call( - context as any, - `export function App({ name }) { return
{name}
}`, - '/entry.tsx', - ) + return plugin.transform.handler.call(context as any, code, '/entry.tsx') } async function getViteReactConfig( From ad18aec4224ee794bcfd61c45122846586c0b6f3 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 24 Aug 2026 15:47:55 +0200 Subject: [PATCH 2/4] Code review --- packages/plugin-react/src/index.ts | 17 +++++------------ .../plugin-react/tests/reactCompiler.test.ts | 18 ------------------ 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index f880f4531..bc7eaca6c 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -297,7 +297,7 @@ export default function viteReact(opts: Options = {}): Plugin[] { } function createReactCompilerPlugin( - options: ReactCompilerPluginOptions, + { logDiagnostics, ...reactCompilerOptions }: ReactCompilerPluginOptions, include: NonNullable, exclude: NonNullable, reactOptions: Pick, @@ -307,7 +307,7 @@ function createReactCompilerPlugin( let jsxDevelopment = false let compiler: typeof import('oxc-transform-react') | undefined const runtime = - options.target === '17' || options.target === '18' + reactCompilerOptions.target === '17' || reactCompilerOptions.target === '18' ? 'react-compiler-runtime' : 'react/compiler-runtime' @@ -353,20 +353,13 @@ function createReactCompilerPlugin( const isClient = this.environment?.config.consumer !== 'server' const shouldCompile = isClient && - (options.compilationMode === 'annotation' + (reactCompilerOptions.compilationMode === 'annotation' ? /['"]use memo['"]/.test(code) : defaultCodeFilter.test(code)) // The config hook is not called when the plugin is used with Rolldown directly. const { transform } = compiler ?? (await loadCompiler((message) => this.error(message))) - const cleanOptions = { ...options } - delete cleanOptions.logDiagnostics - - const reactCompiler: boolean | ReactCompilerOptions = shouldCompile - ? cleanOptions - : false - const result = await transform(id.split('?')[0]!, code, { jsx: { runtime: reactOptions.jsxRuntime, @@ -374,7 +367,7 @@ function createReactCompilerPlugin( importSource: reactOptions.jsxImportSource, refresh: isClient && isFastRefreshEnabled(), }, - reactCompiler, + reactCompiler: shouldCompile ? reactCompilerOptions : false, sourcemap, }) const diagnostics = result.errors.map( @@ -387,7 +380,7 @@ function createReactCompilerPlugin( diagnostics.join('\n\n') || 'React Compiler transform failed.', ) } - if (options.logDiagnostics) { + if (logDiagnostics) { for (const diagnostic of diagnostics) { this.warn(diagnostic) } diff --git a/packages/plugin-react/tests/reactCompiler.test.ts b/packages/plugin-react/tests/reactCompiler.test.ts index 8bd3decba..bcf560703 100644 --- a/packages/plugin-react/tests/reactCompiler.test.ts +++ b/packages/plugin-react/tests/reactCompiler.test.ts @@ -125,24 +125,6 @@ describe('compiler option', () => { 'Hooks must always be called in a consistent order', ) }) - - test('fails on fatal diagnostics when logging is disabled', async () => { - await expect( - transformWithBuildConfig( - { logDiagnostics: false, panicThreshold: 'all_errors' }, - false, - 'client', - ` - import { useState } from 'react' - - export function App({ condition }) { - if (condition) useState(0) - return
- } - `, - ), - ).rejects.toThrow('Hooks must always be called in a consistent order') - }) }) async function transformWithBuildConfig( From c92eb25c0a7da2d3c2766bc9f6ffdf4b2e84ed35 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Tue, 25 Aug 2026 14:35:49 +0200 Subject: [PATCH 3/4] Add README.md, CHANGELOG.md --- packages/plugin-react/CHANGELOG.md | 4 ++++ packages/plugin-react/README.md | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index 6ca06f538..0527be1d1 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Add `compiler.logDiagnostics` option + +Recoverable React Compiler diagnostics are no longer logged by default. Set `compiler.logDiagnostics` to `true` to log them through Vite. Fatal diagnostics are always logged and fail the transform. + ## 6.1.0 (2026-08-19) ### Add experimental native React Compiler support ([#1419](https://github.com/vitejs/vite-plugin-react/pull/1419)) diff --git a/packages/plugin-react/README.md b/packages/plugin-react/README.md index 1799fc321..eb82cb9db 100644 --- a/packages/plugin-react/README.md +++ b/packages/plugin-react/README.md @@ -106,6 +106,12 @@ The `compiler` option also accepts [React Compiler options](https://react.dev/re react({ compiler: { compilationMode: 'annotation' } }) ``` +Set `logDiagnostics` to `true` to log recoverable React Compiler diagnostics through Vite. This option defaults to `false`. Fatal diagnostics are always logged and fail the transform. + +```js +react({ compiler: { logDiagnostics: true } }) +``` + ### Babel React Compiler React Compiler can also be used through Babel with the exported `reactCompilerPreset` helper. This requires [`@rolldown/plugin-babel`](https://npmx.dev/package/@rolldown/plugin-babel), [`babel-plugin-react-compiler`](https://npmx.dev/package/babel-plugin-react-compiler), and [`@babel/core`](https://npmx.dev/package/@babel/core) as peer dependencies: From 569b813e1946cb360a32652470908c98dfb2d8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0?= Date: Fri, 28 Aug 2026 11:16:45 +0900 Subject: [PATCH 4/4] chore: correct test name --- packages/plugin-react/tests/reactCompiler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/tests/reactCompiler.test.ts b/packages/plugin-react/tests/reactCompiler.test.ts index bcf560703..e275b894c 100644 --- a/packages/plugin-react/tests/reactCompiler.test.ts +++ b/packages/plugin-react/tests/reactCompiler.test.ts @@ -102,7 +102,7 @@ describe('compiler option', () => { expect((await transformWithBuildConfig({}, true)).map).toBeTruthy() }) - test('logs recoverable diagnostics by default', async () => { + test('logs recoverable diagnostics when enabled', async () => { const diagnostics: unknown[] = [] await transformWithBuildConfig(