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: diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index b86d7c37b..bc7eaca6c 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, + { logDiagnostics, ...reactCompilerOptions }: ReactCompilerPluginOptions, include: NonNullable, exclude: NonNullable, reactOptions: Pick, @@ -298,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' @@ -344,7 +353,7 @@ 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. @@ -358,7 +367,7 @@ function createReactCompilerPlugin( importSource: reactOptions.jsxImportSource, refresh: isClient && isFastRefreshEnabled(), }, - reactCompiler: shouldCompile ? options : false, + reactCompiler: shouldCompile ? reactCompilerOptions : false, sourcemap, }) const diagnostics = result.errors.map( @@ -371,8 +380,10 @@ function createReactCompilerPlugin( diagnostics.join('\n\n') || 'React Compiler transform failed.', ) } - for (const diagnostic of diagnostics) { - this.warn(diagnostic) + if (logDiagnostics) { + for (const diagnostic of diagnostics) { + this.warn(diagnostic) + } } return { code: result.code, map: result.map } @@ -384,7 +395,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..e275b894c 100644 --- a/packages/plugin-react/tests/reactCompiler.test.ts +++ b/packages/plugin-react/tests/reactCompiler.test.ts @@ -101,12 +101,38 @@ describe('compiler option', () => { expect(withoutSourcemap.map).toBeFalsy() expect((await transformWithBuildConfig({}, true)).map).toBeTruthy() }) + + test('logs recoverable diagnostics when enabled', 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', + ) + }) }) 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 +141,9 @@ async function transformWithBuildConfig( error(message: unknown): never { throw new Error(String(message)) }, - warn() {}, + warn(message: unknown) { + onWarn?.(message) + }, environment: { config: { consumer } }, } @@ -142,11 +170,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(