From e866b1d1e9b8a1dc7a43f1be0c510784566b9bb1 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Fri, 3 Oct 2025 09:48:37 -0400 Subject: [PATCH 1/9] Add getRootNode to fabric fragment instance (#34544) Stacked on #34533 for root fragment handling This is the same approach as DOM, where we call getRootNode on the parent. Tests are in react-native using Fantom. --- .../src/ReactFiberConfigFabric.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 40ff26ff89b..e6ac1901c52 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -645,6 +645,9 @@ export type FragmentInstanceType = { observeUsing: (observer: IntersectionObserver) => void, unobserveUsing: (observer: IntersectionObserver) => void, compareDocumentPosition: (otherNode: PublicInstance) => number, + getRootNode(getRootNodeOptions?: { + composed: boolean, + }): Node | FragmentInstanceType, }; function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { @@ -754,6 +757,21 @@ function collectChildren(child: Fiber, collection: Array): boolean { return false; } +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getRootNode = function ( + this: FragmentInstanceType, + getRootNodeOptions?: {composed: boolean}, +): Node | FragmentInstanceType { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { + return this; + } + const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber); + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + const rootNode = (parentHostInstance.getRootNode(getRootNodeOptions): Node); + return rootNode; +}; + export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { From 74dee8ef641c7a0a67db192f83c11ac0d9f279ff Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Fri, 3 Oct 2025 09:54:33 -0400 Subject: [PATCH 2/9] Add getClientRects to fabric fragment instance (#34545) Stacked on #34544 We only have getBoundingClientRect available from RN currently. This should work as a substitute for this case because the equivalent of multi-rect elements in RN is a nested Text component. We only include the rects of top-level host components here so we can assume that calling getBoundingClientRect on each child is the same result. Tested in react-native with Fantom. --- .../src/ReactFiberConfigFabric.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index e6ac1901c52..0f334eea860 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -648,6 +648,7 @@ export type FragmentInstanceType = { getRootNode(getRootNodeOptions?: { composed: boolean, }): Node | FragmentInstanceType, + getClientRects: () => Array, }; function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { @@ -772,6 +773,27 @@ FragmentInstance.prototype.getRootNode = function ( return rootNode; }; +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getClientRects = function ( + this: FragmentInstanceType, +): Array { + const rects: Array = []; + traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects); + return rects; +}; +function collectClientRects(child: Fiber, rects: Array): boolean { + const instance = getPublicInstanceFromHostFiber(child); + + // getBoundingClientRect is available on Fabric instances while getClientRects is not. + // This should work as a substitute in this case because the only equivalent of a multi-rect + // element in RN would be a nested Text component. + // Since we only use top-level nodes here, we can assume that getBoundingClientRect is sufficient. + // $FlowFixMe[method-unbinding] + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + rects.push(instance.getBoundingClientRect()); + return false; +} + export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { From 0eebd37041a5712f841edd5fad558e0516e5af61 Mon Sep 17 00:00:00 2001 From: Eugene Choi <4eugenechoi@gmail.com> Date: Fri, 3 Oct 2025 10:52:36 -0400 Subject: [PATCH 3/9] [playground] Config panel quality fixes (#34611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed two small issues with the config panel in the compiler playground: 1. Object descriptions were being confined in the config box and most of it would not be visible upon hover 2. Changed it so that "Applied Configs" would only display a valid set of configs, rather than switching between "Invalid Configs" and the set of options. This would be less visually jarring for users as the Output panel already displays errors. Additionally, if users want to see the list of config options but have a currently broken config, they would previously not know how to fix it. Object hover before: Screenshot 2025-09-26 at 10 41 03 AM Hover after: Screenshot 2025-09-26 at 10 40 37 AM Applied Configs always displays the last valid set of configs: https://github.com/user-attachments/assets/2fb9232f-7388-4488-9b7a-bb48bf09e4ca --- .../components/Editor/ConfigEditor.tsx | 49 +-- .../components/Editor/EditorImpl.tsx | 319 +----------------- .../components/Editor/monacoOptions.ts | 11 + compiler/apps/playground/lib/compilation.ts | 308 +++++++++++++++++ 4 files changed, 349 insertions(+), 338 deletions(-) create mode 100644 compiler/apps/playground/lib/compilation.ts diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx index d922f27c978..18f904d225f 100644 --- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx +++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx @@ -6,7 +6,6 @@ */ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react'; -import {PluginOptions} from 'babel-plugin-react-compiler'; import type {editor} from 'monaco-editor'; import * as monaco from 'monaco-editor'; import React, { @@ -18,9 +17,8 @@ import React, { } from 'react'; import {Resizable} from 're-resizable'; import {useStore, useStoreDispatch} from '../StoreContext'; -import {monacoOptions} from './monacoOptions'; +import {monacoConfigOptions} from './monacoOptions'; import {IconChevron} from '../Icons/IconChevron'; -import prettyFormat from 'pretty-format'; import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes'; // @ts-expect-error - webpack asset/source loader handles .d.ts files as strings @@ -29,9 +27,9 @@ import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts'; loader.config({monaco}); export default function ConfigEditor({ - appliedOptions, + formattedAppliedConfig, }: { - appliedOptions: PluginOptions | null; + formattedAppliedConfig: string; }): React.ReactElement { const [isExpanded, setIsExpanded] = useState(false); @@ -49,7 +47,7 @@ export default function ConfigEditor({ setIsExpanded(false); }); }} - appliedOptions={appliedOptions} + formattedAppliedConfig={formattedAppliedConfig} />
void; - appliedOptions: PluginOptions | null; + onToggle: (expanded: boolean) => void; + formattedAppliedConfig: string; }): React.ReactElement { const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -122,13 +120,6 @@ function ExpandedEditor({ }); }; - const formattedAppliedOptions = appliedOptions - ? prettyFormat(appliedOptions, { - printFunctionName: false, - printBasicPrototype: false, - }) - : 'Invalid configs'; - return ( @@ -158,7 +149,7 @@ function ExpandedEditor({ Config Overrides
-
+
@@ -186,23 +168,16 @@ function ExpandedEditor({ Applied Configs -
+
diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 9f000f85564..5b39b91654e 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,312 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse, ParseResult} from '@babel/parser'; -import * as HermesParser from 'hermes-parser'; -import * as t from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerError, +import { CompilerErrorDetail, CompilerDiagnostic, - Effect, - ErrorCategory, - parseConfigPragmaForTests, - ValueKind, - type Hook, - PluginOptions, - CompilerPipelineValue, - parsePluginOptions, - printReactiveFunctionWithOutlined, - printFunctionWithOutlined, - type LoggerEvent, } from 'babel-plugin-react-compiler'; -import {useDeferredValue, useMemo} from 'react'; +import {useDeferredValue, useMemo, useState} from 'react'; import {useStore} from '../StoreContext'; import ConfigEditor from './ConfigEditor'; import Input from './Input'; -import { - CompilerOutput, - CompilerTransformOutput, - default as Output, - PrintedCompilerPipelineValue, -} from './Output'; -import {transformFromAstSync} from '@babel/core'; - -function parseInput( - input: string, - language: 'flow' | 'typescript', -): ParseResult { - // Extract the first line to quickly check for custom test directives - if (language === 'flow') { - return HermesParser.parse(input, { - babel: true, - flow: 'all', - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }); - } else { - return babelParse(input, { - plugins: ['typescript', 'jsx'], - sourceType: 'module', - }) as ParseResult; - } -} - -function invokeCompiler( - source: string, - language: 'flow' | 'typescript', - options: PluginOptions, -): CompilerTransformOutput { - const ast = parseInput(source, language); - let result = transformFromAstSync(ast, source, { - filename: '_playgroundFile.js', - highlightCode: false, - retainLines: true, - plugins: [[BabelPluginReactCompiler, options]], - ast: true, - sourceType: 'module', - configFile: false, - sourceMaps: true, - babelrc: false, - }); - if (result?.ast == null || result?.code == null || result?.map == null) { - throw new Error('Expected successful compilation'); - } - return { - code: result.code, - sourceMaps: result.map, - language, - }; -} - -const COMMON_HOOKS: Array<[string, Hook]> = [ - [ - 'useFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePaginationFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useRefetchableFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useLazyLoadQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePreloadedQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], -]; - -function parseOptions( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): PluginOptions { - // Extract the first line to quickly check for custom test directives - const pragma = source.substring(0, source.indexOf('\n')); - - const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { - compilationMode: 'infer', - environment: - mode === 'linter' - ? { - // enabled in compiler - validateRefAccessDuringRender: false, - // enabled in linter - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - } - : { - /* use defaults for compiler mode */ - }, - }); - - // Parse config overrides from config editor - let configOverrideOptions: any = {}; - const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); - if (configOverrides.trim()) { - if (configMatch && configMatch[1]) { - const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); - configOverrideOptions = new Function(`return (${configString})`)(); - } else { - throw new Error('Invalid override format'); - } - } - - const opts: PluginOptions = parsePluginOptions({ - ...parsedPragmaOptions, - ...configOverrideOptions, - environment: { - ...parsedPragmaOptions.environment, - ...configOverrideOptions.environment, - customHooks: new Map([...COMMON_HOOKS]), - }, - }); - - return opts; -} - -function compile( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { - const results = new Map>(); - const error = new CompilerError(); - const otherErrors: Array = []; - const upsert: (result: PrintedCompilerPipelineValue) => void = result => { - const entry = results.get(result.name); - if (Array.isArray(entry)) { - entry.push(result); - } else { - results.set(result.name, [result]); - } - }; - let language: 'flow' | 'typescript'; - if (source.match(/\@flow/)) { - language = 'flow'; - } else { - language = 'typescript'; - } - let transformOutput; - - let baseOpts: PluginOptions | null = null; - try { - baseOpts = parseOptions(source, mode, configOverrides); - } catch (err) { - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Config, - reason: `Unexpected failure when transforming configs! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - if (baseOpts) { - try { - const logIR = (result: CompilerPipelineValue): void => { - switch (result.kind) { - case 'ast': { - break; - } - case 'hir': { - upsert({ - kind: 'hir', - fnName: result.value.id, - name: result.name, - value: printFunctionWithOutlined(result.value), - }); - break; - } - case 'reactive': { - upsert({ - kind: 'reactive', - fnName: result.value.id, - name: result.name, - value: printReactiveFunctionWithOutlined(result.value), - }); - break; - } - case 'debug': { - upsert({ - kind: 'debug', - fnName: null, - name: result.name, - value: result.value, - }); - break; - } - default: { - const _: never = result; - throw new Error(`Unhandled result ${result}`); - } - } - }; - // Add logger options to the parsed options - const opts = { - ...baseOpts, - logger: { - debugLogIRs: logIR, - logEvent: (_filename: string | null, event: LoggerEvent): void => { - if (event.kind === 'CompileError') { - otherErrors.push(event.detail); - } - }, - }, - }; - transformOutput = invokeCompiler(source, language, opts); - } catch (err) { - /** - * error might be an invariant violation or other runtime error - * (i.e. object shape that is not CompilerError) - */ - if (err instanceof CompilerError && err.details.length > 0) { - error.merge(err); - } else { - /** - * Handle unexpected failures by logging (to get a stack trace) - * and reporting - */ - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Invariant, - reason: `Unexpected failure when transforming input! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - } - } - // Only include logger errors if there weren't other errors - if (!error.hasErrors() && otherErrors.length !== 0) { - otherErrors.forEach(e => error.details.push(e)); - } - if (error.hasErrors()) { - return [{kind: 'err', results, error}, language, baseOpts]; - } - return [ - {kind: 'ok', results, transformOutput, errors: error.details}, - language, - baseOpts, - ]; -} +import {CompilerOutput, default as Output} from './Output'; +import {compile} from '../../lib/compilation'; +import prettyFormat from 'pretty-format'; export default function Editor(): JSX.Element { const store = useStore(); @@ -323,6 +28,7 @@ export default function Editor(): JSX.Element { () => compile(deferredStore.source, 'linter', deferredStore.config), [deferredStore.source, deferredStore.config], ); + const [formattedAppliedConfig, setFormattedAppliedConfig] = useState(''); let mergedOutput: CompilerOutput; let errors: Array; @@ -336,11 +42,22 @@ export default function Editor(): JSX.Element { mergedOutput = compilerOutput; errors = compilerOutput.error.details; } + + if (appliedOptions) { + const formatted = prettyFormat(appliedOptions, { + printFunctionName: false, + printBasicPrototype: false, + }); + if (formatted !== formattedAppliedConfig) { + setFormattedAppliedConfig(formatted); + } + } + return ( <>
- +
diff --git a/compiler/apps/playground/components/Editor/monacoOptions.ts b/compiler/apps/playground/components/Editor/monacoOptions.ts index 7fed1b7875f..d52c8bbedfa 100644 --- a/compiler/apps/playground/components/Editor/monacoOptions.ts +++ b/compiler/apps/playground/components/Editor/monacoOptions.ts @@ -32,3 +32,14 @@ export const monacoOptions: Partial = { tabSize: 2, }; + +export const monacoConfigOptions: Partial = { + ...monacoOptions, + lineNumbers: 'off', + renderLineHighlight: 'none', + overviewRulerBorder: false, + overviewRulerLanes: 0, + fontSize: 12, + scrollBeyondLastLine: false, + glyphMargin: false, +}; diff --git a/compiler/apps/playground/lib/compilation.ts b/compiler/apps/playground/lib/compilation.ts new file mode 100644 index 00000000000..10bf0164c0e --- /dev/null +++ b/compiler/apps/playground/lib/compilation.ts @@ -0,0 +1,308 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {parse as babelParse, ParseResult} from '@babel/parser'; +import * as HermesParser from 'hermes-parser'; +import * as t from '@babel/types'; +import BabelPluginReactCompiler, { + CompilerError, + CompilerErrorDetail, + CompilerDiagnostic, + Effect, + ErrorCategory, + parseConfigPragmaForTests, + ValueKind, + type Hook, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, + printReactiveFunctionWithOutlined, + printFunctionWithOutlined, + type LoggerEvent, +} from 'babel-plugin-react-compiler'; +import {transformFromAstSync} from '@babel/core'; +import type { + CompilerOutput, + CompilerTransformOutput, + PrintedCompilerPipelineValue, +} from '../components/Editor/Output'; + +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { + // Extract the first line to quickly check for custom test directives + if (language === 'flow') { + return HermesParser.parse(input, { + babel: true, + flow: 'all', + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }); + } else { + return babelParse(input, { + plugins: ['typescript', 'jsx'], + sourceType: 'module', + }) as ParseResult; + } +} + +function invokeCompiler( + source: string, + language: 'flow' | 'typescript', + options: PluginOptions, +): CompilerTransformOutput { + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, options]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); + } + return { + code: result.code, + sourceMaps: result.map, + language, + }; +} + +const COMMON_HOOKS: Array<[string, Hook]> = [ + [ + 'useFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePaginationFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useRefetchableFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useLazyLoadQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePreloadedQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], +]; + +function parseOptions( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): PluginOptions { + // Extract the first line to quickly check for custom test directives + const pragma = source.substring(0, source.indexOf('\n')); + + const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { + compilationMode: 'infer', + environment: + mode === 'linter' + ? { + // enabled in compiler + validateRefAccessDuringRender: false, + // enabled in linter + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + } + : { + /* use defaults for compiler mode */ + }, + }); + + // Parse config overrides from config editor + let configOverrideOptions: any = {}; + const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); + if (configOverrides.trim()) { + if (configMatch && configMatch[1]) { + const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); + configOverrideOptions = new Function(`return (${configString})`)(); + } else { + throw new Error('Invalid override format'); + } + } + + const opts: PluginOptions = parsePluginOptions({ + ...parsedPragmaOptions, + ...configOverrideOptions, + environment: { + ...parsedPragmaOptions.environment, + ...configOverrideOptions.environment, + customHooks: new Map([...COMMON_HOOKS]), + }, + }); + + return opts; +} + +export function compile( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { + const results = new Map>(); + const error = new CompilerError(); + const otherErrors: Array = []; + const upsert: (result: PrintedCompilerPipelineValue) => void = result => { + const entry = results.get(result.name); + if (Array.isArray(entry)) { + entry.push(result); + } else { + results.set(result.name, [result]); + } + }; + let language: 'flow' | 'typescript'; + if (source.match(/\@flow/)) { + language = 'flow'; + } else { + language = 'typescript'; + } + let transformOutput; + + let baseOpts: PluginOptions | null = null; + try { + baseOpts = parseOptions(source, mode, configOverrides); + } catch (err) { + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Config, + reason: `Unexpected failure when transforming configs! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + if (baseOpts) { + try { + const logIR = (result: CompilerPipelineValue): void => { + switch (result.kind) { + case 'ast': { + break; + } + case 'hir': { + upsert({ + kind: 'hir', + fnName: result.value.id, + name: result.name, + value: printFunctionWithOutlined(result.value), + }); + break; + } + case 'reactive': { + upsert({ + kind: 'reactive', + fnName: result.value.id, + name: result.name, + value: printReactiveFunctionWithOutlined(result.value), + }); + break; + } + case 'debug': { + upsert({ + kind: 'debug', + fnName: null, + name: result.name, + value: result.value, + }); + break; + } + default: { + const _: never = result; + throw new Error(`Unhandled result ${result}`); + } + } + }; + // Add logger options to the parsed options + const opts = { + ...baseOpts, + logger: { + debugLogIRs: logIR, + logEvent: (_filename: string | null, event: LoggerEvent): void => { + if (event.kind === 'CompileError') { + otherErrors.push(event.detail); + } + }, + }, + }; + transformOutput = invokeCompiler(source, language, opts); + } catch (err) { + /** + * error might be an invariant violation or other runtime error + * (i.e. object shape that is not CompilerError) + */ + if (err instanceof CompilerError && err.details.length > 0) { + error.merge(err); + } else { + /** + * Handle unexpected failures by logging (to get a stack trace) + * and reporting + */ + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Invariant, + reason: `Unexpected failure when transforming input! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + } + } + // Only include logger errors if there weren't other errors + if (!error.hasErrors() && otherErrors.length !== 0) { + otherErrors.forEach(e => error.details.push(e)); + } + if (error.hasErrors()) { + return [{kind: 'err', results, error}, language, baseOpts]; + } + return [ + {kind: 'ok', results, transformOutput, errors: error.details}, + language, + baseOpts, + ]; +} From 02bd4458f77e60b5631ff2f0e30b05b8b3a56d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 3 Oct 2025 11:52:44 -0400 Subject: [PATCH 4/9] [DevTools] Double clicking the root should jump to the beginning of the timeline (#34704) Unlike the rects, this never toggles. It just jumps. --- .../src/devtools/views/SuspenseTab/SuspenseRects.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index 8e43944e7a7..f949631be84 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -330,12 +330,25 @@ function SuspenseRectsContainer(): React$Node { }); } + function handleDoubleClick(event: SyntheticMouseEvent) { + if (event.defaultPrevented) { + // Already clicked on an inner rect + return; + } + event.preventDefault(); + suspenseTreeDispatch({ + type: 'SUSPENSE_SET_TIMELINE_INDEX', + payload: 0, + }); + } + const isRootSelected = roots.includes(inspectedElementID); return (
Date: Fri, 3 Oct 2025 09:13:55 -0700 Subject: [PATCH 5/9] [compiler] Remove @babel/plugin-proposal-private-methods (#34715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit redo of #34458 but fixing up prettier Co-authored-by: Arnaud Barré --- .../eslint-plugin-react-compiler/babel.config.js | 1 - .../packages/eslint-plugin-react-compiler/package.json | 1 - .../src/shared/RunReactCompiler.ts | 7 +------ .../eslint-plugin-react-compiler/tsup.config.ts | 8 +------- compiler/yarn.lock | 10 +--------- packages/eslint-plugin-react-hooks/package.json | 1 - .../src/shared/RunReactCompiler.ts | 7 +------ scripts/rollup/bundles.js | 1 - yarn.lock | 8 -------- 9 files changed, 4 insertions(+), 40 deletions(-) diff --git a/compiler/packages/eslint-plugin-react-compiler/babel.config.js b/compiler/packages/eslint-plugin-react-compiler/babel.config.js index 8fc8dfbc24c..9be68a85b7f 100644 --- a/compiler/packages/eslint-plugin-react-compiler/babel.config.js +++ b/compiler/packages/eslint-plugin-react-compiler/babel.config.js @@ -10,6 +10,5 @@ module.exports = { plugins: [ ['@babel/plugin-transform-private-property-in-object', {loose: true}], ['@babel/plugin-transform-class-properties', {loose: true}], - ['@babel/plugin-transform-private-methods', {loose: true}], ], }; diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index e5402611e2b..6f15237c832 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -14,7 +14,6 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts index 53d2f31e0f4..419dc3841c7 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -8,8 +8,6 @@ import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; import {File} from '@babel/types'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import BabelPluginReactCompiler, { parsePluginOptions, validateEnvironmentConfig, @@ -145,10 +143,7 @@ function runReactCompilerImpl({ filename, highlightCode: false, retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], + plugins: [[BabelPluginReactCompiler, options]], sourceType: 'module', configFile: false, babelrc: false, diff --git a/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts b/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts index 3e3b1b13131..4b4f5264394 100644 --- a/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts +++ b/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts @@ -10,13 +10,7 @@ import {defineConfig} from 'tsup'; export default defineConfig({ entry: ['./src/index.ts'], outDir: './dist', - external: [ - '@babel/core', - '@babel/plugin-proposal-private-methods', - 'hermes-parser', - 'zod', - 'zod-validation-error', - ], + external: ['@babel/core', 'hermes-parser', 'zod', 'zod-validation-error'], splitting: false, sourcemap: false, dts: false, diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 696261cbf53..09828954196 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -326,7 +326,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0": +"@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0": version "7.27.0" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz" integrity sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg== @@ -706,14 +706,6 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/traverse" "^7.25.9" -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 8f7cfc361d1..06b15baf89d 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -41,7 +41,6 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index fc9067d2740..acf0564d913 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -9,8 +9,6 @@ import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; import {File} from '@babel/types'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import BabelPluginReactCompiler, { parsePluginOptions, validateEnvironmentConfig, @@ -147,10 +145,7 @@ function runReactCompilerImpl({ filename, highlightCode: false, retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], + plugins: [[BabelPluginReactCompiler, options]], sourceType: 'module', configFile: false, babelrc: false, diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 33bce79e4c7..c66b43797f1 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1253,7 +1253,6 @@ const bundles = [ preferBuiltins: true, externals: [ '@babel/core', - '@babel/plugin-proposal-private-methods', 'hermes-parser', 'zod', 'zod-validation-error', diff --git a/yarn.lock b/yarn.lock index d2f2e8d017c..568aebc99e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,14 +1108,6 @@ "@babel/helper-create-class-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" From f24d3bbc70ae6544b5aafd250f22f0a68b4a3107 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:47:34 -0700 Subject: [PATCH 6/9] Update readme for eprh (#34714) --- packages/eslint-plugin-react-hooks/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index 20d32fe9fd1..afd89ab2263 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -1,8 +1,6 @@ # `eslint-plugin-react-hooks` -This ESLint plugin enforces the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). - -It is a part of the [Hooks API](https://react.dev/reference/react/hooks) for React. +The official ESLint plugin for [React](https://react.dev) which enforces the [Rules of React](https://react.dev/reference/eslint-plugin-react-hooks) and other best practices. ## Installation @@ -89,7 +87,7 @@ If you're using a version earlier than 5.2.0, the legacy config was simply `reco ### Custom Configuration -If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file: +If you want more fine-grained configuration, you can instead choose to enable specific rules. However, we strongly encourage using the recommended presets — see above — so that you will automatically receive new recommended rules as we add them in future versions of the plugin. #### Flat Config (eslint.config.js|ts) From 71753ac90a009ddefe2f7653165902d55edd1898 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 3 Oct 2025 12:58:00 -0400 Subject: [PATCH 7/9] [eprh] Remove hermes-parser (#34719) We will be focusing eslint-plugin-react-hooks as the primary OSS-only package for our lint plugin. eslint-plugin-react-compiler will remain as a Meta only package as some limitations of our internal infra require us to use packages that aren't widely adopted by the rest of the industry. This PR removes `hermes-parser`, which was meant to support parsing Flow syntax. --- .../eslint-plugin-react-hooks/package.json | 9 +++--- .../src/shared/RunReactCompiler.ts | 31 +++++-------------- yarn.lock | 12 ------- 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 06b15baf89d..99abe449472 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -41,7 +41,6 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" }, @@ -50,14 +49,14 @@ "@babel/preset-typescript": "^7.26.0", "@babel/types": "^7.19.0", "@tsconfig/strictest": "^2.0.5", - "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", - "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", - "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0", - "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.62.0", "@types/eslint": "^8.56.12", "@types/estree": "^1.0.6", "@types/estree-jsx": "^1.0.5", "@types/node": "^20.2.5", + "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", + "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", + "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0", + "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.62.0", "babel-eslint": "^10.0.3", "eslint-v7": "npm:eslint@^7.7.0", "eslint-v8": "npm:eslint@^8.57.1", diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index acf0564d913..7755dbe947f 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -17,7 +17,6 @@ import BabelPluginReactCompiler, { LoggerEvent, } from 'babel-plugin-react-compiler'; import type {SourceCode} from 'eslint'; -import * as HermesParser from 'hermes-parser'; import {isDeepStrictEqual} from 'util'; import type {ParseResult} from '@babel/parser'; @@ -78,7 +77,6 @@ function getFlowSuppressions( return results; } - function runReactCompilerImpl({ sourceCode, filename, @@ -115,27 +113,14 @@ function runReactCompilerImpl({ } let babelAST: ParseResult | null = null; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - babelAST = babelParse(sourceCode.text, { - sourceFilename: filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch (err: unknown) { + /* empty */ } if (babelAST != null) { diff --git a/yarn.lock b/yarn.lock index 568aebc99e6..f5ac1f649dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10190,11 +10190,6 @@ hermes-eslint@^0.32.0: hermes-estree "0.32.0" hermes-parser "0.32.0" -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - hermes-estree@0.29.1: version "0.29.1" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5" @@ -10219,13 +10214,6 @@ hermes-parser@0.32.0, hermes-parser@^0.32.0: dependencies: hermes-estree "0.32.0" -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" From d6eb735938bc67b41ad723206ea395ba4d761139 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:08:20 -0700 Subject: [PATCH 8/9] [compiler] Update for Zod v3/v4 compatibility (#34717) Partial redo of #34710. The changes there tried to use `z.function(args, return)` to be compatible across Zod v3 and v4, but Zod 4's function API has completely changed. Instead, I've updated to just use `z.any()` where we expect a function, and manually validate that it's a function before we call the value. We already have validation of the return type (also using Zod). Co-authored-by: kolvian --- .../babel-plugin-react-compiler/package.json | 4 +- .../src/HIR/Environment.ts | 10 +++- .../src/__tests__/envConfig-test.ts | 4 +- .../eslint-plugin-react-compiler/package.json | 4 +- .../react-compiler-healthcheck/package.json | 4 +- .../packages/react-mcp-server/package.json | 2 +- compiler/yarn.lock | 49 +++++-------------- .../eslint-plugin-react-hooks/package.json | 4 +- 8 files changed, 31 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index 75cf3ba53ce..8d3f1c8ae68 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -52,8 +52,8 @@ "react-dom": "0.0.0-experimental-4beb1fd8-20241118", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "resolutions": { "./**/@babel/parser": "7.7.4", diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 9fa88680ca2..7889e13c2f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -159,7 +159,7 @@ export const EnvironmentConfigSchema = z.object({ * A function that, given the name of a module, can optionally return a description * of that module's type signature. */ - moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null), + moduleTypeProvider: z.nullable(z.any()).default(null), /** * A list of functions which the application compiles as macros, where @@ -249,7 +249,7 @@ export const EnvironmentConfigSchema = z.object({ * Allows specifying a function that can populate HIR with type information from * Flow */ - flowTypeProvider: z.nullable(z.function().args(z.string())).default(null), + flowTypeProvider: z.nullable(z.any()).default(null), /** * Enables inference of optional dependency chains. Without this flag @@ -906,6 +906,12 @@ export class Environment { if (moduleTypeProvider == null) { return null; } + if (typeof moduleTypeProvider !== 'function') { + CompilerError.throwInvalidConfig({ + reason: `Expected a function for \`moduleTypeProvider\``, + loc, + }); + } const unparsedModuleConfig = moduleTypeProvider(moduleName); if (unparsedModuleConfig != null) { const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts index f8a6330977f..933990b6bdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts @@ -20,7 +20,7 @@ describe('parseConfigPragma()', () => { validateHooksUsage: 1, } as any); }).toThrowErrorMatchingInlineSnapshot( - `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage"."`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Invalid input: expected boolean, received number at "validateHooksUsage"."`, ); }); @@ -38,7 +38,7 @@ describe('parseConfigPragma()', () => { ], } as any); }).toThrowErrorMatchingInlineSnapshot( - `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: AutodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, ); }); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 6f15237c832..6c95bf495c6 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -15,8 +15,8 @@ "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": { "@babel/preset-env": "^7.22.4", diff --git a/compiler/packages/react-compiler-healthcheck/package.json b/compiler/packages/react-compiler-healthcheck/package.json index 2f6d625eb83..61825b73d83 100644 --- a/compiler/packages/react-compiler-healthcheck/package.json +++ b/compiler/packages/react-compiler-healthcheck/package.json @@ -17,8 +17,8 @@ "fast-glob": "^3.3.2", "ora": "5.4.1", "yargs": "^17.7.2", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": {}, "engines": { diff --git a/compiler/packages/react-mcp-server/package.json b/compiler/packages/react-mcp-server/package.json index 191ff4f9e19..4d744c1d667 100644 --- a/compiler/packages/react-mcp-server/package.json +++ b/compiler/packages/react-mcp-server/package.json @@ -24,7 +24,7 @@ "html-to-text": "^9.0.5", "prettier": "^3.3.3", "puppeteer": "^24.7.2", - "zod": "^3.23.8" + "zod": "^3.22.4 || ^4.0.0" }, "devDependencies": { "@types/html-to-text": "^9.0.4", diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 09828954196..daafc705fdc 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -10486,16 +10486,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10568,14 +10559,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11352,7 +11336,7 @@ workerpool@^6.5.1: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11370,15 +11354,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" @@ -11530,17 +11505,17 @@ zod-to-json-schema@^3.24.1: resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz" integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== -zod-validation-error@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz" - integrity sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ== +"zod-validation-error@^3.0.3 || ^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== -zod-validation-error@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.0.3.tgz" - integrity sha512-cETTrcMq3Ze58vhdR0zD37uJm/694I6mAxcf/ei5bl89cC++fBNxrC2z8lkFze/8hVMPwrbtrwXHR2LB50fpHw== +"zod@^3.22.4 || ^4.0.0": + version "4.1.11" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.11.tgz#4aab62f76cfd45e6c6166519ba31b2ea019f75f5" + integrity sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg== -zod@^3.22.4, zod@^3.23.8, zod@^3.24.1: +zod@^3.23.8, zod@^3.24.1: version "3.24.3" resolved "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz" integrity sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg== diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 99abe449472..557e48af65f 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -41,8 +41,8 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.11.4", From 614a945d9d1031fadcf211a632cb2d7fda495a4f Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Fri, 3 Oct 2025 19:48:28 +0200 Subject: [PATCH 9/9] React DevTools 7.0.0 (#34692) [Preview](https://github.com/eps1lon/react/blob/sebbie/09-28-react_devtools_7.0.0/packages/react-devtools/CHANGELOG.md#700) Suspense tab is omitted since that's gated on Canary or 19.3. Will draft a separate blog post for suspended by and open-in-editor instructions while the extension is in review. --- packages/react-devtools-core/package.json | 2 +- packages/react-devtools-inline/package.json | 2 +- packages/react-devtools-timeline/package.json | 2 +- packages/react-devtools/CHANGELOG.md | 43 +++++++++++++++++++ packages/react-devtools/package.json | 2 +- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index 8ce8f436e81..c8facd1a24d 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-core", - "version": "6.1.5", + "version": "7.0.0", "description": "Use react-devtools outside of the browser", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json index 9d207527b81..0363530cd10 100644 --- a/packages/react-devtools-inline/package.json +++ b/packages/react-devtools-inline/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-inline", - "version": "6.1.5", + "version": "7.0.0", "description": "Embed react-devtools within a website", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-timeline/package.json b/packages/react-devtools-timeline/package.json index 6663eafd3fe..2f54399ce25 100644 --- a/packages/react-devtools-timeline/package.json +++ b/packages/react-devtools-timeline/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "react-devtools-timeline", - "version": "6.1.5", + "version": "7.0.0", "license": "MIT", "dependencies": { "@elg/speedscope": "1.9.0-a6f84db", diff --git a/packages/react-devtools/CHANGELOG.md b/packages/react-devtools/CHANGELOG.md index 1acf089bf9c..0fc637b9946 100644 --- a/packages/react-devtools/CHANGELOG.md +++ b/packages/react-devtools/CHANGELOG.md @@ -4,6 +4,49 @@ --- +### 7.0.0 +Oct 2, 2025 + +Add a "suspended by" section showing all causes of why the inspected element suspended. This includes: + +- `await` in Server Components +- `React.lazy` +- `use()` +- suspensey images, CSS, and fonts + +Add a Code Editor Sidebar Pane in the Chrome Sources Tab (@sebmarkbage [#33968](https://github.com/facebook/react/pull/33968), [#33987](https://github.com/facebook/react/pull/33987), [#33988](https://github.com/facebook/react/pull/33988)) + +Add Option to Open Local Files directly in External Editor (@sebmarkbage [#33983](https://github.com/facebook/react/pull/33983), [#33985](https://github.com/facebook/react/pull/33985), [#33965](https://github.com/facebook/react/pull/33965)) + +#### Other changes + +- Allow inspection of React tree before streaming has finished (@eps1lon [#34360](https://github.com/facebook/react/pull/34360)) +- Always attempt to mount dehydrated roots (@eps1lon [#34209](https://github.com/facebook/react/pull/34209)) +- Show `name` prop of Suspense / Activity in the Components Tree view (@sebmarkbage [#34135](https://github.com/facebook/react/pull/34135)) +- 1st class support of used Thenables (@sebmarkbage, @eps1lon: [#32989](https://github.com/facebook/react/pull/32989), [#34097](https://github.com/facebook/react/pull/34097)) +- Allow inspecting cause, name, message, stack of Errors in props (@eps1lon [#33023](https://github.com/facebook/react/pull/33023)) +- Allow Introspection of React Elements and React.lazy (@sebmarkbage [#34129](https://github.com/facebook/react/pull/34129)) +- Fix "View source" for sources with URLs that aren't normalized (@eps1lon [#32951](https://github.com/facebook/react/pull/32951)) +- Fix symbolication with Index Source Maps (@eps1lon [#34300](https://github.com/facebook/react/pull/34300)) +- Ignore List Stack Traces (@sebmarkbage [#34210](https://github.com/facebook/react/pull/34210)) +- Linkify Source View (@sebmarkbage [#33954](https://github.com/facebook/react/pull/33954)) +- Only show state for ClassComponents (@sebmarkbage [#34091](https://github.com/facebook/react/pull/34091)) +- Show Owner Stacks in "rendered by" View (@sebmarkbage [#34130](https://github.com/facebook/react/pull/34130)) +- Stop mounting empty roots (@eps1lon [#34467](https://github.com/facebook/react/pull/34467)) +- Show changed hooks names in the Profiler tab (@piotrski [#31398](https://github.com/facebook/react/pull/31398)) +- Display native tag for host components for Native (@ruslan [#32762](https://github.com/facebook/react/pull/32762)) +- Static Components panel layout (@ruslan [#33696](https://github.com/facebook/react/pull/33696), [#33517](https://github.com/facebook/react/pull/33517), [#34088](https://github.com/facebook/react/pull/34088)) +- Add Badge to Owners and sometimes stack traces (@sebmarkbage [#34106](https://github.com/facebook/react/pull/34106)) +- Make a non-editable name of KeyValue clickable ([34095](https://github.com/facebook/react/pull/34095)) +- Only inspect elements on left mouseclick (@eps1lon [#34361](https://github.com/facebook/react/pull/34361)) +- Rerender when the browser theme changes (@sebmarkbage [#33992](https://github.com/facebook/react/pull/33992)) +- Stop using native `title` for buttons/icons (@eps1lon [#34379](https://github.com/facebook/react/pull/34379)) +- Style clickable Owner components with angle brackets and bold (@sebmarkbage [#34096](https://github.com/facebook/react/pull/34096)) +- Swap Components tab layout based on container size (@eps1lon [#34035](https://github.com/facebook/react/pull/34035)) +- Use Visually Lighter Skeletons (@sebmarkbage [#34185](https://github.com/facebook/react/pull/34185)) + +--- + ### 6.1.5 July 4, 2025 diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json index 01d6e39c5e1..17c4c836e60 100644 --- a/packages/react-devtools/package.json +++ b/packages/react-devtools/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools", - "version": "6.1.5", + "version": "7.0.0", "description": "Use react-devtools outside of the browser", "license": "MIT", "repository": {